Repository: knative/pkg
Pull request #1301 (2020-05-05 22:55:20)

Implement the first wave of per-reconciler leaderelection.
Detailed design: https://docs.google.com/document/d/1i_QHjQO2T3SNv49xjZLWlivcc0UvZN1Tbw2NKxThkyM/edit

Issue: https://github.com/knative/pkg/issues/1181

WIP as we discuss the doc, but functionally complete and comments are welcome.

knative-prow-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/knative/pkg/pull/1301#" title="Author self-approved">mattmoor</a>*

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

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

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

- ~~[OWNERS](https://github.com/knative/pkg/blob/master/OWNERS)~~ [mattmoor]

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":[]} -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[controller/controller.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1257806185088684032/artifacts/line-cov.html#file0) | 95.2% | 91.6% | -3.6
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1257806185088684032/artifacts/line-cov.html#file1) | Do not exist | 78.8% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1257806185088684032/artifacts/line-cov.html#file2) | Do not exist | 100.0% | 

knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[controller/controller.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1258835592943243266/artifacts/line-cov.html#file0) | 95.2% | 91.6% | -3.6
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1258835592943243266/artifacts/line-cov.html#file1) | Do not exist | 78.8% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1258835592943243266/artifacts/line-cov.html#file2) | Do not exist | 100.0% | 

knative-prow-robot: @mattmoor: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-pkg-unit-tests | 2fd7463a8d3f4f0d2ca4b29e05f09791a9439e76 | [link](https://prow.knative.dev/view/gcs/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-unit-tests/1258835592943243267) | `/test pull-knative-pkg-unit-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_pkg/1301). [Your PR dashboard](https://gubernator.knative.dev/pr/mattmoor).

<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 -->
mattmoor: /retest
knative-prow-robot: @mattmoor: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-pkg-unit-tests | 2fd7463a8d3f4f0d2ca4b29e05f09791a9439e76 | [link](https://prow.knative.dev/view/gcs/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-unit-tests/1258845407392305152) | `/test pull-knative-pkg-unit-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_pkg/1301). [Your PR dashboard](https://gubernator.knative.dev/pr/mattmoor).

<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 -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[controller/controller.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1259338657723060226/artifacts/line-cov.html#file0) | 95.2% | 91.6% | -3.6
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1259338657723060226/artifacts/line-cov.html#file1) | Do not exist | 78.8% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1259338657723060226/artifacts/line-cov.html#file2) | Do not exist | 100.0% | 

knative-prow-robot: @mattmoor: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-pkg-unit-tests | 4454870bac4ccc7711e7048bcae0ea1d726d9750 | [link](https://prow.knative.dev/view/gcs/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-unit-tests/1259338657723060224) | `/test pull-knative-pkg-unit-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_pkg/1301). [Your PR dashboard](https://gubernator.knative.dev/pr/mattmoor).

<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 -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[controller/controller.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1259858708997869568/artifacts/line-cov.html#file0) | 95.2% | 91.6% | -3.6
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1259858708997869568/artifacts/line-cov.html#file1) | Do not exist | 78.8% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1259858708997869568/artifacts/line-cov.html#file2) | Do not exist | 100.0% | 

knative-prow-robot: @mattmoor: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-pkg-unit-tests | 4454870bac4ccc7711e7048bcae0ea1d726d9750 | [link](https://prow.knative.dev/view/gcs/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-unit-tests/1259338657723060224) | `/test pull-knative-pkg-unit-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_pkg/1301). [Your PR dashboard](https://gubernator.knative.dev/pr/mattmoor).

<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 -->
knative-prow-robot: @mattmoor: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-pkg-unit-tests | 2fd7463a8d3f4f0d2ca4b29e05f09791a9439e76 | [link](https://prow.knative.dev/view/gcs/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-unit-tests/1258845407392305152) | `/test pull-knative-pkg-unit-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_pkg/1301). [Your PR dashboard](https://gubernator.knative.dev/pr/mattmoor).

<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 -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[controller/controller.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1260070347261087747/artifacts/line-cov.html#file0) | 95.2% | 91.3% | -3.9
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1260070347261087747/artifacts/line-cov.html#file1) | Do not exist | 82.2% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1260070347261087747/artifacts/line-cov.html#file2) | Do not exist | 100.0% | 

knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[controller/controller.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1260075127853289473/artifacts/line-cov.html#file0) | 95.2% | 91.3% | -3.9
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1260075127853289473/artifacts/line-cov.html#file1) | Do not exist | 82.2% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1260075127853289473/artifacts/line-cov.html#file2) | Do not exist | 100.0% | 

knative-prow-robot: @mattmoor: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-pkg-unit-tests | 1e729a19a42eb6366c4b6ba1f45d09e37942ed8b | [link](https://prow.knative.dev/view/gcs/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-unit-tests/1260075127853289472) | `/test pull-knative-pkg-unit-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_pkg/1301). [Your PR dashboard](https://gubernator.knative.dev/pr/mattmoor).

<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 -->
mattmoor: /retest
knative-prow-robot: @mattmoor: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-pkg-unit-tests | 2fd7463a8d3f4f0d2ca4b29e05f09791a9439e76 | [link](https://prow.knative.dev/view/gcs/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-unit-tests/1258835592943243267) | `/test pull-knative-pkg-unit-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_pkg/1301). [Your PR dashboard](https://gubernator.knative.dev/pr/mattmoor).

<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 -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[controller/controller.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1261025570456604672/artifacts/line-cov.html#file0) | 95.2% | 91.3% | -3.9
[leaderelection/config.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1261025570456604672/artifacts/line-cov.html#file1) | 68.8% | 82.5% | 13.7
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1261025570456604672/artifacts/line-cov.html#file2) | Do not exist | 81.8% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1261025570456604672/artifacts/line-cov.html#file3) | Do not exist | 100.0% | 

knative-prow-robot: @mattmoor: 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>
knative-prow-robot: @mattmoor: 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>
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[controller/controller.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1266498126907183105/artifacts/line-cov.html#file1) | 92.9% | 91.3% | -1.6
[leaderelection/config.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1266498126907183105/artifacts/line-cov.html#file2) | 56.5% | 72.0% | 15.5
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1266498126907183105/artifacts/line-cov.html#file3) | Do not exist | 81.8% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1266498126907183105/artifacts/line-cov.html#file4) | Do not exist | 100.0% | 

knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[leaderelection/config.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1266503662822232066/artifacts/line-cov.html#file1) | 56.5% | 72.0% | 15.5
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1266503662822232066/artifacts/line-cov.html#file2) | Do not exist | 81.8% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1266503662822232066/artifacts/line-cov.html#file3) | Do not exist | 100.0% | 

knative-prow-robot: @mattmoor: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-pkg-unit-tests | d06f1032ec33dd166c9b6bd5815696218bc1ae91 | [link](https://prow.knative.dev/view/gcs/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-unit-tests/1266503662822232065) | `/test pull-knative-pkg-unit-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_pkg/1301). [Your PR dashboard](https://gubernator.knative.dev/pr/mattmoor).

<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 -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[controller/controller.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1266514106890850304/artifacts/line-cov.html#file1) | 92.9% | 91.3% | -1.6
[leaderelection/config.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1266514106890850304/artifacts/line-cov.html#file2) | 56.5% | 72.0% | 15.5
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1266514106890850304/artifacts/line-cov.html#file3) | Do not exist | 81.8% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1266514106890850304/artifacts/line-cov.html#file4) | Do not exist | 100.0% | 

lionelvillard: `leader.go` seems to be generic enough to be either in `leaderelection` or somewhere else. 

evankanderson: Matt, do you want to pick out and `/assign` some reviewers? I'm willing to do it, but in the absence of specific ownership, I fear this won't get reviewed in a timely fashion.
mattmoor: @evankanderson Waiting on the feature track doc to get approvals before landing any of the pieces here, which is why I've left this as WIP.
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[controller/controller.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1269370751509598208/artifacts/line-cov.html#file1) | 92.9% | 91.3% | -1.5
[leaderelection/config.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1269370751509598208/artifacts/line-cov.html#file2) | 56.5% | 72.0% | 15.5
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1269370751509598208/artifacts/line-cov.html#file3) | Do not exist | 81.8% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1269370751509598208/artifacts/line-cov.html#file4) | Do not exist | 100.0% | 

knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[controller/controller.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1270118883927265282/artifacts/line-cov.html#file1) | 92.9% | 91.4% | -1.5
[leaderelection/config.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1270118883927265282/artifacts/line-cov.html#file2) | 56.5% | 72.0% | 15.5
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1270118883927265282/artifacts/line-cov.html#file3) | Do not exist | 81.8% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1270118883927265282/artifacts/line-cov.html#file4) | Do not exist | 100.0% | 

mattmoor: > leader.go seems to be generic enough to be either in leaderelection or somewhere else

Perhaps, but the concepts are intended to apply to Reconciliation, and the bucketing is intended to partition the key-space (`types.NamespacedName`).  I think the `reconciler.Foo` will help frame some of the names in ways that contextualize them and make them clearer.

The nice thing about Go interfaces is you can just copy them elsewhere later (if you want) and they still apply! 😉 
mattmoor: /hold

I'm going to change the hold type here.  Dave is the last approver on the feature track document, but also a reviewer here.  I've stopped editing these (other than rebases and review comments) a few weeks ago, so the main thing blocking this is the feature track approvals and code review.
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[controller/controller.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1270479428173959168/artifacts/line-cov.html#file1) | 92.9% | 91.4% | -1.5
[leaderelection/config.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1270479428173959168/artifacts/line-cov.html#file2) | 56.5% | 72.0% | 15.5
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1270479428173959168/artifacts/line-cov.html#file3) | Do not exist | 81.8% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1270479428173959168/artifacts/line-cov.html#file4) | Do not exist | 100.0% | 

knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[controller/controller.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1270501950839853059/artifacts/line-cov.html#file1) | 92.9% | 91.4% | -1.5
[leaderelection/config.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1270501950839853059/artifacts/line-cov.html#file2) | 56.5% | 72.0% | 15.5
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1270501950839853059/artifacts/line-cov.html#file3) | Do not exist | 81.8% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1270501950839853059/artifacts/line-cov.html#file4) | Do not exist | 100.0% | 

knative-prow-robot: @mattmoor: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-pkg-unit-tests | ac90b3089f4172f5e8c0c9010d7f6ad62a15cf14 | [link](https://prow.knative.dev/view/gcs/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-unit-tests/1270501950839853058) | `/test pull-knative-pkg-unit-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_pkg/1301). [Your PR dashboard](https://gubernator.knative.dev/pr/mattmoor).

<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 -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[controller/controller.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1270531017685340160/artifacts/line-cov.html#file1) | 92.9% | 91.4% | -1.5
[leaderelection/config.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1270531017685340160/artifacts/line-cov.html#file2) | 56.5% | 72.0% | 15.5
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1270531017685340160/artifacts/line-cov.html#file3) | Do not exist | 81.8% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1270531017685340160/artifacts/line-cov.html#file4) | Do not exist | 100.0% | 

knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[controller/controller.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1270905359631912963/artifacts/line-cov.html#file1) | 92.9% | 91.4% | -1.5
[leaderelection/config.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1270905359631912963/artifacts/line-cov.html#file2) | 56.5% | 72.0% | 15.5
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1270905359631912963/artifacts/line-cov.html#file3) | Do not exist | 81.8% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1270905359631912963/artifacts/line-cov.html#file4) | Do not exist | 100.0% | 

knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[controller/controller.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1273439936116166657/artifacts/line-cov.html#file1) | 92.9% | 91.4% | -1.5
[leaderelection/config.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1273439936116166657/artifacts/line-cov.html#file2) | 56.5% | 72.0% | 15.5
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1273439936116166657/artifacts/line-cov.html#file3) | Do not exist | 81.8% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1273439936116166657/artifacts/line-cov.html#file4) | Do not exist | 100.0% | 

knative-prow-robot: New changes are detected. LGTM label has been removed.
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[controller/controller.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1273731229706883074/artifacts/line-cov.html#file1) | 92.9% | 91.4% | -1.5
[leaderelection/config.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1273731229706883074/artifacts/line-cov.html#file2) | 56.5% | 72.0% | 15.5
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1273731229706883074/artifacts/line-cov.html#file3) | Do not exist | 81.8% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1273731229706883074/artifacts/line-cov.html#file4) | Do not exist | 100.0% | 

knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[leaderelection/config.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1273759918846906369/artifacts/line-cov.html#file1) | 56.5% | 72.0% | 15.5
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1273759918846906369/artifacts/line-cov.html#file2) | Do not exist | 80.0% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1273759918846906369/artifacts/line-cov.html#file3) | Do not exist | 96.0% | 

knative-prow-robot: @mattmoor: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-pkg-unit-tests | 00ed80bac41093151f3bd331147dc0ef00c87791 | [link](https://prow.knative.dev/view/gcs/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-unit-tests/1273759918846906370) | `/test pull-knative-pkg-unit-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_pkg/1301). [Your PR dashboard](https://gubernator.knative.dev/pr/mattmoor).

<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 -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[controller/controller.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1273762435534163968/artifacts/line-cov.html#file1) | 92.9% | 91.4% | -1.5
[leaderelection/config.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1273762435534163968/artifacts/line-cov.html#file2) | 56.5% | 72.0% | 15.5
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1273762435534163968/artifacts/line-cov.html#file3) | Do not exist | 80.0% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1273762435534163968/artifacts/line-cov.html#file4) | Do not exist | 96.0% | 

dprotaso: /lgtm
/approve
knative-prow-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/knative/pkg/pull/1301#issuecomment-646391415" title="Approved">dprotaso</a>*, *<a href="https://github.com/knative/pkg/pull/1301#" title="Author self-approved">mattmoor</a>*

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

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

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

- ~~[OWNERS](https://github.com/knative/pkg/blob/master/OWNERS)~~ [mattmoor]

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":[]} -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-pkg-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[controller/controller.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1273795654337957888/artifacts/line-cov.html#file1) | 92.9% | 91.4% | -1.5
[leaderelection/config.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1273795654337957888/artifacts/line-cov.html#file2) | 56.5% | 72.0% | 15.5
[leaderelection/context.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1273795654337957888/artifacts/line-cov.html#file3) | Do not exist | 80.0% | 
[reconciler/leader.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_pkg/1301/pull-knative-pkg-go-coverage/1273795654337957888/artifacts/line-cov.html#file4) | Do not exist | 96.0% | 

mattmoor: /hold cancel

Patch:
diff --git a/configmap/parse.go b/configmap/parse.go
index 210d2114e..1ef675818 100644
--- a/configmap/parse.go
+++ b/configmap/parse.go
@@ -77,6 +77,20 @@ func AsInt64(key string, target *int64) ParseFunc {
 	}
 }
 
+// AsUint32 parses the value at key as an uint32 into the target, if it exists.
+func AsUint32(key string, target *uint32) ParseFunc {
+	return func(data map[string]string) error {
+		if raw, ok := data[key]; ok {
+			val, err := strconv.ParseUint(raw, 10, 32)
+			if err != nil {
+				return fmt.Errorf("failed to parse %q: %w", key, err)
+			}
+			*target = uint32(val)
+		}
+		return nil
+	}
+}
+
 // AsFloat64 parses the value at key as a float64 into the target, if it exists.
 func AsFloat64(key string, target *float64) ParseFunc {
 	return func(data map[string]string) error {
diff --git a/controller/controller.go b/controller/controller.go
index 3981c5820..6024ec253 100644
--- a/controller/controller.go
+++ b/controller/controller.go
@@ -36,8 +36,10 @@ import (
 	"k8s.io/client-go/util/workqueue"
 
 	"knative.dev/pkg/kmeta"
+	kle "knative.dev/pkg/leaderelection"
 	"knative.dev/pkg/logging"
 	"knative.dev/pkg/logging/logkey"
+	"knative.dev/pkg/reconciler"
 )
 
 const (
@@ -176,6 +178,10 @@ func FilterWithNameAndNamespace(namespace, name string) func(obj interface{}) bo
 // Impl is our core controller implementation.  It handles queuing and feeding work
 // from the queue to an implementation of Reconciler.
 type Impl struct {
+	// Name is the unique name for this controller workqueue within this process.
+	// This is used for surfacing metrics, and per-controller leader election.
+	Name string
+
 	// Reconciler is the workhorse of this controller, it is fed the keys
 	// from the workqueue to process.  Public for testing.
 	Reconciler Reconciler
@@ -205,7 +211,9 @@ func NewImpl(r Reconciler, logger *zap.SugaredLogger, workQueueName string) *Imp
 }
 
 func NewImplWithStats(r Reconciler, logger *zap.SugaredLogger, workQueueName string, reporter StatsReporter) *Impl {
+	logger = logger.Named(workQueueName)
 	return &Impl{
+		Name:       workQueueName,
 		Reconciler: r,
 		WorkQueue: workqueue.NewNamedRateLimitingQueue(
 			workqueue.DefaultControllerRateLimiter(),
@@ -341,6 +349,14 @@ func (c *Impl) EnqueueKey(key types.NamespacedName) {
 	c.logger.Debugf("Adding to queue %s (depth: %d)", safeKey(key), c.WorkQueue.Len())
 }
 
+// MaybeEnqueueBucketKey takes a Bucket and namespace/name string and puts it onto the work queue.
+func (c *Impl) MaybeEnqueueBucketKey(bkt reconciler.Bucket, key types.NamespacedName) {
+	if bkt.Has(key) {
+		c.WorkQueue.Add(key)
+		c.logger.Debugf("Adding to queue %s (depth: %d)", safeKey(key), c.WorkQueue.Len())
+	}
+}
+
 // EnqueueKeyAfter takes a namespace/name string and schedules its execution in
 // the work queue after given delay.
 func (c *Impl) EnqueueKeyAfter(key types.NamespacedName, delay time.Duration) {
@@ -349,10 +365,12 @@ func (c *Impl) EnqueueKeyAfter(key types.NamespacedName, delay time.Duration) {
 }
 
 // RunContext starts the controller's worker threads, the number of which is threadiness.
+// If the context has been decorated for LeaderElection, then an elector is built and run.
 // It then blocks until the context is cancelled, at which point it shuts down its
 // internal work queue and waits for workers to finish processing their current
 // work items.
 func (c *Impl) RunContext(ctx context.Context, threadiness int) error {
+	logger := c.logger
 	defer runtime.HandleCrash()
 	sg := sync.WaitGroup{}
 	defer sg.Wait()
@@ -363,8 +381,20 @@ func (c *Impl) RunContext(ctx context.Context, threadiness int) error {
 		}
 	}()
 
+	if la, ok := c.Reconciler.(reconciler.LeaderAware); ok {
+		// Build and execute an elector.
+		le, err := kle.BuildElector(ctx, la, c.Name, c.MaybeEnqueueBucketKey)
+		if err != nil {
+			return err
+		}
+		sg.Add(1)
+		go func() {
+			defer sg.Done()
+			le.Run(ctx)
+		}()
+	}
+
 	// Launch workers to process resources that get enqueued to our workqueue.
-	logger := c.logger
 	logger.Info("Starting controller and workers")
 	for i := 0; i < threadiness; i++ {
 		sg.Add(1)
diff --git a/injection/sharedmain/main.go b/injection/sharedmain/main.go
index 1410138e1..5c2ba94a3 100644
--- a/injection/sharedmain/main.go
+++ b/injection/sharedmain/main.go
@@ -52,6 +52,7 @@ import (
 	"knative.dev/pkg/logging"
 	"knative.dev/pkg/metrics"
 	"knative.dev/pkg/profiling"
+	"knative.dev/pkg/reconciler"
 	"knative.dev/pkg/signals"
 	"knative.dev/pkg/system"
 	"knative.dev/pkg/version"
@@ -399,6 +400,11 @@ func SecretFetcher(ctx context.Context) metrics.SecretFetcher {
 func ControllersAndWebhooksFromCtors(ctx context.Context,
 	cmw *configmap.InformedWatcher,
 	ctors ...injection.ControllerConstructor) ([]*controller.Impl, []interface{}) {
+
+	// Check whether the context has been infused with a leader elector builder.
+	// If it has, then every reconciler we plan to start MUST implement LeaderAware.
+	leEnabled := kle.HasLeaderElection(ctx)
+
 	controllers := make([]*controller.Impl, 0, len(ctors))
 	webhooks := make([]interface{}, 0)
 	for _, cf := range ctors {
@@ -410,6 +416,12 @@ func ControllersAndWebhooksFromCtors(ctx context.Context,
 		case webhook.AdmissionController, webhook.ConversionController:
 			webhooks = append(webhooks, c)
 		}
+
+		if leEnabled {
+			if _, ok := ctrl.Reconciler.(reconciler.LeaderAware); !ok {
+				log.Fatalf("%T is not leader-aware, all reconcilers must be leader-aware to enable fine-grained leader election.", ctrl.Reconciler)
+			}
+		}
 	}
 
 	return controllers, webhooks
diff --git a/leaderelection/config.go b/leaderelection/config.go
index 06f0874e2..44378d30c 100644
--- a/leaderelection/config.go
+++ b/leaderelection/config.go
@@ -30,6 +30,10 @@ import (
 
 const configMapNameEnv = "CONFIG_LEADERELECTION_NAME"
 
+// MaxBuckets is the maximum number of buckets to allow users to define.
+// This is a variable so that it may be customized in the binary entrypoint.
+var MaxBuckets uint32 = 10
+
 var validResourceLocks = sets.NewString("leases", "configmaps", "endpoints")
 
 // NewConfigFromMap returns a Config for the given map, or an error.
@@ -43,6 +47,8 @@ func NewConfigFromMap(data map[string]string) (*Config, error) {
 		cm.AsDuration("renewDeadline", &config.RenewDeadline),
 		cm.AsDuration("retryPeriod", &config.RetryPeriod),
 
+		cm.AsUint32("buckets", &config.Buckets),
+
 		// enabledComponents are not validated here, because they are dependent on
 		// the component. Components should provide additional validation for this
 		// field.
@@ -51,6 +57,9 @@ func NewConfigFromMap(data map[string]string) (*Config, error) {
 		return nil, err
 	}
 
+	if config.Buckets < 1 || config.Buckets > MaxBuckets {
+		return nil, fmt.Errorf("buckets: value must be between %d <= %d <= %d", 1, config.Buckets, MaxBuckets)
+	}
 	if !validResourceLocks.Has(config.ResourceLock) {
 		return nil, fmt.Errorf(`resourceLock: invalid value %q: valid values are "leases","configmaps","endpoints"`, config.ResourceLock)
 	}
@@ -72,6 +81,7 @@ func NewConfigFromConfigMap(configMap *corev1.ConfigMap) (*Config, error) {
 // single source repository, viz: serving or eventing.
 type Config struct {
 	ResourceLock      string
+	Buckets           uint32
 	LeaseDuration     time.Duration
 	RenewDeadline     time.Duration
 	RetryPeriod       time.Duration
@@ -83,6 +93,7 @@ func (c *Config) GetComponentConfig(name string) ComponentConfig {
 		return ComponentConfig{
 			Component:     name,
 			LeaderElect:   true,
+			Buckets:       c.Buckets,
 			ResourceLock:  c.ResourceLock,
 			LeaseDuration: c.LeaseDuration,
 			RenewDeadline: c.RenewDeadline,
@@ -96,6 +107,7 @@ func (c *Config) GetComponentConfig(name string) ComponentConfig {
 func defaultConfig() *Config {
 	return &Config{
 		ResourceLock:      "leases",
+		Buckets:           1,
 		LeaseDuration:     15 * time.Second,
 		RenewDeadline:     10 * time.Second,
 		RetryPeriod:       2 * time.Second,
@@ -107,6 +119,7 @@ func defaultConfig() *Config {
 type ComponentConfig struct {
 	Component     string
 	LeaderElect   bool
+	Buckets       uint32
 	ResourceLock  string
 	LeaseDuration time.Duration
 	RenewDeadline time.Duration
diff --git a/leaderelection/context.go b/leaderelection/context.go
new file mode 100644
index 000000000..a0d8555f9
--- /dev/null
+++ b/leaderelection/context.go
@@ -0,0 +1,223 @@
+/*
+Copyright 2020 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package leaderelection
+
+import (
+	"context"
+	"fmt"
+	"hash/fnv"
+	"strings"
+	"sync"
+
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/tools/leaderelection"
+	"k8s.io/client-go/tools/leaderelection/resourcelock"
+	"knative.dev/pkg/logging"
+	"knative.dev/pkg/reconciler"
+	"knative.dev/pkg/system"
+)
+
+// WithStandardLeaderElectorBuilder infuses a context with the ability to build
+// LeaderElectors with the provided component configuration acquiring resource
+// locks via the provided kubernetes client.
+func WithStandardLeaderElectorBuilder(ctx context.Context, kc kubernetes.Interface, cc ComponentConfig) context.Context {
+	return context.WithValue(ctx, builderKey{}, &standardBuilder{
+		kc:  kc,
+		lec: cc,
+	})
+}
+
+// HasLeaderElection returns whether there is leader election configuration
+// associated with the context
+func HasLeaderElection(ctx context.Context) bool {
+	val := ctx.Value(builderKey{})
+	return val != nil
+}
+
+// Elector is the interface for running a leader elector.
+type Elector interface {
+	Run(context.Context)
+}
+
+// BuildElector builds a leaderelection.LeaderElector for the named LeaderAware
+// reconciler using a builder added to the context via WithStandardLeaderElectorBuilder.
+func BuildElector(ctx context.Context, la reconciler.LeaderAware, name string, enq func(reconciler.Bucket, types.NamespacedName)) (Elector, error) {
+	if val := ctx.Value(builderKey{}); val != nil {
+		switch builder := val.(type) {
+		case *standardBuilder:
+			return builder.BuildElector(ctx, la, name, enq)
+		}
+		// TODO(mattmoor): Add a flavor of builder that relies on StatefulSet to partition the key space.
+	}
+
+	return &unopposedElector{
+		la:  la,
+		bkt: reconciler.UniversalBucket(),
+		enq: enq,
+	}, nil
+}
+
+type builderKey struct{}
+
+type standardBuilder struct {
+	kc  kubernetes.Interface
+	lec ComponentConfig
+}
+
+func (b *standardBuilder) BuildElector(ctx context.Context, la reconciler.LeaderAware, name string, enq func(reconciler.Bucket, types.NamespacedName)) (Elector, error) {
+	logger := logging.FromContext(ctx)
+
+	id, err := UniqueID()
+	if err != nil {
+		return nil, err
+	}
+
+	buckets := make([]Elector, 0, b.lec.Buckets)
+	for i := uint32(0); i < b.lec.Buckets; i++ {
+		bkt := &bucket{
+			component: b.lec.Component,
+			name:      name,
+			index:     i,
+			total:     b.lec.Buckets,
+		}
+
+		rl, err := resourcelock.New(b.lec.ResourceLock,
+			system.Namespace(), // use namespace we are running in
+			bkt.Name(),
+			b.kc.CoreV1(),
+			b.kc.CoordinationV1(),
+			resourcelock.ResourceLockConfig{
+				Identity: id,
+			})
+		if err != nil {
+			return nil, err
+		}
+		logger.Infof("%s will run in leader-elected mode with id %q", bkt.Name(), rl.Identity())
+
+		le, err := leaderelection.NewLeaderElector(leaderelection.LeaderElectionConfig{
+			Lock:          rl,
+			LeaseDuration: b.lec.LeaseDuration,
+			RenewDeadline: b.lec.RenewDeadline,
+			RetryPeriod:   b.lec.RetryPeriod,
+			Callbacks: leaderelection.LeaderCallbacks{
+				OnStartedLeading: func(context.Context) {
+					logger.Infof("%q has started leading %q", rl.Identity(), bkt.Name())
+					if err := la.Promote(bkt, enq); err != nil {
+						// TODO(mattmoor): We expect this to effectively never happen,
+						// but if it does, we should support wrapping `le` in an elector
+						// we can cancel here.
+						logger.Fatalf("%q failed to Promote: %v", rl.Identity(), err)
+					}
+				},
+				OnStoppedLeading: func() {
+					logger.Infof("%q has stopped leading %q", rl.Identity(), bkt.Name())
+					la.Demote(bkt)
+				},
+			},
+			ReleaseOnCancel: true,
+
+			Name: rl.Identity(),
+		})
+		if err != nil {
+			return nil, err
+		}
+		// TODO: use health check watchdog, knative/pkg#1048
+		// if lec.WatchDog != nil {
+		// 	lec.WatchDog.SetLeaderElection(le)
+		// }
+		buckets = append(buckets, &runUntilCancelled{Elector: le})
+	}
+	return &runAll{les: buckets}, nil
+}
+
+// unopposedElector promotes when run without needing to be elected.
+type unopposedElector struct {
+	bkt reconciler.Bucket
+	la  reconciler.LeaderAware
+	enq func(reconciler.Bucket, types.NamespacedName)
+}
+
+// Run implements Elector
+func (ue *unopposedElector) Run(ctx context.Context) {
+	ue.la.Promote(ue.bkt, ue.enq)
+}
+
+type runAll struct {
+	les []Elector
+}
+
+// Run implements Elector
+func (ra *runAll) Run(ctx context.Context) {
+	sg := sync.WaitGroup{}
+	defer sg.Wait()
+
+	for _, le := range ra.les {
+		sg.Add(1)
+		go func(le Elector) {
+			defer sg.Done()
+			le.Run(ctx)
+		}(le)
+	}
+}
+
+// runUntilCancelled wraps a single-term Elector into one that runs until
+// the passed context is cancelled.
+type runUntilCancelled struct {
+	// Elector is a single-term elector as we get from K8s leaderelection package.
+	Elector
+}
+
+// Run implements Elector
+func (ruc *runUntilCancelled) Run(ctx context.Context) {
+	// Turn the single-term elector into a continuous election cycle.
+	for {
+		ruc.Elector.Run(ctx)
+		select {
+		case <-ctx.Done():
+			return // Run quit because context was cancelled, we are done!
+		default:
+			// Context wasn't cancelled, start over.
+		}
+	}
+}
+
+type bucket struct {
+	component string
+	name      string
+
+	// We are bucket {index} of {total}
+	index uint32
+	total uint32
+}
+
+var _ reconciler.Bucket = (*bucket)(nil)
+
+// Name implements reconciler.Bucket
+func (b *bucket) Name() string {
+	// The resource name is the lowercase:
+	//   {component}.{workqueue}.{index}-of-{total}
+	return strings.ToLower(fmt.Sprintf("%s.%s.%02d-of-%02d", b.component, b.name, b.index, b.total))
+}
+
+// Has implements reconciler.Bucket
+func (b *bucket) Has(nn types.NamespacedName) bool {
+	h := fnv.New32a()
+	h.Write([]byte(nn.Namespace + "." + nn.Name))
+	ii := h.Sum32() % b.total
+	return b.index == ii
+}
diff --git a/reconciler/leader.go b/reconciler/leader.go
new file mode 100644
index 000000000..3d5f68fdc
--- /dev/null
+++ b/reconciler/leader.go
@@ -0,0 +1,118 @@
+/*
+Copyright 2020 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package reconciler
+
+import (
+	"sync"
+
+	"k8s.io/apimachinery/pkg/types"
+)
+
+// Bucket is an opaque type used to scope leadership.
+type Bucket interface {
+	// Name returns a string representing this bucket, which uniquely
+	// identifies the bucket and is suitable for use as a resource lock name.
+	Name() string
+
+	// Has determines whether this Bucket contains a particular key.
+	Has(key types.NamespacedName) bool
+}
+
+// UniversalBucket returns a Bucket that "Has()" all keys.
+func UniversalBucket() Bucket {
+	return &bucket{}
+}
+
+type bucket struct{}
+
+var _ Bucket = (*bucket)(nil)
+
+// Name implements Bucket
+func (b *bucket) Name() string {
+	return ""
+}
+
+// Has implements Bucket
+func (b *bucket) Has(nn types.NamespacedName) bool {
+	return true
+}
+
+// LeaderAware is implemented by Reconcilers that are aware of their leader status.
+type LeaderAware interface {
+	// Promote is called when we become the leader of a given Bucket.  It must be
+	// supplied with an enqueue function through which a Bucket resync may be triggered.
+	Promote(b Bucket, enq func(Bucket, types.NamespacedName)) error
+
+	// Demote is called when we stop being the leader for the specified Bucket.
+	Demote(Bucket)
+}
+
+// LeaderAwareFuncs implements LeaderAware using the given functions for handling
+// promotion and demotion.
+type LeaderAwareFuncs struct {
+	sync.RWMutex
+	buckets map[string]Bucket
+
+	PromoteFunc func(b Bucket, enq func(Bucket, types.NamespacedName)) error
+	DemoteFunc  func(b Bucket)
+}
+
+var _ LeaderAware = (*LeaderAwareFuncs)(nil)
+
+// IsLeaderFor implements LeaderAware
+func (laf *LeaderAwareFuncs) IsLeaderFor(key types.NamespacedName) bool {
+	laf.RLock()
+	defer laf.RUnlock()
+
+	for _, bkt := range laf.buckets {
+		if bkt.Has(key) {
+			return true
+		}
+	}
+	return false
+}
+
+// Promote implements LeaderAware
+func (laf *LeaderAwareFuncs) Promote(b Bucket, enq func(Bucket, types.NamespacedName)) error {
+	func() {
+		laf.Lock()
+		defer laf.Unlock()
+		if laf.buckets == nil {
+			laf.buckets = make(map[string]Bucket, 1)
+		}
+		laf.buckets[b.Name()] = b
+		return
+	}()
+
+	if promote := laf.PromoteFunc; promote != nil {
+		return promote(b, enq)
+	}
+	return nil
+}
+
+// Demote implements LeaderAware
+func (laf *LeaderAwareFuncs) Demote(b Bucket) {
+	func() {
+		laf.Lock()
+		defer laf.Unlock()
+		delete(laf.buckets, b.Name())
+	}()
+
+	if demote := laf.DemoteFunc; demote != nil {
+		demote(b)
+	}
+}


Test patch:
diff --git a/configmap/parse_test.go b/configmap/parse_test.go
index 477dd1488..26fb3332d 100644
--- a/configmap/parse_test.go
+++ b/configmap/parse_test.go
@@ -30,6 +30,7 @@ type testConfig struct {
 	boo bool
 	i32 int32
 	i64 int64
+	u32 uint32
 	f64 float64
 	dur time.Duration
 	set sets.String
@@ -51,6 +52,7 @@ func TestParse(t *testing.T) {
 			"test-bool":     "true",
 			"test-int32":    "1",
 			"test-int64":    "2",
+			"test-uint32":   "3",
 			"test-float64":  "1.0",
 			"test-duration": "1m",
 			"test-set":      "a,b,c",
@@ -61,6 +63,7 @@ func TestParse(t *testing.T) {
 			boo: true,
 			i32: 1,
 			i64: 2,
+			u32: 3,
 			f64: 1.0,
 			dur: time.Minute,
 			set: sets.NewString("a", "b", "c"),
@@ -106,6 +109,12 @@ func TestParse(t *testing.T) {
 			"test-int64": "foo",
 		},
 		expectErr: true,
+	}, {
+		name: "uint32 error",
+		data: map[string]string{
+			"test-uint32": "foo",
+		},
+		expectErr: true,
 	}, {
 		name: "float64 error",
 		data: map[string]string{
@@ -133,6 +142,7 @@ func TestParse(t *testing.T) {
 				AsBool("test-bool", &test.conf.boo),
 				AsInt32("test-int32", &test.conf.i32),
 				AsInt64("test-int64", &test.conf.i64),
+				AsUint32("test-uint32", &test.conf.u32),
 				AsFloat64("test-float64", &test.conf.f64),
 				AsDuration("test-duration", &test.conf.dur),
 				AsStringSet("test-set", &test.conf.set),
diff --git a/controller/controller_test.go b/controller/controller_test.go
index 3f857c78d..8d8e14e72 100644
--- a/controller/controller_test.go
+++ b/controller/controller_test.go
@@ -25,15 +25,22 @@ import (
 	"time"
 
 	"github.com/google/go-cmp/cmp"
+	coordinationv1 "k8s.io/api/coordination/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
+	fakekube "k8s.io/client-go/kubernetes/fake"
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/tools/record"
 	"k8s.io/client-go/util/workqueue"
+	"knative.dev/pkg/ptr"
+	"knative.dev/pkg/system"
+	_ "knative.dev/pkg/system/testing"
 
 	. "knative.dev/pkg/controller/testing"
+	"knative.dev/pkg/leaderelection"
 	. "knative.dev/pkg/logging/testing"
+	"knative.dev/pkg/reconciler"
 	. "knative.dev/pkg/testing"
 )
 
@@ -707,7 +714,7 @@ func TestEnqueues(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			defer ClearAll()
+			t.Cleanup(ClearAll)
 			impl := NewImplWithStats(&NopReconciler{}, TestLogger(t), "Testing", &FakeStatsReporter{})
 			test.work(impl)
 
@@ -724,7 +731,7 @@ func TestEnqueues(t *testing.T) {
 }
 
 func TestEnqeueAfter(t *testing.T) {
-	defer ClearAll()
+	t.Cleanup(ClearAll)
 	impl := NewImplWithStats(&NopReconciler{}, TestLogger(t), "Testing", &FakeStatsReporter{})
 	impl.EnqueueAfter(&Resource{
 		ObjectMeta: metav1.ObjectMeta{
@@ -760,7 +767,7 @@ func TestEnqeueAfter(t *testing.T) {
 }
 
 func TestEnqeueKeyAfter(t *testing.T) {
-	defer ClearAll()
+	t.Cleanup(ClearAll)
 	impl := NewImplWithStats(&NopReconciler{}, TestLogger(t), "Testing", &FakeStatsReporter{})
 	impl.EnqueueKeyAfter(types.NamespacedName{Namespace: "waiting", Name: "for"}, 5*time.Second)
 	impl.EnqueueKeyAfter(types.NamespacedName{Namespace: "the", Name: "waterfall"}, 500*time.Millisecond)
@@ -782,22 +789,23 @@ func TestEnqeueKeyAfter(t *testing.T) {
 
 type CountingReconciler struct {
 	m     sync.Mutex
-	Count int
+	count int
 }
 
 func (cr *CountingReconciler) Reconcile(context.Context, string) error {
 	cr.m.Lock()
 	defer cr.m.Unlock()
-	cr.Count++
+	cr.count++
 	return nil
 }
 
 func TestStartAndShutdown(t *testing.T) {
-	defer ClearAll()
+	t.Cleanup(ClearAll)
 	r := &CountingReconciler{}
 	impl := NewImplWithStats(r, TestLogger(t), "Testing", &FakeStatsReporter{})
 
 	ctx, cancel := context.WithCancel(context.Background())
+	t.Cleanup(cancel)
 	doneCh := make(chan struct{})
 
 	go func() {
@@ -820,18 +828,160 @@ func TestStartAndShutdown(t *testing.T) {
 		// We expect the work to complete.
 	}
 
-	if got, want := r.Count, 0; got != want {
-		t.Errorf("Count = %v, wanted %v", got, want)
+	if got, want := r.count, 0; got != want {
+		t.Errorf("count = %v, wanted %v", got, want)
+	}
+}
+
+type countingLeaderAwareReconciler struct {
+	reconciler.LeaderAwareFuncs
+
+	m     sync.Mutex
+	count int
+}
+
+var _ reconciler.LeaderAware = (*countingLeaderAwareReconciler)(nil)
+
+func (cr *countingLeaderAwareReconciler) Reconcile(ctx context.Context, key string) error {
+	namespace, name, err := cache.SplitMetaNamespaceKey(key)
+	if err != nil {
+		return err
+	}
+
+	if cr.IsLeaderFor(types.NamespacedName{
+		Namespace: namespace,
+		Name:      name,
+	}) {
+		cr.m.Lock()
+		defer cr.m.Unlock()
+		cr.count++
+	}
+	return nil
+}
+
+func TestStartAndShutdownWithLeaderAwareNoElection(t *testing.T) {
+	t.Cleanup(ClearAll)
+	promoted := make(chan struct{})
+	r := &countingLeaderAwareReconciler{
+		LeaderAwareFuncs: reconciler.LeaderAwareFuncs{
+			PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error {
+				close(promoted)
+				return nil
+			},
+		},
+	}
+	impl := NewImplWithStats(r, TestLogger(t), "Testing", &FakeStatsReporter{})
+
+	ctx, cancel := context.WithCancel(context.Background())
+	t.Cleanup(cancel)
+	doneCh := make(chan struct{})
+
+	go func() {
+		defer close(doneCh)
+		StartAll(ctx, impl)
+	}()
+
+	select {
+	case <-promoted:
+		// We expect to be promoted immediately, since there is no
+		// ElectorBuilder attached to the context.
+	case <-doneCh:
+		t.Fatal("StartAll finished early.")
+	case <-time.After(10 * time.Second):
+		t.Error("Timed out waiting for StartAll.")
+	}
+
+	cancel()
+
+	select {
+	case <-time.After(1 * time.Second):
+		t.Error("Timed out waiting for controller to finish.")
+	case <-doneCh:
+		// We expect the work to complete.
+	}
+
+	if got, want := r.count, 0; got != want {
+		t.Errorf("reconcile count = %v, wanted %v", got, want)
+	}
+}
+
+func TestStartAndShutdownWithLeaderAwareWithLostElection(t *testing.T) {
+	t.Cleanup(ClearAll)
+	promoted := make(chan struct{})
+	r := &countingLeaderAwareReconciler{
+		LeaderAwareFuncs: reconciler.LeaderAwareFuncs{
+			PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error {
+				close(promoted)
+				return nil
+			},
+		},
+	}
+	cc := leaderelection.ComponentConfig{
+		Component:     "component",
+		LeaderElect:   true,
+		ResourceLock:  "leases",
+		LeaseDuration: 15 * time.Second,
+		RenewDeadline: 10 * time.Second,
+		RetryPeriod:   2 * time.Second,
+	}
+	kc := fakekube.NewSimpleClientset(
+		&coordinationv1.Lease{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: system.Namespace(),
+				Name:      "component.testing.00-of-01",
+			},
+			Spec: coordinationv1.LeaseSpec{
+				HolderIdentity:       ptr.String("not-us"),
+				LeaseDurationSeconds: ptr.Int32(3000),
+				AcquireTime:          &metav1.MicroTime{time.Now()},
+				RenewTime:            &metav1.MicroTime{time.Now().Add(3000 * time.Second)},
+			},
+		},
+	)
+
+	impl := NewImplWithStats(r, TestLogger(t), "Testing", &FakeStatsReporter{})
+
+	ctx, cancel := context.WithCancel(context.Background())
+	t.Cleanup(cancel)
+	ctx = leaderelection.WithStandardLeaderElectorBuilder(ctx, kc, cc)
+	doneCh := make(chan struct{})
+
+	go func() {
+		defer close(doneCh)
+		StartAll(ctx, impl)
+	}()
+
+	select {
+	case <-promoted:
+		t.Fatal("Unexpected promotion.")
+	case <-time.After(3 * time.Second):
+		// Wait for 3 seconds for good measure.
+	case <-doneCh:
+		t.Error("StartAll finished early.")
+	}
+
+	cancel()
+
+	select {
+	case <-time.After(1 * time.Second):
+		t.Error("Timed out waiting for controller to finish.")
+	case <-doneCh:
+		// We expect the work to complete.
+	}
+
+	if got, want := r.count, 0; got != want {
+		t.Errorf("reconcile count = %v, wanted %v", got, want)
 	}
 }
 
 func TestStartAndShutdownWithWork(t *testing.T) {
-	defer ClearAll()
+	t.Cleanup(ClearAll)
 	r := &CountingReconciler{}
 	reporter := &FakeStatsReporter{}
 	impl := NewImplWithStats(r, TestLogger(t), "Testing", reporter)
 
 	ctx, cancel := context.WithCancel(context.Background())
+	t.Cleanup(cancel)
 	doneCh := make(chan struct{})
 
 	impl.EnqueueKey(types.NamespacedName{Namespace: "foo", Name: "bar"})
@@ -856,11 +1006,11 @@ func TestStartAndShutdownWithWork(t *testing.T) {
 		// We expect the work to complete.
 	}
 
-	if got, want := r.Count, 1; got != want {
-		t.Errorf("Count = %v, wanted %v", got, want)
+	if got, want := r.count, 1; got != want {
+		t.Errorf("reconcile count = %v, wanted %v", got, want)
 	}
 	if got, want := impl.WorkQueue.NumRequeues(types.NamespacedName{Namespace: "foo", Name: "bar"}), 0; got != want {
-		t.Errorf("Count = %v, wanted %v", got, want)
+		t.Errorf("requeues = %v, wanted %v", got, want)
 	}
 
 	checkStats(t, reporter, 1, 0, 1, trueString)
@@ -902,12 +1052,13 @@ func (er *ErrorReconciler) Reconcile(context.Context, string) error {
 }
 
 func TestStartAndShutdownWithErroringWork(t *testing.T) {
-	defer ClearAll()
+	t.Cleanup(ClearAll)
 	r := &ErrorReconciler{}
 	reporter := &FakeStatsReporter{}
 	impl := NewImplWithStats(r, TestLogger(t), "Testing", reporter)
 
 	ctx, cancel := context.WithCancel(context.Background())
+	t.Cleanup(cancel)
 	doneCh := make(chan struct{})
 
 	impl.EnqueueKey(types.NamespacedName{Namespace: "", Name: "bar"})
@@ -956,12 +1107,13 @@ func (er *PermanentErrorReconciler) Reconcile(context.Context, string) error {
 }
 
 func TestStartAndShutdownWithPermanentErroringWork(t *testing.T) {
-	defer ClearAll()
+	t.Cleanup(ClearAll)
 	r := &PermanentErrorReconciler{}
 	reporter := &FakeStatsReporter{}
 	impl := NewImplWithStats(r, TestLogger(t), "Testing", reporter)
 
 	ctx, cancel := context.WithCancel(context.Background())
+	t.Cleanup(cancel)
 	doneCh := make(chan struct{})
 
 	impl.EnqueueKey(types.NamespacedName{Namespace: "foo", Name: "bar"})
@@ -1048,11 +1200,12 @@ func (*dummyStore) List() []interface{} {
 }
 
 func TestImplGlobalResync(t *testing.T) {
-	defer ClearAll()
+	t.Cleanup(ClearAll)
 	r := &CountingReconciler{}
 	impl := NewImplWithStats(r, TestLogger(t), "Testing", &FakeStatsReporter{})
 
 	ctx, cancel := context.WithCancel(context.Background())
+	t.Cleanup(cancel)
 	doneCh := make(chan struct{})
 
 	go func() {
@@ -1079,7 +1232,7 @@ func TestImplGlobalResync(t *testing.T) {
 		// We expect the work to complete.
 	}
 
-	if want, got := 3, r.Count; want != got {
+	if want, got := 3, r.count; want != got {
 		t.Errorf("GlobalResync: want = %v, got = %v", want, got)
 	}
 }
@@ -1319,6 +1472,7 @@ func TestRunInformersFinished(t *testing.T) {
 	}()
 
 	ctx, cancel := context.WithCancel(TestContextWithLogger(t))
+	t.Cleanup(cancel)
 
 	waitInformers, err := RunInformers(ctx.Done(), fi)
 	if err != nil {
diff --git a/leaderelection/config_test.go b/leaderelection/config_test.go
index 3d3767788..e2850c8a8 100644
--- a/leaderelection/config_test.go
+++ b/leaderelection/config_test.go
@@ -18,6 +18,8 @@ package leaderelection
 
 import (
 	"errors"
+	"fmt"
+	"strconv"
 	"testing"
 	"time"
 
@@ -30,6 +32,7 @@ import (
 func okConfig() *Config {
 	return &Config{
 		ResourceLock:      "leases",
+		Buckets:           1,
 		LeaseDuration:     15 * time.Second,
 		RenewDeadline:     10 * time.Second,
 		RetryPeriod:       2 * time.Second,
@@ -40,6 +43,7 @@ func okConfig() *Config {
 func okData() map[string]string {
 	return map[string]string{
 		"resourceLock": "leases",
+		"buckets":      "1",
 		// values in this data come from the defaults suggested in the
 		// code:
 		// https://github.com/kubernetes/client-go/blob/kubernetes-1.16.0/tools/leaderelection/leaderelection.go
@@ -72,6 +76,17 @@ func TestNewConfigMapFromData(t *testing.T) {
 			config.EnabledComponents.Insert("controller")
 			return config
 		}(),
+	}, {
+		name: "OK config - controller enabled with multiple buckets",
+		data: kmeta.UnionMaps(okData(), map[string]string{
+			"buckets": "5",
+		}),
+		expected: func() *Config {
+			config := okConfig()
+			config.EnabledComponents.Insert("controller")
+			config.Buckets = 5
+			return config
+		}(),
 	}, {
 		name: "invalid resourceLock",
 		data: kmeta.UnionMaps(okData(), map[string]string{
@@ -96,6 +111,24 @@ func TestNewConfigMapFromData(t *testing.T) {
 			"retryPeriod": "flops",
 		}),
 		err: errors.New(`failed to parse "retryPeriod": time: invalid duration flops`),
+	}, {
+		name: "invalid buckets - not an int",
+		data: kmeta.UnionMaps(okData(), map[string]string{
+			"buckets": "not-an-int",
+		}),
+		err: errors.New(`failed to parse "buckets": strconv.ParseUint: parsing "not-an-int": invalid syntax`),
+	}, {
+		name: "invalid buckets - too small",
+		data: kmeta.UnionMaps(okData(), map[string]string{
+			"buckets": "0",
+		}),
+		err: fmt.Errorf("buckets: value must be between 1 <= 0 <= %d", MaxBuckets),
+	}, {
+		name: "invalid buckets - too large",
+		data: kmeta.UnionMaps(okData(), map[string]string{
+			"buckets": strconv.Itoa(int(MaxBuckets + 1)),
+		}),
+		err: fmt.Errorf(`buckets: value must be between 1 <= %d <= %d`, MaxBuckets+1, MaxBuckets),
 	}}
 
 	for _, tc := range cases {
diff --git a/leaderelection/context_test.go b/leaderelection/context_test.go
new file mode 100644
index 000000000..5e9bc6ce0
--- /dev/null
+++ b/leaderelection/context_test.go
@@ -0,0 +1,146 @@
+// +build !race
+// TODO(https://github.com/kubernetes/kubernetes/issues/90952): Remove the above.
+
+/*
+Copyright 2020 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package leaderelection
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
+	fakekube "k8s.io/client-go/kubernetes/fake"
+	ktesting "k8s.io/client-go/testing"
+	"knative.dev/pkg/reconciler"
+	_ "knative.dev/pkg/system/testing"
+)
+
+func TestWithBuilder(t *testing.T) {
+	cc := ComponentConfig{
+		Component:     "component",
+		LeaderElect:   true,
+		Buckets:       1,
+		ResourceLock:  "leases",
+		LeaseDuration: 15 * time.Second,
+		RenewDeadline: 10 * time.Second,
+		RetryPeriod:   2 * time.Second,
+	}
+	kc := fakekube.NewSimpleClientset()
+	ctx := context.Background()
+
+	promoted := make(chan struct{})
+	demoted := make(chan struct{})
+	laf := &reconciler.LeaderAwareFuncs{
+		PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error {
+			close(promoted)
+			return nil
+		},
+		DemoteFunc: func(bkt reconciler.Bucket) {
+			close(demoted)
+		},
+	}
+	enq := func(reconciler.Bucket, types.NamespacedName) {}
+
+	created := make(chan struct{})
+	kc.PrependReactor("create", "leases",
+		func(action ktesting.Action) (bool, runtime.Object, error) {
+			close(created)
+			return false, nil, nil
+		},
+	)
+
+	updated := make(chan struct{})
+	kc.PrependReactor("update", "leases",
+		func(action ktesting.Action) (bool, runtime.Object, error) {
+			// Only close update once.
+			select {
+			case <-updated:
+			default:
+				close(updated)
+			}
+			return false, nil, nil
+		},
+	)
+
+	if HasLeaderElection(ctx) {
+		t.Error("HasLeaderElection() = true, wanted false")
+	}
+	if le, err := BuildElector(ctx, laf, "name", enq); err != nil {
+		t.Errorf("BuildElector() = %v, wanted an unopposedElector", err)
+	} else if _, ok := le.(*unopposedElector); !ok {
+		t.Errorf("BuildElector() = %T, wanted an unopposedElector", le)
+	}
+
+	ctx = WithStandardLeaderElectorBuilder(ctx, kc, cc)
+	if !HasLeaderElection(ctx) {
+		t.Error("HasLeaderElection() = false, wanted true")
+	}
+
+	le, err := BuildElector(ctx, laf, "name", enq)
+	if err != nil {
+		t.Errorf("BuildElector() = %v", err)
+	}
+
+	// We shouldn't see leases until we Run the elector.
+	select {
+	case <-promoted:
+		t.Error("Got promoted, want no actions.")
+	case <-demoted:
+		t.Error("Got demoted, want no actions.")
+	case <-created:
+		t.Error("Got created, want no actions.")
+	case <-updated:
+		t.Error("Got updated, want no actions.")
+	default:
+	}
+
+	ctx, cancel := context.WithCancel(context.Background())
+	t.Cleanup(cancel)
+	go le.Run(ctx)
+
+	select {
+	case <-created:
+		// We expect the lease to be created.
+	case <-time.After(1 * time.Second):
+		t.Fatal("Timed out waiting for lease creation.")
+	}
+	select {
+	case <-promoted:
+		// We expect to have been promoted.
+	case <-time.After(1 * time.Second):
+		t.Fatal("Timed out waiting for promotion.")
+	}
+
+	// Cancelling the context should case us to give up leadership.
+	cancel()
+
+	select {
+	case <-updated:
+		// We expect the lease to be updated.
+	case <-time.After(1 * time.Second):
+		t.Fatal("Timed out waiting for lease update.")
+	}
+	select {
+	case <-demoted:
+		// We expect to have been demoted.
+	case <-time.After(1 * time.Second):
+		t.Fatal("Timed out waiting for demotion.")
+	}
+}
diff --git a/reconciler/leader_test.go b/reconciler/leader_test.go
new file mode 100644
index 000000000..cb57b8c7c
--- /dev/null
+++ b/reconciler/leader_test.go
@@ -0,0 +1,82 @@
+/*
+Copyright 2020 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package reconciler
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"k8s.io/apimachinery/pkg/types"
+)
+
+func TestLeaderAwareFuncs(t *testing.T) {
+	laf := LeaderAwareFuncs{}
+	wantBkt := UniversalBucket()
+	wantKey := types.NamespacedName{
+		Namespace: "foo",
+		Name:      "bar",
+	}
+	called := false
+	wantFunc := func(gotBkt Bucket, gotKey types.NamespacedName) {
+		called = true
+		if !cmp.Equal(gotKey, wantKey) {
+			t.Errorf("key (-want, +got) = %s", cmp.Diff(wantKey, gotKey))
+		}
+		if !cmp.Equal(gotBkt, wantBkt) {
+			t.Errorf("bucket (-want, +got) = %s", cmp.Diff(wantBkt, gotBkt))
+		}
+	}
+
+	laf.PromoteFunc = func(bkt Bucket, gotFunc func(Bucket, types.NamespacedName)) error {
+		gotFunc(bkt, wantKey)
+		if !called {
+			t.Error("gotFunc didn't call wantFunc!")
+		}
+
+		// IsLeaderFor takes the bucket's lock, so make sure that the callback
+		// we provide is not called while the lock is still held by calling a
+		// function that we know takes the lock.
+		if !laf.IsLeaderFor(wantKey) {
+			t.Error("IsLeaderFor() = false, wanted true")
+		}
+		return nil
+	}
+	laf.DemoteFunc = func(bkt Bucket) {
+		// Check that we're not called while the lock is held,
+		// and that we are no longer leader.
+		if laf.IsLeaderFor(wantKey) {
+			t.Error("IsLeaderFor() = true, wanted false")
+		}
+	}
+
+	// We don't start as leader.
+	if laf.IsLeaderFor(wantKey) {
+		t.Error("IsLeaderFor() = true, wanted false")
+	}
+
+	// After Promote we are leader.
+	laf.Promote(wantBkt, wantFunc)
+	if !laf.IsLeaderFor(wantKey) {
+		t.Error("IsLeaderFor() = false, wanted true")
+	}
+
+	// After Demote we are not leader.
+	laf.Demote(wantBkt)
+	if laf.IsLeaderFor(wantKey) {
+		t.Error("IsLeaderFor() = true, wanted false")
+	}
+}
diff --git a/test/presubmit-tests.sh b/test/presubmit-tests.sh
index 261599f71..af8199dd0 100755
--- a/test/presubmit-tests.sh
+++ b/test/presubmit-tests.sh
@@ -37,4 +37,16 @@ function pre_build_tests() {
   return 0
 }
 
+# Run the unit tests with an additional flag '-mod=vendor' to avoid
+# downloading the deps in unit tests CI job
+function unit_tests() {
+  # Run the default way.
+  default_unit_test_runner || failed=1
+
+  # Run unit testing select packages without race detection,
+  # so that they may use: // +build !race
+  report_go_test ./leaderelection || failed=1
+
+}
+
 main $@