Repository: redhat-developer/service-binding-operator
Pull request #1130 (2022-04-13 19:33:57)

bind labeled workloads created after sb reconcile
# Changes

/kind enhancement

Workloads that we bound with label selectors won't get bound if they
were created after the service binding was first reconciled.  As a
workaround, periodically reconcile service bindings with label selectors
to pick up any new workloads.

This uses an exponential backoff strategy, which is the default for when
a duration before re-reconciling is not specified.  A fixed interval was
considered, but was decided against out of concerns for thundering herd
problems in a cluster.

Signed-off-by: Andy Sadler <ansadler@redhat.com>

# Submitter Checklist

As the author of this PR, please check off the items in this checklist:

- [ ] [Docs](https://github.com/redhat-developer/service-binding-operator/blob/master/CONTRIBUTING.md#docs) 
  included if any changes are user facing
- [x] [Tests](https://github.com/redhat-developer/service-binding-operator/blob/master/CONTRIBUTING.md#tests)
  included if any functionality added or changed. For bugfixes please include tests that can catch regressions
- [x] All acceptance test scenarios included in the PR which verifies a bugfix or a requested feature reported by a non-member are tagged with `@external-feedback` tag.
- [x] Follows the [commit message standard](https://github.com/redhat-developer/service-binding-operator/blob/master/CONTRIBUTING.md#commits)



openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please ask for approval from **sadlerap** after the PR has been reviewed.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=redhat-developer%2Fservice-binding-operator).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/redhat-developer/service-binding-operator/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["sadlerap"]} -->
openshift-ci[bot]: @sadlerap: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/4.11-acceptance | 57d6c69a8a920e48b87ab8b7b6d78d738eb24ca8 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.11-acceptance/1514326051088502784) | true | `/test 4.11-acceptance`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=1130). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:sadlerap).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci[bot]: @sadlerap: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/4.11-acceptance | 57d6c69a8a920e48b87ab8b7b6d78d738eb24ca8 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.11-acceptance/1514326051088502784) | true | `/test 4.11-acceptance`
ci/prow/4.10-acceptance | 57d6c69a8a920e48b87ab8b7b6d78d738eb24ca8 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.10-acceptance/1514326051038171136) | true | `/test 4.10-acceptance`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=1130). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:sadlerap).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci[bot]: @sadlerap: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/4.11-acceptance | 57d6c69a8a920e48b87ab8b7b6d78d738eb24ca8 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.11-acceptance/1514326051088502784) | true | `/test 4.11-acceptance`
ci/prow/4.10-acceptance | 57d6c69a8a920e48b87ab8b7b6d78d738eb24ca8 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.10-acceptance/1514326051038171136) | true | `/test 4.10-acceptance`
ci/prow/4.9-acceptance | 57d6c69a8a920e48b87ab8b7b6d78d738eb24ca8 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.9-acceptance/1514326051189166080) | true | `/test 4.9-acceptance`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=1130). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:sadlerap).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci[bot]: @sadlerap: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/4.11-acceptance | 57d6c69a8a920e48b87ab8b7b6d78d738eb24ca8 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.11-acceptance/1514326051088502784) | true | `/test 4.11-acceptance`
ci/prow/4.10-acceptance | 57d6c69a8a920e48b87ab8b7b6d78d738eb24ca8 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.10-acceptance/1514326051038171136) | true | `/test 4.10-acceptance`
ci/prow/4.9-acceptance | 57d6c69a8a920e48b87ab8b7b6d78d738eb24ca8 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.9-acceptance/1514326051189166080) | true | `/test 4.9-acceptance`
ci/prow/4.8-acceptance | 57d6c69a8a920e48b87ab8b7b6d78d738eb24ca8 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.8-acceptance/1514326051130445824) | true | `/test 4.8-acceptance`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=1130). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:sadlerap).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
pmacik: /retest
codecov[bot]: # [Codecov](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1130?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer) Report
> Merging [#1130](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1130?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer) (57d6c69) into [master](https://codecov.io/gh/redhat-developer/service-binding-operator/commit/78327d1fe237871694d546678383ba942095c26e?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer) (78327d1) will **decrease** coverage by `0.47%`.
> The diff coverage is `75.00%`.

[![Impacted file tree graph](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1130/graphs/tree.svg?width=650&height=150&src=pr&token=CICBeVWlrC&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer)](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1130?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer)

```diff
@@            Coverage Diff             @@
##           master    #1130      +/-   ##
==========================================
- Coverage   58.92%   58.44%   -0.48%     
==========================================
  Files          30       30              
  Lines        1670     2344     +674     
==========================================
+ Hits          984     1370     +386     
- Misses        560      848     +288     
  Partials      126      126              
```


| [Impacted Files](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1130?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer) | Coverage Δ | |
|---|---|---|
| [pkg/reconcile/pipeline/api.go](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1130/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer#diff-cGtnL3JlY29uY2lsZS9waXBlbGluZS9hcGkuZ28=) | `77.77% <ø> (+2.77%)` | :arrow_up: |
| [...kg/reconcile/pipeline/context/spec\_binding\_impl.go](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1130/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer#diff-cGtnL3JlY29uY2lsZS9waXBlbGluZS9jb250ZXh0L3NwZWNfYmluZGluZ19pbXBsLmdv) | `68.57% <0.00%> (-3.98%)` | :arrow_down: |
| [pkg/reconcile/pipeline/context/impl.go](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1130/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer#diff-cGtnL3JlY29uY2lsZS9waXBlbGluZS9jb250ZXh0L2ltcGwuZ28=) | `65.34% <100.00%> (+0.18%)` | :arrow_up: |
| [pkg/reconcile/pipeline/handler/project/impl.go](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1130/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer#diff-cGtnL3JlY29uY2lsZS9waXBlbGluZS9oYW5kbGVyL3Byb2plY3QvaW1wbC5nbw==) | `62.19% <100.00%> (-0.80%)` | :arrow_down: |
| [pkg/converter/unstructured.go](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1130/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer#diff-cGtnL2NvbnZlcnRlci91bnN0cnVjdHVyZWQuZ28=) | `18.91% <0.00%> (-3.31%)` | :arrow_down: |
| [pkg/binding/jsonpath.go](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1130/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer#diff-cGtnL2JpbmRpbmcvanNvbnBhdGguZ28=) | `66.66% <0.00%> (-2.57%)` | :arrow_down: |
| [pkg/reconcile/pipeline/secretbackedbindings.go](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1130/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer#diff-cGtnL3JlY29uY2lsZS9waXBlbGluZS9zZWNyZXRiYWNrZWRiaW5kaW5ncy5nbw==) | `64.28% <0.00%> (-2.39%)` | :arrow_down: |
| [pkg/binding/spec.go](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1130/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer#diff-cGtnL2JpbmRpbmcvc3BlYy5nbw==) | `70.58% <0.00%> (-2.14%)` | :arrow_down: |
| [apis/spec/v1alpha3/servicebinding\_webhook.go](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1130/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer#diff-YXBpcy9zcGVjL3YxYWxwaGEzL3NlcnZpY2ViaW5kaW5nX3dlYmhvb2suZ28=) | `65.38% <0.00%> (-1.29%)` | :arrow_down: |
| ... and [24 more](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1130/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1130?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1130?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer). Last update [78327d1...57d6c69](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1130?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer).

openshift-ci[bot]: @sadlerap: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
openshift-ci[bot]: @sadlerap: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/4.11-acceptance | 1d21dc504ccf167074e41fee06c37ac4e3e42a31 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.11-acceptance/1517592197992550400) | true | `/test 4.11-acceptance`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=1130). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:sadlerap).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci[bot]: @sadlerap: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/4.11-acceptance | 1d21dc504ccf167074e41fee06c37ac4e3e42a31 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.11-acceptance/1517592197992550400) | true | `/test 4.11-acceptance`
ci/prow/4.10-acceptance | 1d21dc504ccf167074e41fee06c37ac4e3e42a31 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.10-acceptance/1517592197917052928) | true | `/test 4.10-acceptance`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=1130). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:sadlerap).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci[bot]: @sadlerap: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/4.11-acceptance | 656cad5754e713cb193339745087519ccaed266e | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.11-acceptance/1517619054871842816) | true | `/test 4.11-acceptance`
ci/prow/4.10-acceptance | 656cad5754e713cb193339745087519ccaed266e | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.10-acceptance/1517619054838288384) | true | `/test 4.10-acceptance`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=1130). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:sadlerap).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci[bot]: @sadlerap: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/4.11-acceptance | 656cad5754e713cb193339745087519ccaed266e | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.11-acceptance/1517619054871842816) | true | `/test 4.11-acceptance`
ci/prow/4.10-acceptance | 656cad5754e713cb193339745087519ccaed266e | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.10-acceptance/1517619054838288384) | true | `/test 4.10-acceptance`
ci/prow/4.9-acceptance | 656cad5754e713cb193339745087519ccaed266e | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.9-acceptance/1517619054943145984) | true | `/test 4.9-acceptance`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=1130). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:sadlerap).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci[bot]: @sadlerap: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/4.11-acceptance | 656cad5754e713cb193339745087519ccaed266e | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.11-acceptance/1517619054871842816) | true | `/test 4.11-acceptance`
ci/prow/4.10-acceptance | 656cad5754e713cb193339745087519ccaed266e | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.10-acceptance/1517619054838288384) | true | `/test 4.10-acceptance`
ci/prow/4.9-acceptance | 656cad5754e713cb193339745087519ccaed266e | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.9-acceptance/1517619054943145984) | true | `/test 4.9-acceptance`
ci/prow/4.8-acceptance | 656cad5754e713cb193339745087519ccaed266e | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.8-acceptance/1517619054905397248) | true | `/test 4.8-acceptance`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=1130). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:sadlerap).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci[bot]: @sadlerap: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/4.9-acceptance | b2f9f685310e7b71ddb1f32cb5180854ea9a551b | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.9-acceptance/1518936385782812672) | true | `/test 4.9-acceptance`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=1130). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:sadlerap).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
sadlerap: /retest
openshift-ci[bot]: New changes are detected. LGTM label has been removed.
openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please ask for approval from **baijum** after the PR has been reviewed.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=redhat-developer%2Fservice-binding-operator).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/redhat-developer/service-binding-operator/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["baijum"]} -->
openshift-ci[bot]: @sadlerap: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/4.8-acceptance | 9a04214a24151d527866e6062bf9f2b3f5045f17 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.8-acceptance/1523721640498696192) | true | `/test 4.8-acceptance`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=1130). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:sadlerap).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
sadlerap: /retest
openshift-ci[bot]: @sadlerap: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/4.8-acceptance | 9a04214a24151d527866e6062bf9f2b3f5045f17 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.8-acceptance/1523758209234374656) | true | `/test 4.8-acceptance`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=1130). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:sadlerap).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci[bot]: @sadlerap: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/4.10-acceptance | 72abc5fce967dd410ad36b87cec2c43e112b7e22 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.10-acceptance/1524031931186941952) | true | `/test 4.10-acceptance`
ci/prow/4.8-acceptance | 72abc5fce967dd410ad36b87cec2c43e112b7e22 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.8-acceptance/1524031931216302080) | true | `/test 4.8-acceptance`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=1130). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:sadlerap).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
pmacik: /retest
openshift-ci[bot]: @sadlerap: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/4.8-acceptance | 72abc5fce967dd410ad36b87cec2c43e112b7e22 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.8-acceptance/1524354200950018048) | true | `/test 4.8-acceptance`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=1130). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:sadlerap).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci[bot]: @sadlerap: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/4.8-acceptance | fd9a903743237615e044c522026c0e02b9da32fb | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/1130/pull-ci-redhat-developer-service-binding-operator-master-4.8-acceptance/1524510701354225664) | true | `/test 4.8-acceptance`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=1130). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:sadlerap).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->

Patch:
diff --git a/Makefile b/Makefile
index 347ed942..c77f9f4c 100644
--- a/Makefile
+++ b/Makefile
@@ -35,6 +35,7 @@ CONTAINER_RUNTIME ?= docker
 
 QUAY_USERNAME ?= redhat-developer+travis
 REGISTRY_USERNAME ?= $(QUAY_USERNAME)
+REGISTRY_NAMESPACE ?= $(QUAY_USERNAME)
 QUAY_TOKEN ?= ""
 REGISTRY_PASSWORD ?= $(QUAY_TOKEN)
 
@@ -54,6 +55,15 @@ else
 TEST_ACCEPTANCE_TAGS_ARG ?= --tags="~@disabled" --tags="~@examples"
 endif
 
+# -- Variables for performance tests
+TEST_PERFORMANCE_OUTPUT_DIR ?= $(OUTPUT_DIR)/performance
+TEST_PERFORMANCE_ARTIFACTS ?= $(ARTIFACT_DIR)
+TEST_PERFORMANCE_NS_PREFIX ?= entanglement
+TEST_PERFORMANCE_USERS_PER_SCENARIO ?= 400
+OPENSHIFT_API ?=
+OPENSHIFT_USERNAME ?=
+OPENSHIFT_PASSWORD ?=
+
 GO ?= CGO_ENABLED=$(CGO_ENABLED) GOCACHE=$(GOCACHE) GOFLAGS="$(GOFLAGS)" GO111MODULE=$(GO111MODULE) go
 
 
@@ -63,7 +73,7 @@ GO ?= CGO_ENABLED=$(CGO_ENABLED) GOCACHE=$(GOCACHE) GOFLAGS="$(GOFLAGS)" GO111MO
 ## Runs linters
 lint: setup-venv lint-go-code lint-yaml lint-python-code lint-feature-files lint-conflicts
 
-YAML_FILES := $(shell find . -path ./vendor -prune -o -path ./config -prune -o -type f -regex ".*\.y[a]ml" -print)
+YAML_FILES := $(shell find . -path ./vendor -prune -o -path ./config -prune -o -path ./test/performance -prune -o -type f -regex ".*\.y[a]ml" -print)
 .PHONY: lint-yaml
 ## Runs yamllint on all yaml files
 lint-yaml: ${YAML_FILES}
@@ -325,3 +335,26 @@ clean:
 ## render site
 site:
 	$(CONTAINER_RUNTIME) run -u $(shell id -u) -e CI=true -e HOME=/antora -v ${PWD}:/antora:Z --rm -t antora/antora:2.3.4 antora-playbook.yaml
+
+.PHONY: test-performance-setup
+## Setup OpenShift cluster for performance test
+test-performance-setup:
+	@oc login $(OPENSHIFT_API) -u $(OPENSHIFT_USERNAME) -p $(OPENSHIFT_PASSWORD) --insecure-skip-tls-verify=true
+	QUAY_NAMESPACE=$(REGISTRY_NAMESPACE) ./test/performance/setup.sh
+
+.PHONY: test-performance
+## Run performance test
+test-performance: test-performance-setup deploy-from-index-image
+	OUTPUT_DIR=$(TEST_PERFORMANCE_OUTPUT_DIR) ./test/performance/run.sh $(TEST_PERFORMANCE_NS_PREFIX) $(TEST_PERFORMANCE_USERS_PER_SCENARIO)
+
+.PHONY: test-performance-collect-kpi
+## Collect KPI (Key Performance Indicators)
+test-performance-collect-kpi:
+	METRICS=$(TEST_PERFORMANCE_OUTPUT_DIR)/metrics RESULTS=$(TEST_PERFORMANCE_OUTPUT_DIR)/results  NS_PREFIX=$(TEST_PERFORMANCE_NS_PREFIX) ./test/performance/collect-kpi.sh
+
+.PHONY: test-performance-artifacts
+# Collect artifacts from performance test to be archived in CI
+test-performance-artifacts:
+	$(Q)echo "Gathering performance test artifacts"
+	$(Q)mkdir -p $(TEST_PERFORMANCE_ARTIFACTS) \
+		&& cp -rvf $(TEST_PERFORMANCE_OUTPUT_DIR) $(TEST_PERFORMANCE_ARTIFACTS)/
diff --git a/controllers/common.go b/controllers/common.go
index 35c457ae..d17dfa09 100644
--- a/controllers/common.go
+++ b/controllers/common.go
@@ -3,6 +3,7 @@ package controllers
 import (
 	"context"
 	"flag"
+
 	"github.com/redhat-developer/service-binding-operator/pkg/client/kubernetes"
 
 	"github.com/go-logr/logr"
@@ -103,7 +104,7 @@ func (r *BindingReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
 	} else {
 		log.Info("Deleted, unbind the application")
 	}
-	retry, err := r.pipeline.Process(serviceBinding) // <.>
+	retry, delay, err := r.pipeline.Process(serviceBinding) // <.>
 	if !retry && err == nil {
 		if serviceBinding.HasDeletionTimestamp() {
 			if apis.MaybeRemoveFinalizer(serviceBinding) {
@@ -113,7 +114,7 @@ func (r *BindingReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
 			}
 		}
 	}
-	result := ctrl.Result{Requeue: retry}
+	result := ctrl.Result{Requeue: retry, RequeueAfter: delay}
 	log.Info("Done", "retry", retry, "error", err)
 	if retry {
 		return result, err
diff --git a/docs/userguide/modules/creating-service-bindings/pages/binding-options.adoc b/docs/userguide/modules/creating-service-bindings/pages/binding-options.adoc
index 00c541a1..c98ddc4e 100644
--- a/docs/userguide/modules/creating-service-bindings/pages/binding-options.adoc
+++ b/docs/userguide/modules/creating-service-bindings/pages/binding-options.adoc
@@ -182,4 +182,59 @@ spec:
 ----
 <1>  Database service
 <2> Optional "id" field
-<3>  Translation service
\ No newline at end of file
+<3>  Translation service
+
+== Binding workloads from a label selector
+
+Sometimes, it may be useful to specify the workload being bound by using a label selector.  For
+instance, you may want to bind a service to every workload with the label `environment: production`
+set.  SBO is able to support this kind of binding by being able to bind with label selectors.  As an
+example, to project a single secret into multiple workloads, set up your application field like so:
+[source,yaml]
+----
+apiVersion: binding.operators.coreos.com/v1alpha1
+kind: ServiceBinding
+metadata:
+  name: multi-application-binding
+  namespace: service-binding-demo
+spec:
+  application:
+    labelSelector:
+      matchLabels:
+        environment: production
+    group: apps
+    version: v1
+    resource: deployments
+  services:
+    group: ""
+    version: v1
+    kind: Secret
+    name: super-secret-data
+----
+
+Or, using the spec API:
+[source,yaml]
+----
+apiVersion: servicebindings.io/v1alpha3
+kind: ServiceBinding
+metadata:
+  name: multi-application-binding
+  namespace: service-binding-demo
+spec:
+  workload:
+    selector:
+      matchLabels:
+        environment: production
+    apiVersion: app/v1
+    kind: Deployment
+  service:
+    apiVersion: v1
+    kind: Secret
+    name: super-secret-data
+----
+
+If a service binding is declared using these label selectors to pick up workloads, SBO will
+periodically attempt to find and bind new workloads that match the given label selector.
+
+As a note: it is currently forbidden to attempt a binding with both `name:` and `labelSelector`
+defined (or `selector` in the spec API).  Any attempt to do so will result in a error.
diff --git a/go.mod b/go.mod
index 328c9373..4c4c625f 100644
--- a/go.mod
+++ b/go.mod
@@ -9,6 +9,7 @@ require (
 	github.com/onsi/gomega v1.15.0
 	github.com/operator-framework/api v0.3.8
 	github.com/stretchr/testify v1.7.0
+	golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
 	k8s.io/api v0.22.1
 	k8s.io/apiextensions-apiserver v0.22.1
 	k8s.io/apimachinery v0.22.1
diff --git a/main.go b/main.go
index 338d0a60..64de2f0f 100644
--- a/main.go
+++ b/main.go
@@ -19,11 +19,12 @@ package main
 import (
 	"flag"
 	"fmt"
+	"os"
+	"sync"
+
 	"github.com/redhat-developer/service-binding-operator/apis/webhooks"
 	v1apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	v1beta1apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
-	"os"
-	"sync"
 
 	crdcontrollers "github.com/redhat-developer/service-binding-operator/controllers/crd"
 
diff --git a/pkg/reconcile/pipeline/api.go b/pkg/reconcile/pipeline/api.go
index e9eab677..05f3adcc 100644
--- a/pkg/reconcile/pipeline/api.go
+++ b/pkg/reconcile/pipeline/api.go
@@ -2,6 +2,7 @@ package pipeline
 
 import (
 	"fmt"
+	"time"
 
 	olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
 	"github.com/redhat-developer/service-binding-operator/pkg/binding"
@@ -21,7 +22,7 @@ type Pipeline interface {
 	// Returns true if processing should be repeated
 	// and optional error if occurred
 	// important: even if error occurred it might not be needed to retry processing
-	Process(binding interface{}) (bool, error)
+	Process(binding interface{}) (bool, time.Duration, error)
 }
 
 // A container-like object.  EnvFrom is optional; all other fields are required.
@@ -49,6 +50,7 @@ type FlowStatus struct {
 	Retry bool
 	Stop  bool
 	Err   error
+	Delay time.Duration
 }
 
 type HasResource interface {
@@ -167,6 +169,10 @@ type Context interface {
 	// The current processing stops and context gets closed
 	RetryProcessing(reason error)
 
+	// Indicates that the binding should be retried with a delay.  The context will determine the
+	// appropriate delay to add.  This will close the context, and is similar to RetryProcessing
+	DelayReprocessing(reason error)
+
 	// Indicates that en error has occurred while processing the binding
 	Error(err error)
 
@@ -186,6 +192,9 @@ type Context interface {
 	FlowStatus() FlowStatus
 
 	WorkloadResourceTemplate(gvr *schema.GroupVersionResource, containerPath string) (*WorkloadMapping, error)
+
+	// Is this service binding getting its workloads from a label selector?
+	HasLabelSelector() bool
 }
 
 // Provides context for a given service binding
diff --git a/pkg/reconcile/pipeline/builder/pipeline.go b/pkg/reconcile/pipeline/builder/pipeline.go
index 0ac6fcc7..81ad8089 100644
--- a/pkg/reconcile/pipeline/builder/pipeline.go
+++ b/pkg/reconcile/pipeline/builder/pipeline.go
@@ -2,6 +2,8 @@ package builder
 
 import (
 	"fmt"
+	"time"
+
 	"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline"
 	"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline/handler/collect"
 	"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline/handler/mapping"
@@ -16,7 +18,7 @@ type impl struct {
 	handlers    []pipeline.Handler
 }
 
-func (i *impl) Process(binding interface{}) (retry bool, err error) {
+func (i *impl) Process(binding interface{}) (retry bool, delay time.Duration, err error) {
 	defer func() {
 		if perr := recover(); perr != nil {
 			retry = true
@@ -25,7 +27,7 @@ func (i *impl) Process(binding interface{}) (retry bool, err error) {
 	}()
 	ctx, err := i.ctxProvider.Get(binding)
 	if err != nil {
-		return false, err
+		return false, time.Duration(0), err
 	}
 	var status pipeline.FlowStatus
 	for _, h := range i.handlers {
@@ -37,9 +39,9 @@ func (i *impl) Process(binding interface{}) (retry bool, err error) {
 	}
 	err = ctx.Close()
 	if err != nil {
-		return true, err
+		return true, time.Duration(0), err
 	}
-	return status.Retry, status.Err
+	return status.Retry, status.Delay, status.Err
 }
 
 type builder struct {
diff --git a/pkg/reconcile/pipeline/context/flow_ctrl.go b/pkg/reconcile/pipeline/context/flow_ctrl.go
index d6db8b8b..ec0071a1 100644
--- a/pkg/reconcile/pipeline/context/flow_ctrl.go
+++ b/pkg/reconcile/pipeline/context/flow_ctrl.go
@@ -1,11 +1,16 @@
 package context
 
-import "github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline"
+import (
+	"time"
+
+	"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline"
+)
 
 type flowCtrl struct {
 	retry bool
 	stop  bool
 	err   error
+	delay time.Duration
 }
 
 func (i *flowCtrl) FlowStatus() pipeline.FlowStatus {
@@ -13,6 +18,7 @@ func (i *flowCtrl) FlowStatus() pipeline.FlowStatus {
 		Retry: i.retry,
 		Stop:  i.stop,
 		Err:   i.err,
+		Delay: i.delay,
 	}
 }
 
@@ -20,6 +26,14 @@ func (i *flowCtrl) RetryProcessing(reason error) {
 	i.retry = true
 	i.stop = true
 	i.err = reason
+	i.delay = 0
+}
+
+func (i *flowCtrl) RetryProcessingWithDelay(reason error, delay time.Duration) {
+	i.retry = true
+	i.stop = reason != nil // for label selection
+	i.err = reason
+	i.delay = delay
 }
 
 func (i *flowCtrl) Error(err error) {
diff --git a/pkg/reconcile/pipeline/context/impl.go b/pkg/reconcile/pipeline/context/impl.go
index a6814b2e..36fd9850 100644
--- a/pkg/reconcile/pipeline/context/impl.go
+++ b/pkg/reconcile/pipeline/context/impl.go
@@ -6,15 +6,18 @@ import (
 	"encoding/hex"
 	"fmt"
 	"sort"
+	"time"
 
 	"github.com/redhat-developer/service-binding-operator/apis"
 	"github.com/redhat-developer/service-binding-operator/apis/binding/v1alpha1"
 	"github.com/redhat-developer/service-binding-operator/apis/spec/v1alpha3"
 	"github.com/redhat-developer/service-binding-operator/pkg/client/kubernetes"
 	"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline/context/service"
+	"golang.org/x/time/rate"
 	authv1 "k8s.io/api/authentication/v1"
 	v1 "k8s.io/api/authorization/v1"
 	clientauthzv1 "k8s.io/client-go/kubernetes/typed/authorization/v1"
+	"k8s.io/client-go/util/workqueue"
 
 	"github.com/redhat-developer/service-binding-operator/pkg/converter"
 	"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline"
@@ -71,6 +74,8 @@ type impl struct {
 	serviceBuilder service.Builder
 
 	resourceMapping *pipeline.WorkloadMapping
+
+	labelSelectionRateLimiter workqueue.RateLimiter
 }
 
 type bindingImpl struct {
@@ -128,6 +133,12 @@ var Provider = func(client dynamic.Interface, subjectAccessReviewClient clientau
 							return apis.Requester(sb.ObjectMeta)
 						},
 						serviceBuilder: service.NewBuilder(typeLookup).WithClient(client).LookOwnedResources(sb.Spec.DetectBindingResources),
+						labelSelectionRateLimiter: workqueue.NewMaxOfRateLimiter(
+							workqueue.NewItemExponentialFailureRateLimiter(5*time.Millisecond, 2*time.Minute),
+							&workqueue.BucketRateLimiter{
+								Limiter: rate.NewLimiter(rate.Limit(10), 100),
+							},
+						),
 					},
 					serviceBinding: sb,
 				}, nil
@@ -257,7 +268,7 @@ func (i *bindingImpl) Applications() ([]pipeline.Application, error) {
 				resourceMapping:   *mappingTemplate,
 			})
 		}
-		if i.serviceBinding.Spec.Application.LabelSelector != nil && i.serviceBinding.Spec.Application.LabelSelector.MatchLabels != nil {
+		if i.HasLabelSelector() {
 			matchLabels := i.serviceBinding.Spec.Application.LabelSelector.MatchLabels
 			opts := metav1.ListOptions{
 				LabelSelector: labels.Set(matchLabels).String(),
@@ -571,3 +582,12 @@ func (i *impl) WorkloadResourceTemplate(gvr *schema.GroupVersionResource, contai
 	i.resourceMapping = workloadMapping
 	return workloadMapping, nil
 }
+
+func (i *bindingImpl) HasLabelSelector() bool {
+	return i.serviceBinding.Spec.Application.LabelSelector != nil
+}
+
+func (i *impl) DelayReprocessing(err error) {
+	delay := i.labelSelectionRateLimiter.When(i.bindingMeta)
+	i.RetryProcessingWithDelay(err, delay)
+}
diff --git a/pkg/reconcile/pipeline/context/spec_binding_impl.go b/pkg/reconcile/pipeline/context/spec_binding_impl.go
index 9da02513..7834902a 100644
--- a/pkg/reconcile/pipeline/context/spec_binding_impl.go
+++ b/pkg/reconcile/pipeline/context/spec_binding_impl.go
@@ -3,6 +3,7 @@ package context
 import (
 	"context"
 	"fmt"
+	"time"
 
 	"github.com/redhat-developer/service-binding-operator/apis"
 	"github.com/redhat-developer/service-binding-operator/apis/spec/v1alpha3"
@@ -10,6 +11,7 @@ import (
 	"github.com/redhat-developer/service-binding-operator/pkg/converter"
 	"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline"
 	"github.com/redhat-developer/service-binding-operator/pkg/reconcile/pipeline/context/service"
+	"golang.org/x/time/rate"
 	"k8s.io/api/authentication/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -18,6 +20,7 @@ import (
 	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/client-go/dynamic"
 	authv1 "k8s.io/client-go/kubernetes/typed/authorization/v1"
+	"k8s.io/client-go/util/workqueue"
 )
 
 var _ pipeline.Context = &specImpl{}
@@ -64,6 +67,12 @@ var SpecProvider = func(client dynamic.Interface, subjectAccessReviewClient auth
 							return apis.Requester(sb.ObjectMeta)
 						},
 						serviceBuilder: service.NewBuilder(typeLookup).WithClient(client),
+						labelSelectionRateLimiter: workqueue.NewMaxOfRateLimiter(
+							workqueue.NewItemExponentialFailureRateLimiter(5*time.Millisecond, 2*time.Minute),
+							&workqueue.BucketRateLimiter{
+								Limiter: rate.NewLimiter(rate.Limit(10), 100),
+							},
+						),
 					},
 					serviceBinding: sb,
 				}
@@ -164,7 +173,7 @@ func (i *specImpl) Applications() ([]pipeline.Application, error) {
 				resourceMapping:        *mappingTemplate,
 			})
 		}
-		if i.serviceBinding.Spec.Workload.Selector != nil && i.serviceBinding.Spec.Workload.Selector.MatchLabels != nil {
+		if i.HasLabelSelector() {
 			matchLabels := i.serviceBinding.Spec.Workload.Selector.MatchLabels
 			opts := metav1.ListOptions{
 				LabelSelector: labels.Set(matchLabels).String(),
@@ -209,3 +218,7 @@ func (s *specImpl) NamingTemplate() string {
 func (s *specImpl) Mappings() map[string]string {
 	return make(map[string]string)
 }
+
+func (i *specImpl) HasLabelSelector() bool {
+	return i.serviceBinding != nil && i.serviceBinding.Spec.Workload.Selector != nil
+}
diff --git a/pkg/reconcile/pipeline/handler/project/impl.go b/pkg/reconcile/pipeline/handler/project/impl.go
index 4032728e..1d2fc89c 100644
--- a/pkg/reconcile/pipeline/handler/project/impl.go
+++ b/pkg/reconcile/pipeline/handler/project/impl.go
@@ -49,6 +49,10 @@ func PreFlightCheck(mandatoryBindingKeys ...string) func(pipeline.Context) {
 
 func PostFlightCheck(ctx pipeline.Context) {
 	ctx.SetCondition(apis.Conditions().InjectionReady().Reason("ApplicationUpdated").Build())
+	if ctx.HasLabelSelector() {
+		// Force periodic reprocessing of this service binding to catch new workloads
+		ctx.DelayReprocessing(nil)
+	}
 }
 
 func InjectSecretRef(ctx pipeline.Context) {
diff --git a/pkg/reconcile/pipeline/mapping.go b/pkg/reconcile/pipeline/mapping.go
index a1548b54..1bd07d13 100644
--- a/pkg/reconcile/pipeline/mapping.go
+++ b/pkg/reconcile/pipeline/mapping.go
@@ -209,6 +209,10 @@ func addRaw(obj map[string]interface{}, resource interface{}, data []map[string]
 }
 
 func (container *MetaContainer) AddEnvVars(vars []corev1.EnvVar) error {
+	for _, v := range vars {
+		// ignore errors, we could be projecting env vars that don't exist yet
+		_ = container.RemoveEnvVars(v.Name)
+	}
 	var data []map[string]interface{}
 	for _, variable := range vars {
 		x, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&variable)
@@ -241,6 +245,8 @@ func (container *MetaContainer) RemoveEnvVars(name string) error {
 
 func (container *MetaContainer) AddEnvFromVar(envVar corev1.EnvFromSource) error {
 	if len(container.EnvFrom) != 0 {
+		// ignore errors, we could be projecting env vars that don't exist yet
+		_ = container.RemoveEnvFromVars(envVar.SecretRef.Name)
 		data, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&envVar)
 		if err != nil {
 			return err
diff --git a/pkg/reconcile/pipeline/mocks/mocks_pipeline.go b/pkg/reconcile/pipeline/mocks/mocks_pipeline.go
index d91f66ec..9624f368 100644
--- a/pkg/reconcile/pipeline/mocks/mocks_pipeline.go
+++ b/pkg/reconcile/pipeline/mocks/mocks_pipeline.go
@@ -147,6 +147,18 @@ func (mr *MockContextMockRecorder) Close() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockContext)(nil).Close))
 }
 
+// DelayReprocessing mocks base method.
+func (m *MockContext) DelayReprocessing(arg0 error) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "DelayReprocessing", arg0)
+}
+
+// DelayReprocessing indicates an expected call of DelayReprocessing.
+func (mr *MockContextMockRecorder) DelayReprocessing(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DelayReprocessing", reflect.TypeOf((*MockContext)(nil).DelayReprocessing), arg0)
+}
+
 // EnvBindings mocks base method.
 func (m *MockContext) EnvBindings() []*pipeline.EnvBinding {
 	m.ctrl.T.Helper()
@@ -187,6 +199,20 @@ func (mr *MockContextMockRecorder) FlowStatus() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FlowStatus", reflect.TypeOf((*MockContext)(nil).FlowStatus))
 }
 
+// HasLabelSelector mocks base method.
+func (m *MockContext) HasLabelSelector() bool {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "HasLabelSelector")
+	ret0, _ := ret[0].(bool)
+	return ret0
+}
+
+// HasLabelSelector indicates an expected call of HasLabelSelector.
+func (mr *MockContextMockRecorder) HasLabelSelector() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasLabelSelector", reflect.TypeOf((*MockContext)(nil).HasLabelSelector))
+}
+
 // Mappings mocks base method.
 func (m *MockContext) Mappings() map[string]string {
 	m.ctrl.T.Helper()
diff --git a/vendor/modules.txt b/vendor/modules.txt
index a7c450e3..825ec76d 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -213,6 +213,7 @@ golang.org/x/text/transform
 golang.org/x/text/unicode/bidi
 golang.org/x/text/unicode/norm
 # golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
+## explicit
 golang.org/x/time/rate
 # golang.org/x/tools v0.1.2
 golang.org/x/tools/go/ast/inspector


Test patch:
diff --git a/pkg/reconcile/pipeline/builder/pipeline_integration_test.go b/pkg/reconcile/pipeline/builder/pipeline_integration_test.go
index 9fbb2e04..278ddf61 100644
--- a/pkg/reconcile/pipeline/builder/pipeline_integration_test.go
+++ b/pkg/reconcile/pipeline/builder/pipeline_integration_test.go
@@ -3,6 +3,7 @@ package builder_test
 import (
 	c "context"
 	"reflect"
+	"time"
 
 	"github.com/redhat-developer/service-binding-operator/apis"
 	"github.com/redhat-developer/service-binding-operator/apis/binding/v1alpha1"
@@ -128,9 +129,10 @@ var _ = Describe("Default Pipeline", func() {
 
 		p := builder.DefaultBuilder.WithContextProvider(context.Provider(client, authClient.SubjectAccessReviews(), typeLookup)).Build()
 
-		retry, err := p.Process(sb)
+		retry, delay, err := p.Process(sb)
 		Expect(err).NotTo(HaveOccurred())
 		Expect(retry).To(BeFalse())
+		Expect(delay).To(Equal(time.Duration(0)))
 
 		u, err := client.Resource(v1alpha1.GroupVersionResource).Namespace(sb.Namespace).Get(c.Background(), sb.Name, metav1.GetOptions{})
 		Expect(err).NotTo(HaveOccurred())
diff --git a/pkg/reconcile/pipeline/builder/pipeline_test.go b/pkg/reconcile/pipeline/builder/pipeline_test.go
index 967343c3..af5fcc92 100644
--- a/pkg/reconcile/pipeline/builder/pipeline_test.go
+++ b/pkg/reconcile/pipeline/builder/pipeline_test.go
@@ -3,6 +3,7 @@ package builder_test
 import (
 	"errors"
 	"fmt"
+	"time"
 
 	"github.com/golang/mock/gomock"
 	"github.com/redhat-developer/service-binding-operator/apis/binding/v1alpha1"
@@ -42,9 +43,10 @@ var _ = Describe("Pipeline", func() {
 		ctx.EXPECT().Close().Return(nil)
 		ctx.EXPECT().FlowStatus().Return(pipeline.FlowStatus{}).Times(2)
 
-		retry, err := p.Process(&v1alpha1.ServiceBinding{})
+		retry, delay, err := p.Process(&v1alpha1.ServiceBinding{})
 		Expect(err).NotTo(HaveOccurred())
 		Expect(retry).To(BeFalse())
+		Expect(delay).To(Equal(time.Duration(0)))
 	})
 
 	It("should report error in case of ctx closing error", func() {
@@ -58,9 +60,10 @@ var _ = Describe("Pipeline", func() {
 		ctx.EXPECT().Close().Return(err)
 		ctx.EXPECT().FlowStatus().Return(pipeline.FlowStatus{}).Times(2)
 
-		retry, err := p.Process(&v1alpha1.ServiceBinding{})
+		retry, delay, err := p.Process(&v1alpha1.ServiceBinding{})
 		Expect(err).To(Equal(err))
 		Expect(retry).To(BeTrue())
+		Expect(delay).To(Equal(time.Duration(0)))
 	})
 
 	It("should stop processing if retry requested and propagate error back to caller", func() {
@@ -79,9 +82,10 @@ var _ = Describe("Pipeline", func() {
 		ctx.EXPECT().FlowStatus().Return(pipeline.FlowStatus{})
 		ctx.EXPECT().FlowStatus().Return(pipeline.FlowStatus{Retry: true, Stop: true, Err: err})
 
-		retry, err := p.Process(&v1alpha1.ServiceBinding{})
+		retry, delay, err := p.Process(&v1alpha1.ServiceBinding{})
 		Expect(err).To(Equal(err))
 		Expect(retry).To(BeTrue())
+		Expect(delay).To(Equal(time.Duration(0)))
 	})
 
 	It("should retry processing if panic occurred in a handler", func() {
@@ -100,9 +104,10 @@ var _ = Describe("Pipeline", func() {
 		ctx.EXPECT().FlowStatus().Return(pipeline.FlowStatus{})
 		ctx.EXPECT().FlowStatus().Return(pipeline.FlowStatus{Retry: true, Stop: true, Err: err})
 
-		retry, rerr := p.Process(&v1alpha1.ServiceBinding{})
+		retry, delay, rerr := p.Process(&v1alpha1.ServiceBinding{})
 		Expect(rerr).To(Equal(err))
 		Expect(retry).To(BeTrue())
+		Expect(delay).To(Equal(time.Duration(0)))
 	})
 
 	It("should stop without retry and error and propagate that back to caller", func() {
@@ -119,9 +124,10 @@ var _ = Describe("Pipeline", func() {
 		ctx.EXPECT().FlowStatus().Return(pipeline.FlowStatus{})
 		ctx.EXPECT().FlowStatus().Return(pipeline.FlowStatus{Retry: false, Stop: true, Err: nil})
 
-		retry, err := p.Process(&v1alpha1.ServiceBinding{})
+		retry, delay, err := p.Process(&v1alpha1.ServiceBinding{})
 		Expect(err).NotTo(HaveOccurred())
 		Expect(retry).To(BeFalse())
+		Expect(delay).To(Equal(time.Duration(0)))
 	})
 
 	It("error on closing context should be propagated back to caller even if handlers did not report any", func() {
@@ -135,9 +141,10 @@ var _ = Describe("Pipeline", func() {
 		ctx.EXPECT().Close().Return(err)
 		ctx.EXPECT().FlowStatus().Return(pipeline.FlowStatus{}).Times(2)
 
-		retry, err := p.Process(&v1alpha1.ServiceBinding{})
+		retry, delay, err := p.Process(&v1alpha1.ServiceBinding{})
 		Expect(err).To(Equal(err))
 		Expect(retry).To(BeTrue())
+		Expect(delay).To(Equal(time.Duration(0)))
 	})
 
 	It("should retry processing if panic occurs when closing context", func() {
@@ -149,9 +156,10 @@ var _ = Describe("Pipeline", func() {
 		ctx.EXPECT().Close().DoAndReturn(func() { panic("foo") })
 		ctx.EXPECT().FlowStatus().Return(pipeline.FlowStatus{}).Times(1)
 
-		retry, perr := p.Process(&v1alpha1.ServiceBinding{})
+		retry, delay, perr := p.Process(&v1alpha1.ServiceBinding{})
 		Expect(perr).To(Equal(err))
 		Expect(retry).To(BeTrue())
+		Expect(delay).To(Equal(time.Duration(0)))
 	})
 
 	It("should retry processing if panic occurs when calling context provider", func() {
@@ -161,9 +169,33 @@ var _ = Describe("Pipeline", func() {
 		provider.EXPECT().Get(&v1alpha1.ServiceBinding{}).DoAndReturn(func(b interface{}) { panic("foo") })
 		p := builder.Builder().WithContextProvider(provider).WithHandlers(h1).Build()
 
-		retry, perr := p.Process(&v1alpha1.ServiceBinding{})
+		retry, delay, perr := p.Process(&v1alpha1.ServiceBinding{})
 		Expect(perr).To(Equal(err))
 		Expect(retry).To(BeTrue())
+		Expect(delay).To(Equal(time.Duration(0)))
+	})
+
+	It("should retry processing with a delay if the pipeline requests it", func() {
+		err := errors.New("foo")
+		delay := 2 * time.Minute
+
+		h1 := defHandler()
+		h1.EXPECT().Handle(ctx)
+		h2 := func(c pipeline.Context) {
+			c.RetryProcessing(err)
+		}
+		h3 := defHandler()
+		p := builder.Builder().WithContextProvider(&ctxProvider{ctx: ctx}).WithHandlers(h1, pipeline.HandlerFunc(h2), h3).Build()
+
+		ctx.EXPECT().RetryProcessing(err)
+		ctx.EXPECT().Close().Return(nil)
+		ctx.EXPECT().FlowStatus().Return(pipeline.FlowStatus{})
+		ctx.EXPECT().FlowStatus().Return(pipeline.FlowStatus{Retry: true, Stop: true, Err: err, Delay: delay})
+
+		retry, rdelay, err := p.Process(&v1alpha1.ServiceBinding{})
+		Expect(err).To(Equal(err))
+		Expect(retry).To(BeTrue())
+		Expect(rdelay).To(Equal(delay))
 	})
 })
 
diff --git a/pkg/reconcile/pipeline/context/impl_test.go b/pkg/reconcile/pipeline/context/impl_test.go
index caa82d31..60a8b1b9 100644
--- a/pkg/reconcile/pipeline/context/impl_test.go
+++ b/pkg/reconcile/pipeline/context/impl_test.go
@@ -263,6 +263,66 @@ var _ = Describe("Context", func() {
 			_, err = ctx.Applications()
 			Expect(err).To(HaveOccurred())
 		})
+		It("should report labels on service bindings when they exist", func() {
+			sb := bindingapi.ServiceBinding{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "sb1",
+					Namespace: "ns1",
+				},
+				Spec: bindingapi.ServiceBindingSpec{
+					Application: bindingapi.Application{
+						Ref: bindingapi.Ref{
+							Group:   "app",
+							Version: "v1",
+							Kind:    "Foo",
+						},
+						LabelSelector: &metav1.LabelSelector{
+							MatchLabels: map[string]string{"foo": "bar"},
+						},
+					},
+				},
+			}
+
+			u := &unstructured.Unstructured{}
+			u.SetName("app1")
+			u.SetNamespace(sb.Namespace)
+			u.SetGroupVersionKind(schema.GroupVersionKind{Group: "app", Version: "v1", Kind: "Foo"})
+			client := fake.NewSimpleDynamicClient(runtime.NewScheme(), u)
+			authClient := &fakeauth.FakeAuthorizationV1{}
+
+			ctx, err := Provider(client, authClient.SubjectAccessReviews(), typeLookup).Get(&sb)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ctx.HasLabelSelector()).To(BeTrue())
+		})
+		It("should not report labels on service bindings when they don't exist", func() {
+			sb := bindingapi.ServiceBinding{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "sb1",
+					Namespace: "ns1",
+				},
+				Spec: bindingapi.ServiceBindingSpec{
+					Application: bindingapi.Application{
+						Ref: bindingapi.Ref{
+							Group:   "app",
+							Version: "v1",
+							Kind:    "Foo",
+							Name:    "app1",
+						},
+					},
+				},
+			}
+
+			u := &unstructured.Unstructured{}
+			u.SetName("app1")
+			u.SetNamespace(sb.Namespace)
+			u.SetGroupVersionKind(schema.GroupVersionKind{Group: "app", Version: "v1", Kind: "Foo"})
+			client := fake.NewSimpleDynamicClient(runtime.NewScheme(), u)
+			authClient := &fakeauth.FakeAuthorizationV1{}
+
+			ctx, err := Provider(client, authClient.SubjectAccessReviews(), typeLookup).Get(&sb)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ctx.HasLabelSelector()).To(BeFalse())
+		})
 	})
 
 	Describe("Services", func() {
diff --git a/pkg/reconcile/pipeline/context/spec_binding_impl_test.go b/pkg/reconcile/pipeline/context/spec_binding_impl_test.go
index b384d8ae..e1cc2cf0 100644
--- a/pkg/reconcile/pipeline/context/spec_binding_impl_test.go
+++ b/pkg/reconcile/pipeline/context/spec_binding_impl_test.go
@@ -315,6 +315,60 @@ var _ = Describe("Spec API Context", func() {
 			Expect(err).NotTo(HaveOccurred())
 			Expect(*containers).To(Equal(mct))
 		})
+		It("should report labels on service bindings when they exist", func() {
+			sb := specapi.ServiceBinding{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "sb1",
+					Namespace: "ns1",
+				},
+				Spec: specapi.ServiceBindingSpec{
+					Workload: specapi.ServiceBindingWorkloadReference{
+						APIVersion: "foo/v1",
+						Kind:       "Foo",
+						Selector: &metav1.LabelSelector{
+							MatchLabels: map[string]string{"foo": "bar"},
+						},
+					},
+				},
+			}
+
+			u := &unstructured.Unstructured{}
+			u.SetName("app1")
+			u.SetNamespace(sb.Namespace)
+			u.SetGroupVersionKind(schema.GroupVersionKind{Group: "app", Version: "v1", Kind: "Foo"})
+			client := fake.NewSimpleDynamicClient(runtime.NewScheme(), u)
+			authClient := &fakeauth.FakeAuthorizationV1{}
+
+			ctx, err := Provider(client, authClient.SubjectAccessReviews(), typeLookup).Get(&sb)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ctx.HasLabelSelector()).To(BeTrue())
+		})
+		It("should not report labels on service bindings when they don't exist", func() {
+			sb := specapi.ServiceBinding{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "sb1",
+					Namespace: "ns1",
+				},
+				Spec: specapi.ServiceBindingSpec{
+					Workload: specapi.ServiceBindingWorkloadReference{
+						APIVersion: "app/v1",
+						Kind:       "Foo",
+						Name:       "app1",
+					},
+				},
+			}
+
+			u := &unstructured.Unstructured{}
+			u.SetName("app1")
+			u.SetNamespace(sb.Namespace)
+			u.SetGroupVersionKind(schema.GroupVersionKind{Group: "app", Version: "v1", Kind: "Foo"})
+			client := fake.NewSimpleDynamicClient(runtime.NewScheme(), u)
+			authClient := &fakeauth.FakeAuthorizationV1{}
+
+			ctx, err := Provider(client, authClient.SubjectAccessReviews(), typeLookup).Get(&sb)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(ctx.HasLabelSelector()).To(BeFalse())
+		})
 	})
 
 	Describe("Services", func() {
diff --git a/pkg/reconcile/pipeline/handler/project/impl_test.go b/pkg/reconcile/pipeline/handler/project/impl_test.go
index 22a59454..e025ea16 100644
--- a/pkg/reconcile/pipeline/handler/project/impl_test.go
+++ b/pkg/reconcile/pipeline/handler/project/impl_test.go
@@ -151,6 +151,7 @@ var _ = Describe("Inject Bindings as Env vars handler", func() {
 			Expect(err).NotTo(HaveOccurred())
 			Expect(&d).To(Equal(expected))
 		})
+
 	})
 
 	Context("env bindings are set", func() {
@@ -359,6 +360,35 @@ var _ = Describe("Injection Preflight checks", func() {
 	})
 })
 
+var _ = Describe("Injection Postflight checks", func() {
+	var (
+		mockCtrl *gomock.Controller
+		ctx      *mocks.MockContext
+	)
+
+	BeforeEach(func() {
+		mockCtrl = gomock.NewController(GinkgoT())
+		ctx = mocks.NewMockContext(mockCtrl)
+	})
+
+	AfterEach(func() {
+		mockCtrl.Finish()
+	})
+
+	It("should trigger rebinding when labels are present", func() {
+		ctx.EXPECT().SetCondition(apis.Conditions().InjectionReady().Reason("ApplicationUpdated").Build())
+		ctx.EXPECT().HasLabelSelector().Return(true)
+		ctx.EXPECT().DelayReprocessing(nil)
+		project.PostFlightCheck(ctx)
+	})
+
+	It("should not trigger rebinding when labels are not present", func() {
+		ctx.EXPECT().SetCondition(apis.Conditions().InjectionReady().Reason("ApplicationUpdated").Build())
+		ctx.EXPECT().HasLabelSelector().Return(false)
+		project.PostFlightCheck(ctx)
+	})
+})
+
 var _ = Describe("Inject bindings as files", func() {
 	var (
 		mockCtrl *gomock.Controller
diff --git a/pkg/reconcile/pipeline/mapping_test.go b/pkg/reconcile/pipeline/mapping_test.go
index 83de0b15..9f0f68a1 100644
--- a/pkg/reconcile/pipeline/mapping_test.go
+++ b/pkg/reconcile/pipeline/mapping_test.go
@@ -297,6 +297,33 @@ var _ = Describe("Mapping workloads", func() {
 			}))
 		})
 
+		It("should not set service binding environment variables more than once", func() {
+			envVars := []v1.EnvVar{
+				{
+					Name:  "FOO",
+					Value: "foo",
+				},
+			}
+
+			envs, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&map[string]interface{}{"env": envVars})
+			Expect(err).NotTo(HaveOccurred())
+			container := pipeline.MetaContainer{
+				Data: envs,
+				Env:  []string{"env"},
+			}
+
+			err = container.AddEnvVars(envVars)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(container.Data).To(Equal(map[string]interface{}{
+				"env": []map[string]interface{}{
+					{
+						"name":  "FOO",
+						"value": "foo",
+					},
+				},
+			}))
+		})
+
 		It("should add new environment variables from a secret", func() {
 			container := pipeline.MetaContainer{
 				Data:    map[string]interface{}{},
@@ -317,6 +344,30 @@ var _ = Describe("Mapping workloads", func() {
 			Expect(container.Data).To(Equal(map[string]interface{}{"envFrom": []map[string]interface{}{env}}))
 		})
 
+		It("should not set environment variables from a secret more than once", func() {
+			container := pipeline.MetaContainer{
+				Data:    map[string]interface{}{},
+				EnvFrom: []string{"envFrom"},
+			}
+			envFromVars := v1.EnvFromSource{
+				SecretRef: &v1.SecretEnvSource{
+					LocalObjectReference: v1.LocalObjectReference{
+						Name: "spam",
+					},
+				},
+			}
+			env, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&envFromVars)
+			Expect(err).NotTo(HaveOccurred())
+
+			err = container.AddEnvFromVar(envFromVars)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(container.Data).To(Equal(map[string]interface{}{"envFrom": []map[string]interface{}{env}}))
+
+			err = container.AddEnvFromVar(envFromVars)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(container.Data).To(Equal(map[string]interface{}{"envFrom": []map[string]interface{}{env}}))
+		})
+
 		It("should not add new environment variables from a secret when envFrom is not set", func() {
 			container := pipeline.MetaContainer{}
 			envFromVars := v1.EnvFromSource{
@@ -328,7 +379,6 @@ var _ = Describe("Mapping workloads", func() {
 			}
 			err := container.AddEnvFromVar(envFromVars)
 			Expect(err).To(HaveOccurred())
-
 		})
 
 		It("should remove environment variables from a secret", func() {
diff --git a/test/acceptance/features/bindMultipleAppsToSingleService.feature b/test/acceptance/features/bindMultipleAppsToSingleService.feature
index 823a8010..237bcd42 100644
--- a/test/acceptance/features/bindMultipleAppsToSingleService.feature
+++ b/test/acceptance/features/bindMultipleAppsToSingleService.feature
@@ -6,6 +6,7 @@ Feature: Bind multiple applications to a single service
     Background:
         Given Namespace [TEST_NAMESPACE] is used
         * Service Binding Operator is running
+        * OLM Operator "custom_app" is running
 
     Scenario: Successfully bind two applications to a single service
         Given Test applications "gen-app-a-s-f-1" and "gen-app-a-s-f-2" is running
@@ -16,7 +17,7 @@ Feature: Bind multiple applications to a single service
             apiVersion: v1
             kind: Secret
             metadata:
-                name: backend-secret
+                name: $scenario_id-secret
             stringData:
                 username: AzureDiamond
                 password: hunter2
@@ -31,7 +32,7 @@ Feature: Bind multiple applications to a single service
                     service.binding: path={.status.data.dbCredentials},objectType=Secret,elementType=map
             status:
                 data:
-                    dbCredentials: backend-secret
+                    dbCredentials: $scenario_id-secret
             """
         When Service Binding is applied
             """
@@ -57,3 +58,232 @@ Feature: Bind multiple applications to a single service
         Then Service Binding is ready
         And The application env var "BACKEND_USERNAME" has value "AzureDiamond" in both apps
         And The application env var "BACKEND_PASSWORD" has value "hunter2" in both apps
+
+    Scenario: Bind applications via label selector after the service binding has been created
+        Given The Secret is present
+            """
+            apiVersion: v1
+            kind: Secret
+            metadata:
+                name: $scenario_id-secret
+            stringData:
+                username: AzureDiamond
+                password: hunter2
+            """
+        And The Custom Resource is present
+            """
+            apiVersion: "stable.example.com/v1"
+            kind: AppConfig
+            metadata:
+                name: $scenario_id-appconfig-1
+                labels:
+                    app-custom: $scenario_id
+            spec:
+                spec:
+                    containers:
+                        - name: foo
+            """
+        And The Workload Resource Mapping is present
+            """
+            apiVersion: servicebinding.io/v1alpha3
+            kind: ClusterWorkloadResourceMapping
+            metadata:
+                name: appconfigs.stable.example.com
+            spec:
+                versions:
+                  - version: "*"
+                    containers:
+                      - path: .spec.spec.containers[*]
+                    volumes: .spec.spec.volumes
+            """
+        And Service Binding is applied
+            """
+            apiVersion: binding.operators.coreos.com/v1alpha1
+            kind: ServiceBinding
+            metadata:
+                name: $scenario_id-binding
+            spec:
+                services:
+                  - group: ""
+                    version: v1
+                    kind: Secret
+                    name: $scenario_id-secret
+                application:
+                    labelSelector:
+                      matchLabels:
+                        app-custom: $scenario_id
+                    group: stable.example.com
+                    version: v1
+                    resource: appconfigs
+            """
+        And Service Binding is ready
+        And jsonpath "{.spec.spec.containers[0].volumeMounts}" on "appconfigs/$scenario_id-appconfig-1" should return "[{"mountPath":"/bindings/$scenario_id-binding","name":"$scenario_id-binding"}]"
+        And jsonpath "{.spec.spec.volumes}" on "appconfigs/$scenario_id-appconfig-1" should return "[{"name":"$scenario_id-binding","secret":{"secretName":"$scenario_id-secret"}}]"
+        When 2 minutes have passed
+        And The Custom Resource is present
+            """
+            apiVersion: "stable.example.com/v1"
+            kind: AppConfig
+            metadata:
+                name: $scenario_id-appconfig-2
+                labels:
+                    app-custom: $scenario_id
+            spec:
+                spec:
+                    containers:
+                        - name: bar
+            """
+        Then jsonpath "{.spec.spec.containers[0].volumeMounts}" on "appconfigs/$scenario_id-appconfig-2" should return "[{"mountPath":"/bindings/$scenario_id-binding","name":"$scenario_id-binding"}]"
+        And jsonpath "{.spec.spec.volumes}" on "appconfigs/$scenario_id-appconfig-2" should return "[{"name":"$scenario_id-binding","secret":{"secretName":"$scenario_id-secret"}}]"
+
+    Scenario: Bind applications via label selector as environment variables after the service binding has been created
+        Given The Secret is present
+            """
+            apiVersion: v1
+            kind: Secret
+            metadata:
+                name: $scenario_id-secret
+            stringData:
+                username: AzureDiamond
+                password: hunter2
+            """
+        And The Custom Resource is present
+            """
+            apiVersion: "stable.example.com/v1"
+            kind: AppConfig
+            metadata:
+                name: $scenario_id-appconfig-1
+                labels:
+                    app-custom: $scenario_id
+            spec:
+                spec:
+                    containers:
+                        - name: foo
+            """
+        And The Workload Resource Mapping is present
+            """
+            apiVersion: servicebinding.io/v1alpha3
+            kind: ClusterWorkloadResourceMapping
+            metadata:
+                name: appconfigs.stable.example.com
+            spec:
+                versions:
+                  - version: "*"
+                    containers:
+                      - path: .spec.spec.containers[*]
+                    volumes: .spec.spec.volumes
+            """
+        And Service Binding is applied
+            """
+            apiVersion: binding.operators.coreos.com/v1alpha1
+            kind: ServiceBinding
+            metadata:
+                name: $scenario_id-binding
+            spec:
+                bindAsFiles: false
+                services:
+                  - group: ""
+                    version: v1
+                    kind: Secret
+                    name: $scenario_id-secret
+                application:
+                    labelSelector:
+                      matchLabels:
+                        app-custom: $scenario_id
+                    group: stable.example.com
+                    version: v1
+                    resource: appconfigs
+            """
+        And Service Binding is ready
+        And Secret has been injected in to CR "$scenario_id-appconfig-1" of kind "appconfigs.stable.example.com" at path "{.spec.spec.containers[0].envFrom[0].secretRef.name}"
+        When 2 minutes have passed
+        And The Custom Resource is present
+            """
+            apiVersion: "stable.example.com/v1"
+            kind: AppConfig
+            metadata:
+                name: $scenario_id-appconfig-2
+                labels:
+                    app-custom: $scenario_id
+            spec:
+                spec:
+                    containers:
+                        - name: bar
+            """
+        Then Secret has been injected in to CR "$scenario_id-appconfig-2" of kind "appconfigs.stable.example.com" at path "{.spec.spec.containers[0].envFrom[0].secretRef.name}"
+
+    @spec
+    Scenario: Bind applications via label selector after the spec service binding has been created
+        Given The Secret is present
+            """
+            apiVersion: v1
+            kind: Secret
+            metadata:
+                name: $scenario_id-secret
+            stringData:
+                username: AzureDiamond
+                password: hunter2
+                type:     secret
+            """
+        And The Custom Resource is present
+            """
+            apiVersion: "stable.example.com/v1"
+            kind: AppConfig
+            metadata:
+                name: $scenario_id-appconfig-1
+                labels:
+                    app-custom: $scenario_id
+            spec:
+                spec:
+                    containers:
+                        - name: foo
+            """
+        And The Workload Resource Mapping is present
+            """
+            apiVersion: servicebinding.io/v1alpha3
+            kind: ClusterWorkloadResourceMapping
+            metadata:
+                name: appconfigs.stable.example.com
+            spec:
+                versions:
+                  - version: "*"
+                    containers:
+                      - path: .spec.spec.containers[*]
+            """
+        And Service Binding is applied
+            """
+            apiVersion: servicebinding.io/v1alpha3
+            kind: ServiceBinding
+            metadata:
+                name: $scenario_id-binding
+            spec:
+                service:
+                    apiVersion: v1
+                    kind: Secret
+                    name: $scenario_id-secret
+                workload:
+                    selector:
+                        matchLabels:
+                            app-custom: $scenario_id
+                    apiVersion: stable.example.com/v1
+                    kind: AppConfig
+            """
+        And Service Binding is ready
+        And jsonpath "{.spec.spec.containers[0].volumeMounts}" on "appconfigs/$scenario_id-appconfig-1" should return "[{"mountPath":"/bindings/$scenario_id-binding","name":"$scenario_id-binding"}]"
+        And jsonpath "{.spec.template.spec.volumes}" on "appconfigs/$scenario_id-appconfig-1" should return "[{"name":"$scenario_id-binding","secret":{"secretName":"$scenario_id-secret"}}]"
+        When 2 minutes have passed
+        And The Custom Resource is present
+            """
+            apiVersion: "stable.example.com/v1"
+            kind: AppConfig
+            metadata:
+                name: $scenario_id-appconfig-2
+                labels:
+                    app-custom: $scenario_id
+            spec:
+                spec:
+                    containers:
+                        - name: bar
+            """
+        Then jsonpath "{.spec.spec.containers[0].volumeMounts}" on "appconfigs/$scenario_id-appconfig-2" should return "[{"mountPath":"/bindings/$scenario_id-binding","name":"$scenario_id-binding"}]"
+        And jsonpath "{.spec.template.spec.volumes}" on "appconfigs/$scenario_id-appconfig-2" should return "[{"name":"$scenario_id-binding","secret":{"secretName":"$scenario_id-secret"}}]"
diff --git a/test/acceptance/features/steps/generic_testapp.py b/test/acceptance/features/steps/generic_testapp.py
index 04301dce..0194bc76 100644
--- a/test/acceptance/features/steps/generic_testapp.py
+++ b/test/acceptance/features/steps/generic_testapp.py
@@ -117,13 +117,15 @@ def check_file_unavailable(context, file_path):
 
 @step(u'Test applications "{first_app_name}" and "{second_app_name}" is running')
 def are_two_apps_running(context, first_app_name, second_app_name, bindingRoot=None):
-    application1 = GenericTestApp(first_app_name, context.namespace.name)
+    first = substitute_scenario_id(context, first_app_name)
+    application1 = GenericTestApp(first, context.namespace.name)
     if not application1.is_running():
         print("application1 is not running, trying to import it")
         application1.install(bindingRoot=bindingRoot)
     context.application1 = application1
 
-    application2 = GenericTestApp(second_app_name, context.namespace.name)
+    second = substitute_scenario_id(context, second_app_name)
+    application2 = GenericTestApp(second, context.namespace.name)
     if not application2.is_running():
         print("application2 is not running, trying to import it")
         application2.install(bindingRoot=bindingRoot)
diff --git a/test/acceptance/features/steps/steps.py b/test/acceptance/features/steps/steps.py
index d2f3e9ae..63f11451 100644
--- a/test/acceptance/features/steps/steps.py
+++ b/test/acceptance/features/steps/steps.py
@@ -10,6 +10,7 @@ import parse
 import binascii
 import yaml
 import json
+import time
 
 from behave import given, register_type, then, when, step
 from knative_serving import KnativeServing
@@ -316,7 +317,7 @@ def delete_yaml(context):
     assert result is not None, f"Unable to delete CR '{metadata_name}': {output}"
 
 
-@then(u'Secret has been injected in to CR "{cr_name}" of kind "{crd_name}" at path "{json_path}"')
+@step(u'Secret has been injected in to CR "{cr_name}" of kind "{crd_name}" at path "{json_path}"')
 def verify_injected_secretRef(context, cr_name, crd_name, json_path):
     sb = list(context.bindings.values())[0]
     name = substitute_scenario_id(context, cr_name)
@@ -463,3 +464,8 @@ def check_service_account_bound(context, cluster_role_name, cluster_rolebinding_
             continue
 
     assert subject_found, f"Could not find rolebinding for the role '{cluster_role_name}'"
+
+
+@step(u'{time_min} minutes have passed')
+def step_impl(context, time_min):
+    time.sleep(60 * int(time_min))
diff --git a/test/performance/.gitignore b/test/performance/.gitignore
new file mode 100644
index 00000000..1eec8a03
--- /dev/null
+++ b/test/performance/.gitignore
@@ -0,0 +1,5 @@
+metrics*
+out
+resource-list*
+results*
+toolchain-e2e.git
diff --git a/test/performance/collect-kpi.sh b/test/performance/collect-kpi.sh
new file mode 100755
index 00000000..4ca19e7f
--- /dev/null
+++ b/test/performance/collect-kpi.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+
+WS=${WS:-$(readlink -m $(dirname $0))}
+
+indent() {
+    sed 's/^/  /'
+}
+
+DT=$(date "+%F_%T")
+export RESULTS=${RESULTS:-results-$DT}
+mkdir -p $RESULTS
+
+export METRICS=${METRICS:-$(find . -type d -name 'metrics-*')}
+
+NS_PREFIX=${NS_PREFIX:-entanglement}
+
+SBO_METRICS=$(find $METRICS -type f -name 'pod-info.service-binding-operator-*.csv')
+
+SCENARIOS="nosb-inv nosb-val sb-inc sb-inv sb-val"
+
+USER_NS_PREFIXES=""
+for scenario in $SCENARIOS; do
+    USER_NS_PREFIXES="$USER_NS_PREFIXES $NS_PREFIX-$scenario"
+done
+
+kpi_yaml=$RESULTS/kpi.yaml
+echo "kpi:" >$kpi_yaml
+
+output=$RESULTS/sbo-metrics.kpi.yaml
+echo "- name: usage" >$output
+echo "  metrics:" >>$output
+
+python $WS/kpi.py -c $SBO_METRICS -x 0 -y 1 | indent >>$output
+python $WS/kpi.py -c $SBO_METRICS -x 0 -y 2 | indent >>$output
+
+cat $output >>$kpi_yaml
+
+if [ -z $PROCESS_ONLY ]; then
+    $WS/collect-results.sh "${USER_NS_PREFIXES}"
+fi
+
+for scenario in $SCENARIOS; do
+    ns_prefix=$NS_PREFIX-$scenario
+    output=$RESULTS/$scenario.kpi.yaml
+    echo "- name: $scenario" >$output
+    echo "  metrics:" >>$output
+    for i in 5 6 7; do
+        python $WS/kpi.py -c "$(readlink -m $RESULTS/$ns_prefix.sbr-timestamps.csv)" -x 1 -y $i -d "%Y-%m-%d %H:%M:%S" | indent >>$output
+    done
+    cat $output >>$kpi_yaml
+done
diff --git a/test/performance/collect-metrics.sh b/test/performance/collect-metrics.sh
new file mode 100755
index 00000000..06caa29e
--- /dev/null
+++ b/test/performance/collect-metrics.sh
@@ -0,0 +1,77 @@
+#!/bin/bash
+
+PERIOD="${1:-30}"
+RESULTS=${2:-metrics-$(date "+%F_%T")}
+
+mkdir -p "$RESULTS"
+
+strip_unit() {
+    echo -n $1 | sed -e 's,\([0-9]\+\)m,\1,g' | sed -e 's,\([0-9]\+\)Mi,\1,g' | sed -e 's,\([0-9]\+\)%,\1,g'
+}
+
+# Nodes
+oc get nodes >$RESULTS/nodes.yaml
+oc describe nodes >$RESULTS/nodes.info
+
+node_info_file() {
+    readlink -m "$RESULTS/node-info.$1.csv"
+}
+
+node_line() {
+    node=$1
+    node_json="$(oc get node $node -o json)"
+    echo -n "$(echo "$node_json" | jq -rc '.status.conditions[] | select(.type=="MemoryPressure").status');"
+    echo -n "$(echo "$node_json" | jq -rc '.status.conditions[] | select(.type=="DiskPressure").status');"
+    echo -n "$(echo "$node_json" | jq -rc '.status.conditions[] | select(.type=="PIDPressure").status');"
+    echo -n "$(echo "$node_json" | jq -rc '.status.conditions[] | select(.type=="Ready").status');"
+    node_info=($(oc adm top node $node --no-headers))
+    echo -n "$(strip_unit ${node_info[1]});"
+    echo -n "$(strip_unit ${node_info[2]});"
+    echo -n "$(strip_unit ${node_info[3]});"
+    echo "$(strip_unit ${node_info[4]})"
+}
+
+NODES=($(oc get nodes -o json | jq -rc '.items[].metadata.name' | sort))
+for node in "${NODES[@]}"; do
+    echo "Time;MemoryPressure;DiskPressure;PIDPressure;Ready;CPU_millicores;CPU_percent;Memory_MiB;Memory_percent" >$(node_info_file $node)
+done
+
+# Operator pods
+pod_info_file() {
+    readlink -m "$RESULTS/pod-info.$1.csv"
+}
+
+pod_line() {
+    pod=$1
+    ns=$2
+    pod_info=($(oc adm top pod $pod -n $ns --no-headers))
+    echo -n "$(strip_unit ${pod_info[1]});"
+    echo "$(strip_unit ${pod_info[2]})"
+}
+
+for namespace in openshift-operators openshift-monitoring openshift-apiserver openshift-kube-apiserver openshift-sdn openshift-operator-lifecycle-manager; do
+    PODS=($(oc get pods -n $namespace -o json | jq -rc '.items[].metadata.name' | grep -E 'operator|prometheus|apiserver|sdn|ovs|olm|packageserver' | sort))
+    for pod in "${PODS[@]}"; do
+        echo "Time;CPU_millicores;Memory_MiB" >$(pod_info_file $pod)
+    done
+done
+
+echo "Collecting metrics"
+# Periodical collection
+while true; do
+    echo -n "."
+    for namespace in openshift-operators openshift-monitoring openshift-apiserver openshift-kube-apiserver openshift-sdn openshift-operator-lifecycle-manager; do
+        PODS=($(oc get pods -n $namespace -o json | jq -rc '.items[].metadata.name' | grep -E 'operator|prometheus|apiserver|sdn|ovs|olm|packageserver' | sort))
+        for pod in "${PODS[@]}"; do
+            pod_file=$(pod_info_file $pod)
+            echo -n "$(date -u '+%F %T.%N');" >>$pod_file
+            pod_line $pod $namespace >>$pod_file
+        done
+    done
+    for node in ${NODES[@]}; do
+        node_file=$(node_info_file $node)
+        echo -n "$(date -u '+%F %T.%N');" >>$node_file
+        node_line $node >>$node_file
+    done
+    sleep ${PERIOD}s
+done
diff --git a/test/performance/collect-results.sh b/test/performance/collect-results.sh
new file mode 100755
index 00000000..d6b7adeb
--- /dev/null
+++ b/test/performance/collect-results.sh
@@ -0,0 +1,149 @@
+#!/bin/bash -e
+
+DT=$(date "+%F_%T")
+RESULTS=${RESULTS:-results-$DT}
+mkdir -p $RESULTS
+
+USER_NS_PREFIXES=${1:-entanglement}
+PROCESS_ONLY=${PROCESS_ONLY:-}
+
+ddiff_sec() {
+    secdiff=$(echo "$(date -d "$2" +%s) - $(date -d "$1" +%s)" | bc | sed -e 's,^\.\([0-9]\+\),0.\1,')
+    nanosecdiff=$(echo "$(date -d "$2" +%N) - $(date -d "$1" +%N)" | bc | sed -e 's,^\.\([0-9]\+\),0.\1,')
+    echo "scale=9; $secdiff + ($nanosecdiff / 1000000000)" | bc | sed -e 's,^\.\([0-9]\+\),0.\1,'
+}
+
+# Resource counts
+resource_counts() {
+    echo -n "$1;"
+    # All resource counts from user namespaces
+    echo -n "$(oc get $1 --all-namespaces -o custom-columns=NAMESPACE:.metadata.namespace --ignore-not-found=true | grep $USER_NS_PREFIX | wc -l)"
+    echo -n ";"
+    # All resource counts from all namespaces
+    echo "$(oc get $1 --all-namespaces -o name --ignore-not-found=true | wc -l)"
+}
+
+# Dig various timestamps out
+timestamps() {
+    NS_PREFIX=$1
+    SBR_JSON=$2
+    DEPLOYMENTS_JSON=$3
+    SBO_LOG=$4
+    RESULTS=$5
+
+    BINDING_TIMESTAMPS_CSV=${NS_PREFIX}.binding-timestamps.csv
+    SBR_TIMESTAMPS_CSV=${NS_PREFIX}.sbr-timestamps.csv
+    TMP_CSV=${NS_PREFIX}.tmp.csv
+
+    LOG_SEG_DIR=$RESULTS/sbo-log-segments
+    mkdir -p $LOG_SEG_DIR
+
+    rm -f $RESULTS/$TMP_CSV
+    jq -rc 'select(.metadata.namespace | startswith("'$NS_PREFIX'")) | ((.metadata.namespace) + ";" + (.metadata.name) + ";" + (.metadata.creationTimestamp) + ";" + (if (.status == null) then ("") else (.status.conditions[] | select(.type=="Ready").lastTransitionTime) end ))' $SBR_JSON >$RESULTS/$TMP_CSV
+    echo "ServiceBinding;Created;ReconciledTimestamp;Ready;AllDoneTimestamp;TimeToReconcile;ReconciledToDone;TimeToDone" >$RESULTS/$SBR_TIMESTAMPS_CSV
+    for i in $(cat $RESULTS/$TMP_CSV); do
+        ns=$(echo -n $i | cut -d ";" -f1)
+        name=$(echo -n $i | cut -d ";" -f2)
+
+        # ServiceBinding
+        echo -n $ns/$name
+        echo -n ";"
+
+        # Created
+        created=$(date -d $(echo -n $i | cut -d ";" -f3) "+%F %T")
+        echo -n "$created"
+        echo -n ";"
+
+        # ReconciledTimestamp
+        log=$LOG_SEG_DIR/$ns.log
+        cat $SBO_LOG | grep $ns >$log
+        reconciled=$(date -d @$(cat $log | jq -rc 'if .serviceBinding != null then select(.serviceBinding | contains("'$ns/$name'")) | select(.msg | contains("Reconciling")).ts else empty end' | head -n1) "+%F %T.%N" | tr -d "\n")
+        echo -n "$reconciled"
+        echo -n ";"
+
+        # Ready
+        ready=$(date -d $(echo -n $i | cut -d ";" -f4) "+%F %T")
+        echo -n "$ready"
+        echo -n ";"
+
+        # AllDoneTimestamp
+        done_ts=$(cat "$log" | jq -rc 'select(.msg | contains("Done")) | select(.serviceBinding | contains("'$ns/$name'")) | select(.retry==false).ts')
+        if [ -n "$done_ts" ]; then
+            all_done=$(date -d "@$done_ts" "+%F %T.%N")
+        else
+            all_done=""
+        fi
+        echo -n "$all_done"
+        echo -n ";"
+
+        # TimeToReconciled
+        echo -n "$(ddiff_sec "$created" "$reconciled")"
+        echo -n ";"
+
+        # ReconciledToDone
+        echo -n "$(ddiff_sec "$reconciled" "$all_done")"
+        echo -n ";"
+
+        # TimeToDone
+        echo -n "$(ddiff_sec "$created" "$all_done")"
+
+        echo ""
+
+    done \
+        >>$RESULTS/$SBR_TIMESTAMPS_CSV
+
+    rm -f $RESULTS/$TMP_CSV
+    jq -rc 'select(.metadata.namespace | startswith("'$NS_PREFIX'")) | ((.metadata.namespace) + ";" + (.metadata.name) + ";" + (.metadata.creationTimestamp) + ";" + (.status.conditions[] | select(.type=="Available") | select(.status=="True").lastTransitionTime))' $DEPLOYMENTS_JSON >$RESULTS/$TMP_CSV
+    echo "Namespace;Deployment;Deployment_Created;Deployment_Available;SB_Name;SB_created;SB_ReconciledTimestamp;SB_Ready;SB_AllDoneTimestamp" >$RESULTS/$BINDING_TIMESTAMPS_CSV
+    for i in $(cat $RESULTS/$TMP_CSV); do
+        NS=$(echo -n $i | cut -d ";" -f1)
+        echo -n $NS
+        echo -n ";"
+        echo -n $(echo -n $i | cut -d ";" -f2)
+        echo -n ";"
+        echo -n $(date -d $(echo -n $i | cut -d ";" -f3) "+%F %T")
+        echo -n ";"
+        echo -n $(date -d $(echo -n $i | cut -d ";" -f4) "+%F %T")
+        echo -n ";"
+        cat $RESULTS/$SBR_TIMESTAMPS_CSV | grep $NS
+    done >>$RESULTS/$BINDING_TIMESTAMPS_CSV
+    rm -f $RESULTS/$TMP_CSV
+}
+
+# Collect timestamps
+{
+
+    # ServiceBiding operator log
+    if [ -z "$PROCESS_ONLY" ]; then
+        oc logs $(oc get $(oc get pods -n openshift-operators -o name | grep service-binding-operator) -n openshift-operators -o jsonpath='{.metadata.name}') -n openshift-operators >$RESULTS/service-binding-operator.log
+    fi
+
+    for ns_prefix in $USER_NS_PREFIXES; do
+        if [ -z "$PROCESS_ONLY" ]; then
+            # ServiceBinding resources in user namespaces
+            oc get servicebindings --all-namespaces -o json | jq -r '.items[] | select(.metadata.namespace | startswith("'$ns_prefix'"))' >$RESULTS/$ns_prefix.service-bindings.json
+
+            # Deployment resources in user namespaces
+            oc get deployment --all-namespaces -o json | jq -r '.items[] | select(.metadata.namespace | startswith("'$ns_prefix'" )) | select(.metadata.name | contains("sbo-perf-app"))' >$RESULTS/$ns_prefix.deployments.json
+        fi
+
+        # Service Binding Timestamps
+        timestamps $ns_prefix $RESULTS/$ns_prefix.service-bindings.json $RESULTS/$ns_prefix.deployments.json $RESULTS/service-binding-operator.log $RESULTS
+    done
+} &
+
+# Collect resource counts
+{
+    if [ -z "$PROCESS_ONLY" ]; then
+        oc api-resources --verbs=list --namespaced -o name | sort >resource-list.namespaced
+        oc api-resources --verbs=list --namespaced=false -o name | sort >resource-list.cluster
+
+        RESOURCE_COUNTS_OUT=$RESULTS/resource-count.csv
+        echo "Resource;UserNamespaces;AllNamespaces" >$RESOURCE_COUNTS_OUT
+        for i in $(cat resource-list.namespaced resource-list.cluster | sort); do
+            echo resource_counts $i >>$RESOURCE_COUNTS_OUT
+        done
+    fi
+} &
+
+wait
diff --git a/test/performance/deploy-sbr.sh b/test/performance/deploy-sbr.sh
new file mode 100755
index 00000000..d1ebf6d5
--- /dev/null
+++ b/test/performance/deploy-sbr.sh
@@ -0,0 +1,34 @@
+#!/bin/bash -x
+
+SBR_YAML=${1:-sbo-test.sbr.yaml}
+
+APP_NAME=$(yq e '.spec.application.name' $SBR_YAML)
+SBR_NAME=$(yq e '.metadata.name' $SBR_YAML)
+USER_NS_PREFIX=${2:-entanglement}
+oc get deploy --all-namespaces -o json | jq -rc '.items[] | select(.metadata.name | contains("'$APP_NAME'")).metadata.namespace' | grep $USER_NS_PREFIX >workload.namespace.list
+
+no_ns=$(cat workload.namespace.list | wc -l)
+
+split -l $((no_ns / 5)) workload.namespace.list sbr-segment
+
+for i in sbr-segment*; do
+    for j in $(cat $i); do
+        oc apply -f $SBR_YAML -n $j --server-side=true
+        sleep 0.02s
+    done &
+done
+
+wait
+
+#Wait for the all the service bindings to get status
+retries=360
+until [[ $retries == 0 ]]; do
+    sb_with_status_set=$(oc get sbr --all-namespaces -o json | jq -rc '.items[]| select(.metadata.namespace | contains("'$USER_NS_PREFIX'")) | select(.status != null).metadata.name' | wc -l)
+    [ $no_ns != $sb_with_status_set ] || break
+    echo "Waiting for all the Service Binding resources to be processed by operator... currently only $sb_with_status_set/$no_ns are"
+    sleep 10
+    retries=$(($retries - 1))
+done
+
+rm -rf sbr-segment*
+rm -rf workload.namespace.list
diff --git a/test/performance/install-operators.sh b/test/performance/install-operators.sh
new file mode 100755
index 00000000..faa076c9
--- /dev/null
+++ b/test/performance/install-operators.sh
@@ -0,0 +1,51 @@
+#!/bin/bash -e
+export RHOAS_INDEX_IMAGE=${RHOAS_INDEX_IMAGE:-quay.io/rhoas/service-operator-registry:autolatest}
+export RHOAS_CHANNEL=${RHOAS_CHANNEL:-beta}
+export RHOAS_PACKAGE=${RHOAS_PACKAGE:-rhoas-operator}
+export RHOAS_CATSRC_NAMESPACE=${RHOAS_CATSRC_NAMESPACE:-openshift-marketplace}
+export RHOAS_CATSRC_NAME=${RHOAS_CATSRC_NAME:-rhoas-operators}
+export RHOAS_NAMESPACE=${RHOAS_NAMESPACE:-openshift-operators}
+
+echo "Installing RHOAS Operator"
+oc apply -f - <<EOD
+---
+apiVersion: operators.coreos.com/v1alpha1
+kind: CatalogSource
+metadata:
+  name: $RHOAS_CATSRC_NAME
+  namespace: $RHOAS_CATSRC_NAMESPACE
+spec:
+  displayName: RHOAS Operators
+  icon:
+    base64data: ""
+    mediatype: ""
+  image: $RHOAS_INDEX_IMAGE
+  priority: -400
+  publisher: RHOAS
+  sourceType: grpc
+  updateStrategy:
+    registryPoll:
+      interval: 260s
+---
+apiVersion: operators.coreos.com/v1alpha1
+kind: Subscription
+metadata:
+  name: $RHOAS_PACKAGE
+  namespace: $RHOAS_NAMESPACE
+spec:
+  channel: $RHOAS_CHANNEL
+  installPlanApproval: Automatic
+  name: $RHOAS_PACKAGE
+  source: $RHOAS_CATSRC_NAME
+  sourceNamespace: $RHOAS_CATSRC_NAMESPACE
+EOD
+
+#Wait for the operator to get up and running
+retries=50
+until [[ $retries == 0 ]]; do
+    kubectl get deployment/rhoas-operator -n $RHOAS_NAMESPACE >/dev/null 2>&1 && break
+    echo "Waiting for rhoas-operator to be created in $RHOAS_NAMESPACE namespace"
+    sleep 5
+    retries=$(($retries - 1))
+done
+kubectl rollout status -w deployment/rhoas-operator -n $RHOAS_NAMESPACE
diff --git a/test/performance/kpi.py b/test/performance/kpi.py
new file mode 100755
index 00000000..1e231a8e
--- /dev/null
+++ b/test/performance/kpi.py
@@ -0,0 +1,80 @@
+
+import argparse
+import csv
+import yaml
+import sys
+import statistics
+from datetime import datetime
+
+
+def normalize_value(value):
+    if float(value) < 0:
+        return "n/a"
+    else:
+        return value
+
+
+ap = argparse.ArgumentParser()
+ap.add_argument('-c', '--csv-file', type=str, help='')
+ap.add_argument('-x', '--x-column', type=int, help='')
+ap.add_argument('-y', '--y-column', type=int, help='')
+ap.add_argument('-d', '--date-format', type=str,
+                default='%Y-%m-%d %H:%M:%S.%f', help='')
+
+args = ap.parse_args()
+
+csv_file = args.csv_file
+x_column = args.x_column
+y_column = args.y_column
+dateformat = args.date_format
+
+with open(csv_file, "r") as file:
+    csvreader = csv.reader(file, delimiter=";")
+
+    headers = []
+    headers = next(csvreader)
+
+    x_header = headers[x_column]
+    y_header = headers[y_column]
+
+    rows = []
+    for row in csvreader:
+        if row[x_column] == "" or row[y_column] == "" or row[x_column] is None or row[y_column] is None:
+            sys.stderr.write(
+                f"WARNING: Incomplete row data {row}, skipping...\n")
+            continue
+        rowmap = {}
+        if len(row[x_column]) > 26:
+            x = row[x_column][0:26]
+        else:
+            x = row[x_column]
+        rowmap[x_header] = datetime.strptime(x, dateformat).timestamp()
+        rowmap[y_header] = row[y_column]
+        rows.append(rowmap)
+
+count = len(rows)
+
+min = float("inf")
+max = float("-inf")
+values = []
+
+for rowmap in rows:
+    value = float(rowmap[y_header])
+    values.append(value)
+
+    if value > max:
+        max = value
+
+    if value < min:
+        min = value
+
+metrics = {}
+metrics["name"] = y_header
+metrics["first"] = normalize_value(float(rows[0][y_header]))
+metrics["minimum"] = normalize_value(min)
+metrics["average"] = normalize_value(statistics.mean(values))
+metrics["median"] = normalize_value(statistics.median(values))
+metrics["maximum"] = normalize_value(max)
+metrics["last"] = normalize_value(float(rows[count - 1][y_header]))
+
+print(yaml.dump([metrics]))
diff --git a/test/performance/kpidiff.py b/test/performance/kpidiff.py
new file mode 100755
index 00000000..9b81efb1
--- /dev/null
+++ b/test/performance/kpidiff.py
@@ -0,0 +1,56 @@
+
+import argparse
+import sys
+import yaml
+
+ap = argparse.ArgumentParser()
+ap.add_argument('-a', type=str, help='Base for comparison')
+ap.add_argument('-b', type=str, help='Compared data')
+ap.add_argument('-r', '--raw', action="store_true", default=False,
+                help='Store differences as raw values (instead of percentages)')
+
+args = ap.parse_args()
+
+a_yaml_file = args.a
+b_yaml_file = args.b
+raw = bool(args.raw)
+
+with open(a_yaml_file, "r") as a_yaml:
+    with (open(b_yaml_file, "r") as b_yaml):
+        a_kpi = yaml.safe_load(a_yaml)["kpi"]
+        b_kpi = yaml.safe_load(b_yaml)["kpi"]
+
+        kpi_i = 0
+        for kpi in b_kpi:
+            name = kpi["name"]
+            metric_i = 0
+            for metric in kpi["metrics"]:
+                for stat in metric.keys():
+                    if stat == "name":
+                        continue
+                    if a_kpi[kpi_i]["metrics"][metric_i][stat] == "n/a" or b_kpi[kpi_i]["metrics"][metric_i][stat] == "n/a":
+                        value = "n/a"
+                    else:
+                        a = float(a_kpi[kpi_i]["metrics"][metric_i][stat])
+                        b = float(b_kpi[kpi_i]["metrics"][metric_i][stat])
+                        if a == 0:
+                            if a != b:
+                                c = float('inf')
+                            else:
+                                c = 0.0
+                        else:
+                            c = b / a - 1.0
+                        if raw:
+                            value = c
+                        else:
+                            if c > 0:
+                                sign = "+"
+                            else:
+                                sign = ""
+                            value = f"{sign}{round(c*100, 2)}%"
+                    b_kpi[kpi_i]["metrics"][metric_i][stat] = value
+                metric_i += 1
+            kpi_i += 1
+output = {"kpi-diff": b_kpi}
+
+print(yaml.dump(output))
diff --git a/test/performance/run.sh b/test/performance/run.sh
new file mode 100755
index 00000000..423922ec
--- /dev/null
+++ b/test/performance/run.sh
@@ -0,0 +1,38 @@
+#!/bin/bash -x
+
+WS=${WS:-$(readlink -m $(dirname $0))}
+OUTPUT_DIR=${OUTPUT_DIR:-$WS/out}
+WAIT_BEFORE=${WAIT_BEFORE:-60s}
+WAIT_AFTER=${WAIT_AFTER:-60s}
+
+mkdir -p $OUTPUT_DIR
+
+$WS/collect-metrics.sh 30 $OUTPUT_DIR/metrics &
+METRICS_PID=$!
+
+trap "kill -9 $METRICS_PID" EXIT
+
+sleep $WAIT_BEFORE
+
+pushd $WS/toolchain-e2e.git
+
+USER_NS_PREFIX=${1:-entanglement}
+USERS_PER_SCENARIO=${2:-25}
+DEFAULT_WORKLOADS=${DEFAULT_WORKLOADS:-$USERS_PER_SCENARIO}
+
+SBO_NAMESPACE=${SBO_NAMESPACE:-openshift-operators}
+SBO_DEPLOYMENT=${SBO_DEPLOYMENT:-service-binding-operator}
+
+yes | go run setup/main.go --template=$WS/user-workloads/valid/sbo-test.with-sbr.user-workloads.yaml --users $USERS_PER_SCENARIO --default $DEFAULT_WORKLOADS --custom $USERS_PER_SCENARIO --operators-limit 0 --workloads $SBO_NAMESPACE:$SBO_DEPLOYMENT --username $USER_NS_PREFIX-sb-val > $OUTPUT_DIR/$USER_NS_PREFIX-sb-val.log
+
+yes | go run setup/main.go --template=$WS/user-workloads/valid/sbo-test.without-sbr.user-workloads.yaml --users $USERS_PER_SCENARIO --default $DEFAULT_WORKLOADS --custom $USERS_PER_SCENARIO --operators-limit 0 --workloads $SBO_NAMESPACE:$SBO_DEPLOYMENT --username $USER_NS_PREFIX-nosb-val > $OUTPUT_DIR/$USER_NS_PREFIX-nosb-val.log
+$WS/deploy-sbr.sh $WS/user-workloads/valid/sbo-test.sbr.yaml $USER_NS_PREFIX-nosb-val > $OUTPUT_DIR/$USER_NS_PREFIX-nosb-val.deploy-sbr.log
+
+yes | go run setup/main.go --template=$WS/user-workloads/invalid/sbo-test.with-sbr.user-workloads.yaml --users $USERS_PER_SCENARIO --default $DEFAULT_WORKLOADS --custom $USERS_PER_SCENARIO --operators-limit 0 --workloads $SBO_NAMESPACE:$SBO_DEPLOYMENT --username $USER_NS_PREFIX-sb-inv > $OUTPUT_DIR/$USER_NS_PREFIX-sb-inv.log
+
+yes | go run setup/main.go --template=$WS/user-workloads/invalid/sbo-test.without-sbr.user-workloads.yaml --users $USERS_PER_SCENARIO --default $DEFAULT_WORKLOADS --custom $USERS_PER_SCENARIO --operators-limit 0 --workloads $SBO_NAMESPACE:$SBO_DEPLOYMENT --username $USER_NS_PREFIX-nosb-inv > $OUTPUT_DIR/$USER_NS_PREFIX-nosb-inv.log
+  $WS/deploy-sbr.sh $WS/user-workloads/invalid/sbo-test.sbr.yaml $USER_NS_PREFIX-nosb-inv > $OUTPUT_DIR/$USER_NS_PREFIX-nosb-inv.deploy-sbr.log
+
+yes | go run setup/main.go --template=$WS/user-workloads/incomplete/sbo-test.with-sbr.user-workloads.yaml --users $USERS_PER_SCENARIO --default $DEFAULT_WORKLOADS --custom $USERS_PER_SCENARIO --operators-limit 0 --workloads $SBO_NAMESPACE:$SBO_DEPLOYMENT --username $USER_NS_PREFIX-sb-inc > $OUTPUT_DIR/$USER_NS_PREFIX-sb-inc.log
+
+sleep $WAIT_AFTER
diff --git a/test/performance/setup.sh b/test/performance/setup.sh
new file mode 100755
index 00000000..8b78be0d
--- /dev/null
+++ b/test/performance/setup.sh
@@ -0,0 +1,47 @@
+#!/bin/bash -e
+
+# Environment
+if [ -z "$QUAY_NAMESPACE" ]; then
+    echo "QUAY_NAMESPACE environemnt variable needs to be set to a non-empty value"
+    exit 1
+fi
+
+WS=${WS:-$(readlink -m $(dirname $0))}
+
+# Ensure oc is loged in
+OPENSHIFT_API_TOKEN=$(oc whoami -t) || (echo "a token is required to capture metrics, use 'oc login' to log into the cluster" && exit 1)
+
+# Install Developer Sandbox
+WSTC=$WS/toolchain-e2e.git
+TOOLCHAIN_E2E_REPO=${TOOLCHAIN_E2E_REPO:-https://github.com/codeready-toolchain/toolchain-e2e}
+TOOLCHAIN_E2E_BRANCH=${TOOLCHAIN_E2E_BRANCH:-master}
+if [ ! -d $WSTC ]; then
+    git clone $TOOLCHAIN_E2E_REPO $WSTC
+fi
+cd $WSTC
+git reset --hard
+git checkout $TOOLCHAIN_E2E_BRANCH
+git pull
+make dev-deploy-e2e
+
+wait_for_deployment() {
+    deployment=$1
+    ns=$2
+    #Wait for the operator to get up and running
+    retries=50
+    until [[ $retries == 0 ]]; do
+        kubectl get deployment/$deployment -n $ns >/dev/null 2>&1 && break
+        echo "Waiting for $deployment to be created in $ns namespace"
+        sleep 5
+        retries=$(($retries - 1))
+    done
+    kubectl rollout status -w deployment/$deployment -n $ns
+}
+
+wait_for_deployment host-operator-controller-manager toolchain-host-operator
+wait_for_deployment registration-service toolchain-host-operator
+wait_for_deployment member-operator-controller-manager toolchain-member-operator
+wait_for_deployment member-operator-webhook toolchain-member-operator
+
+# Install operators
+$WS/install-operators.sh
diff --git a/test/performance/user-workloads/incomplete/sbo-test.with-sbr.user-workloads.yaml b/test/performance/user-workloads/incomplete/sbo-test.with-sbr.user-workloads.yaml
new file mode 100644
index 00000000..ea301731
--- /dev/null
+++ b/test/performance/user-workloads/incomplete/sbo-test.with-sbr.user-workloads.yaml
@@ -0,0 +1,78 @@
+kind: Template
+apiVersion: v1
+metadata:
+  name: sbo-perf-with-sbr-incomplete
+objects:
+  - apiVersion: apps/v1
+    kind: Deployment
+    metadata:
+      name: sbo-perf-svc-incomplete
+      labels:
+        app: sbo-perf-svc-incomplete
+    spec:
+      replicas: 1
+      strategy:
+        type: RollingUpdate
+      selector:
+        matchLabels:
+          app: sbo-perf-svc-incomplete
+      template:
+        metadata:
+          labels:
+            app: sbo-perf-svc-incomplete
+        spec:
+          containers:
+          - name: busybox
+            image: busybox
+            imagePullPolicy: IfNotPresent
+            command: ['sh', '-c', 'echo Container 1 is Running ; sleep 3600']
+            ports:
+            - containerPort: 8080
+  - apiVersion: v1
+    kind: Service
+    metadata:
+      labels:
+        app: sbo-perf-svc-incomplete
+      name: sbo-perf-svc-incomplete
+      annotations:
+        service.binding/portMappings: "path={.spec.ports},elementType=sliceOfMaps,sourceKey=protocol,sourceValue=targetPort"
+    spec:
+      ports:
+      - port: 8080
+        protocol: TCP
+        targetPort: 8080
+      selector:
+        app: sbo-perf-svc-incomplete
+  - apiVersion: route.openshift.io/v1
+    kind: Route
+    metadata:
+      labels:
+        app: sbo-perf-svc-incomplete
+      name: sbo-perf-svc-incomplete
+      annotations:
+        service.binding/host: path={.spec.host}
+    spec:
+      port:
+        targetPort: 8080
+      to:
+        kind: "Service"
+        name: sbo-perf-svc-incomplete
+  - apiVersion: binding.operators.coreos.com/v1alpha1
+    kind: ServiceBinding
+    metadata:
+      name: service-binding-incomplete
+    spec:
+      services:
+      - group: route.openshift.io
+        version: v1
+        kind: Route
+        name: sbo-perf-svc-incomplete
+      - group: ""
+        version: v1
+        kind: Service
+        name: sbo-perf-svc-incomplete
+      application:
+        name: sbo-perf-app-incomplete
+        group: apps
+        version: v1
+        resource: deployments
diff --git a/test/performance/user-workloads/invalid/sbo-test.sbr.yaml b/test/performance/user-workloads/invalid/sbo-test.sbr.yaml
new file mode 100644
index 00000000..1c9368cf
--- /dev/null
+++ b/test/performance/user-workloads/invalid/sbo-test.sbr.yaml
@@ -0,0 +1,20 @@
+---
+apiVersion: binding.operators.coreos.com/v1alpha1
+kind: ServiceBinding
+metadata:
+  name: service-binding-invalid
+spec:
+  services:
+  - group: route.openshift.io
+    version: v1
+    kind: Route
+    name: sbo-perf-svc-invalid
+  - group: ""
+    version: v1
+    kind: Service
+    name: sbo-perf-svc-invalid
+  application:
+    name: sbo-perf-app-invalid
+    group: apps
+    version: v1
+    resource: deployments
diff --git a/test/performance/user-workloads/invalid/sbo-test.with-sbr.user-workloads.yaml b/test/performance/user-workloads/invalid/sbo-test.with-sbr.user-workloads.yaml
new file mode 100644
index 00000000..63c4166c
--- /dev/null
+++ b/test/performance/user-workloads/invalid/sbo-test.with-sbr.user-workloads.yaml
@@ -0,0 +1,129 @@
+kind: Template
+apiVersion: v1
+metadata:
+  name: sbo-perf-with-sbr-invalid
+objects:
+  - apiVersion: apps/v1
+    kind: Deployment
+    metadata:
+      name: sbo-perf-app-invalid
+      labels:
+        app: sbo-perf-app-invalid
+    spec:
+      replicas: 1
+      strategy:
+        type: RollingUpdate
+      selector:
+        matchLabels:
+          app: sbo-perf-app-invalid
+      template:
+        metadata:
+          labels:
+            app: sbo-perf-app-invalid
+        spec:
+          containers:
+          - name: sbo-generic-test-app
+            image: quay.io/redhat-developer/sbo-generic-test-app:20200923
+            imagePullPolicy: IfNotPresent
+            ports:
+            - containerPort: 8080
+  - apiVersion: v1
+    kind: Service
+    metadata:
+      labels:
+        app: sbo-perf-app-invalid
+      name: sbo-perf-app-invalid
+    spec:
+      ports:
+      - port: 8080
+        protocol: TCP
+        targetPort: 8080
+      selector:
+        app: sbo-perf-app-invalid
+  - apiVersion: route.openshift.io/v1
+    kind: Route
+    metadata:
+      labels:
+        app: sbo-perf-app-invalid
+      name: sbo-perf-app-invalid
+      annotations:
+        service.binding/host: path={.spec.host}
+    spec:
+      port:
+        targetPort: 8080
+      to:
+        kind: "Service"
+        name: sbo-perf-app-invalid
+  - apiVersion: apps/v1
+    kind: Deployment
+    metadata:
+      name: sbo-perf-svc-invalid
+      labels:
+        app: sbo-perf-svc-invalid
+    spec:
+      replicas: 1
+      strategy: 
+        type: RollingUpdate
+      selector:
+        matchLabels:
+          app: sbo-perf-svc-invalid
+      template:
+        metadata:
+          labels:
+            app: sbo-perf-svc-invalid
+        spec:
+          containers:
+          - name: busybox
+            image: busybox
+            imagePullPolicy: IfNotPresent
+            command: ['sh', '-c', 'echo Container 1 is Running ; sleep 3600']
+            ports:
+            - containerPort: 8080
+  - apiVersion: v1
+    kind: Service
+    metadata:
+      labels:
+        app: sbo-perf-svc-invalid
+      name: sbo-perf-svc-invalid
+      annotations:
+        service.binding/portMappings: "path={.spec.ports},elementType=sliceOfMaps,sourceKey=protocol,sourceValue=asdf"
+    spec:
+      ports:
+      - port: 8080
+        protocol: TCP
+        targetPort: 8080
+      selector:
+        app: sbo-perf-svc-invalid
+  - apiVersion: route.openshift.io/v1
+    kind: Route
+    metadata:
+      labels:
+        app: sbo-perf-svc-invalid
+      name: sbo-perf-svc-invalid
+      annotations:
+        service.binding/host: path={.spec.host}
+    spec:
+      port:
+        targetPort: 8080
+      to:
+        kind: "Service"
+        name: sbo-perf-svc-invalid
+  - apiVersion: binding.operators.coreos.com/v1alpha1
+    kind: ServiceBinding
+    metadata:
+      name: service-binding-invalid
+    spec:
+      services:
+      - group: route.openshift.io
+        version: v1
+        kind: Route
+        name: sbo-perf-svc-invalid
+      - group: ""
+        version: v1
+        kind: Service
+        name: sbo-perf-svc-invalid
+      application:
+        name: sbo-perf-app-invalid
+        group: apps
+        version: v1
+        resource: deployments
diff --git a/test/performance/user-workloads/invalid/sbo-test.without-sbr.user-workloads.yaml b/test/performance/user-workloads/invalid/sbo-test.without-sbr.user-workloads.yaml
new file mode 100644
index 00000000..c1557623
--- /dev/null
+++ b/test/performance/user-workloads/invalid/sbo-test.without-sbr.user-workloads.yaml
@@ -0,0 +1,110 @@
+kind: Template
+apiVersion: v1
+metadata:
+  name: sbo-perf-without-sbr-invalid
+objects:
+  - apiVersion: apps/v1
+    kind: Deployment
+    metadata:
+      name: sbo-perf-app-invalid
+      labels:
+        app: sbo-perf-app-invalid
+    spec:
+      replicas: 1
+      strategy:
+        type: RollingUpdate
+      selector:
+        matchLabels:
+          app: sbo-perf-app-invalid
+      template:
+        metadata:
+          labels:
+            app: sbo-perf-app-invalid
+        spec:
+          containers:
+          - name: sbo-generic-test-app
+            image: quay.io/redhat-developer/sbo-generic-test-app:20200923
+            imagePullPolicy: IfNotPresent
+            ports:
+            - containerPort: 8080
+  - apiVersion: v1
+    kind: Service
+    metadata:
+      labels:
+        app: sbo-perf-app-invalid
+      name: sbo-perf-app-invalid
+    spec:
+      ports:
+      - port: 8080
+        protocol: TCP
+        targetPort: 8080
+      selector:
+        app: sbo-perf-app-invalid
+  - apiVersion: route.openshift.io/v1
+    kind: Route
+    metadata:
+      labels:
+        app: sbo-perf-app-invalid
+      name: sbo-perf-app-invalid
+      annotations:
+        service.binding/host: path={.spec.host}
+    spec:
+      port:
+        targetPort: 8080
+      to:
+        kind: "Service"
+        name: sbo-perf-app-invalid
+  - apiVersion: apps/v1
+    kind: Deployment
+    metadata:
+      name: sbo-perf-svc-invalid
+      labels:
+        app: sbo-perf-svc-invalid
+    spec:
+      replicas: 1
+      strategy: 
+        type: RollingUpdate
+      selector:
+        matchLabels:
+          app: sbo-perf-svc-invalid
+      template:
+        metadata:
+          labels:
+            app: sbo-perf-svc-invalid
+        spec:
+          containers:
+          - name: busybox
+            image: busybox
+            imagePullPolicy: IfNotPresent
+            command: ['sh', '-c', 'echo Container 1 is Running ; sleep 3600']
+            ports:
+            - containerPort: 8080
+  - apiVersion: v1
+    kind: Service
+    metadata:
+      labels:
+        app: sbo-perf-svc-invalid
+      name: sbo-perf-svc-invalid
+      annotations:
+        service.binding/portMappings: "path={.spec.ports},elementType=sliceOfMaps,sourceKey=protocol,sourceValue=asdf"
+    spec:
+      ports:
+      - port: 8080
+        protocol: TCP
+        targetPort: 8080
+      selector:
+        app: sbo-perf-svc-invalid
+  - apiVersion: route.openshift.io/v1
+    kind: Route
+    metadata:
+      labels:
+        app: sbo-perf-svc-invalid
+      name: sbo-perf-svc-invalid
+      annotations:
+        service.binding/host: path={.spec.host}
+    spec:
+      port:
+        targetPort: 8080
+      to:
+        kind: "Service"
+        name: sbo-perf-svc-invalid
diff --git a/test/performance/user-workloads/valid/sbo-test.sbr.yaml b/test/performance/user-workloads/valid/sbo-test.sbr.yaml
new file mode 100644
index 00000000..fc203f09
--- /dev/null
+++ b/test/performance/user-workloads/valid/sbo-test.sbr.yaml
@@ -0,0 +1,20 @@
+---
+apiVersion: binding.operators.coreos.com/v1alpha1
+kind: ServiceBinding
+metadata:
+  name: service-binding-valid
+spec:
+  services:
+  - group: route.openshift.io
+    version: v1
+    kind: Route
+    name: sbo-perf-svc-valid
+  - group: ""
+    version: v1
+    kind: Service
+    name: sbo-perf-svc-valid
+  application:
+    name: sbo-perf-app-valid
+    group: apps
+    version: v1
+    resource: deployments
diff --git a/test/performance/user-workloads/valid/sbo-test.with-sbr.user-workloads.yaml b/test/performance/user-workloads/valid/sbo-test.with-sbr.user-workloads.yaml
new file mode 100644
index 00000000..75b945c4
--- /dev/null
+++ b/test/performance/user-workloads/valid/sbo-test.with-sbr.user-workloads.yaml
@@ -0,0 +1,129 @@
+kind: Template
+apiVersion: v1
+metadata:
+  name: sbo-perf-with-sbr-valid
+objects:
+  - apiVersion: apps/v1
+    kind: Deployment
+    metadata:
+      name: sbo-perf-app-valid
+      labels:
+        app: sbo-perf-app-valid
+    spec:
+      replicas: 1
+      strategy:
+        type: RollingUpdate
+      selector:
+        matchLabels:
+          app: sbo-perf-app-valid
+      template:
+        metadata:
+          labels:
+            app: sbo-perf-app-valid
+        spec:
+          containers:
+          - name: sbo-generic-test-app
+            image: quay.io/redhat-developer/sbo-generic-test-app:20200923
+            imagePullPolicy: IfNotPresent
+            ports:
+            - containerPort: 8080
+  - apiVersion: v1
+    kind: Service
+    metadata:
+      labels:
+        app: sbo-perf-app-valid
+      name: sbo-perf-app-valid
+    spec:
+      ports:
+      - port: 8080
+        protocol: TCP
+        targetPort: 8080
+      selector:
+        app: sbo-perf-app-valid
+  - apiVersion: route.openshift.io/v1
+    kind: Route
+    metadata:
+      labels:
+        app: sbo-perf-app-valid
+      name: sbo-perf-app-valid
+      annotations:
+        service.binding/host: path={.spec.host}
+    spec:
+      port:
+        targetPort: 8080
+      to:
+        kind: "Service"
+        name: sbo-perf-app-valid
+  - apiVersion: apps/v1
+    kind: Deployment
+    metadata:
+      name: sbo-perf-svc-valid
+      labels:
+        app: sbo-perf-svc-valid
+    spec:
+      replicas: 1
+      strategy: 
+        type: RollingUpdate
+      selector:
+        matchLabels:
+          app: sbo-perf-svc-valid
+      template:
+        metadata:
+          labels:
+            app: sbo-perf-svc-valid
+        spec:
+          containers:
+          - name: busybox
+            image: busybox
+            imagePullPolicy: IfNotPresent
+            command: ['sh', '-c', 'echo Container 1 is Running ; sleep 3600']
+            ports:
+            - containerPort: 8080
+  - apiVersion: v1
+    kind: Service
+    metadata:
+      labels:
+        app: sbo-perf-svc-valid
+      name: sbo-perf-svc-valid
+      annotations:
+        service.binding/portMappings: "path={.spec.ports},elementType=sliceOfMaps,sourceKey=protocol,sourceValue=targetPort"
+    spec:
+      ports:
+      - port: 8080
+        protocol: TCP
+        targetPort: 8080
+      selector:
+        app: sbo-perf-svc-valid
+  - apiVersion: route.openshift.io/v1
+    kind: Route
+    metadata:
+      labels:
+        app: sbo-perf-svc-valid
+      name: sbo-perf-svc-valid
+      annotations:
+        service.binding/host: path={.spec.host}
+    spec:
+      port:
+        targetPort: 8080
+      to:
+        kind: "Service"
+        name: sbo-perf-svc-valid
+  - apiVersion: binding.operators.coreos.com/v1alpha1
+    kind: ServiceBinding
+    metadata:
+      name: service-binding-valid
+    spec:
+      services:
+      - group: route.openshift.io
+        version: v1
+        kind: Route
+        name: sbo-perf-svc-valid
+      - group: ""
+        version: v1
+        kind: Service
+        name: sbo-perf-svc-valid
+      application:
+        name: sbo-perf-app-valid
+        group: apps
+        version: v1
+        resource: deployments
diff --git a/test/performance/user-workloads/valid/sbo-test.without-sbr.user-workloads.yaml b/test/performance/user-workloads/valid/sbo-test.without-sbr.user-workloads.yaml
new file mode 100644
index 00000000..af640a1a
--- /dev/null
+++ b/test/performance/user-workloads/valid/sbo-test.without-sbr.user-workloads.yaml
@@ -0,0 +1,110 @@
+kind: Template
+apiVersion: v1
+metadata:
+  name: sbo-perf-without-sbr-valid
+objects:
+  - apiVersion: apps/v1
+    kind: Deployment
+    metadata:
+      name: sbo-perf-app-valid
+      labels:
+        app: sbo-perf-app-valid
+    spec:
+      replicas: 1
+      strategy:
+        type: RollingUpdate
+      selector:
+        matchLabels:
+          app: sbo-perf-app-valid
+      template:
+        metadata:
+          labels:
+            app: sbo-perf-app-valid
+        spec:
+          containers:
+          - name: sbo-generic-test-app
+            image: quay.io/redhat-developer/sbo-generic-test-app:20200923
+            imagePullPolicy: IfNotPresent
+            ports:
+            - containerPort: 8080
+  - apiVersion: v1
+    kind: Service
+    metadata:
+      labels:
+        app: sbo-perf-app-valid
+      name: sbo-perf-app-valid
+    spec:
+      ports:
+      - port: 8080
+        protocol: TCP
+        targetPort: 8080
+      selector:
+        app: sbo-perf-app-valid
+  - apiVersion: route.openshift.io/v1
+    kind: Route
+    metadata:
+      labels:
+        app: sbo-perf-app-valid
+      name: sbo-perf-app-valid
+      annotations:
+        service.binding/host: path={.spec.host}
+    spec:
+      port:
+        targetPort: 8080
+      to:
+        kind: "Service"
+        name: sbo-perf-app-valid
+  - apiVersion: apps/v1
+    kind: Deployment
+    metadata:
+      name: sbo-perf-svc-valid
+      labels:
+        app: sbo-perf-svc-valid
+    spec:
+      replicas: 1
+      strategy: 
+        type: RollingUpdate
+      selector:
+        matchLabels:
+          app: sbo-perf-svc-valid
+      template:
+        metadata:
+          labels:
+            app: sbo-perf-svc-valid
+        spec:
+          containers:
+          - name: busybox
+            image: busybox
+            imagePullPolicy: IfNotPresent
+            command: ['sh', '-c', 'echo Container 1 is Running ; sleep 3600']
+            ports:
+            - containerPort: 8080
+  - apiVersion: v1
+    kind: Service
+    metadata:
+      labels:
+        app: sbo-perf-svc-valid
+      name: sbo-perf-svc-valid
+      annotations:
+        service.binding/portMappings: "path={.spec.ports},elementType=sliceOfMaps,sourceKey=protocol,sourceValue=targetPort"
+    spec:
+      ports:
+      - port: 8080
+        protocol: TCP
+        targetPort: 8080
+      selector:
+        app: sbo-perf-svc-valid
+  - apiVersion: route.openshift.io/v1
+    kind: Route
+    metadata:
+      labels:
+        app: sbo-perf-svc-valid
+      name: sbo-perf-svc-valid
+      annotations:
+        service.binding/host: path={.spec.host}
+    spec:
+      port:
+        targetPort: 8080
+      to:
+        kind: "Service"
+        name: sbo-perf-svc-valid