Repository: kubernetes-sigs/kubefed
Pull request #1361 (2021-02-22 05:01:57)

kubefedcluster use cadata in kubeconfig file
<!--  Thanks for sending a pull request!  Here are some tips for you:
1. If this is your first time, read our contributor guidelines https://git.k8s.io/community/contributors/guide#your-first-contribution and developer guide https://git.k8s.io/community/contributors/devel/development.md#development-guide
2. If you want *faster* PR reviews, read how: https://git.k8s.io/community/contributors/guide/pull-requests.md#best-practices-for-faster-reviews
3. Add an entry to CHANGELOG.md if the PR represents a user-visible change.
4. If the PR is unfinished, see how to mark it: https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests
-->

**What this PR does / why we need it**:

Kubefedcluster's cadata build from member cluster's serviceaccount, it work well when kubefed directlly conntect to member cluster. In my case, kubefed connect to a proxyserver (it proxy all apiserver's traffic), and the proxyserver has a independent cabundle.

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Fixes #

**Special notes for your reviewer**:



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f7100397..3d4eca5d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,5 @@
 # Unreleased
 
-# v0.6.1
-- [#1346](https://github.com/kubernetes-sigs/kubefed/pull/1346)
-  fix: upgrade path broken from older versions than v0.6.0.
-- [#1347](https://github.com/kubernetes-sigs/kubefed/pull/1347)
-  chore: retain healthCheckNodePort for service when updating.
-- [#1334](https://github.com/kubernetes-sigs/kubefed/pull/1334)
-  chore: exec enable cmd ignore some apiservices errors.
 # v0.6.0
 - [#1328](https://github.com/kubernetes-sigs/kubefed/pull/1328)
   docs: optimize chart readme. 
diff --git a/README.md b/README.md
index 59b309f3..52708ffb 100644
--- a/README.md
+++ b/README.md
@@ -57,6 +57,7 @@ higher-level APIs:
 | [Push propagation of arbitrary types to remote clusters](https://github.com/kubernetes-sigs/kubefed/blob/master/docs/userguide.md#verify-your-deployment-is-working) | Alpha | PushReconciler | true |
 | [CLI utility (`kubefedctl`)](https://github.com/kubernetes-sigs/kubefed/blob/master/docs/userguide.md#kubefedctl-cli) | Alpha | | |
 | [Generate KubeFed APIs without writing code](https://github.com/kubernetes-sigs/kubefed/blob/master/docs/userguide.md#enabling-federation-of-an-api-type) | Alpha | | |
+| [Multicluster Service DNS via `external-dns`](https://github.com/kubernetes-sigs/kubefed/blob/master/docs/servicedns-with-externaldns.md) | Alpha | CrossClusterServiceDiscovery | false |
 | [Multicluster Ingress DNS via `external-dns`](https://github.com/kubernetes-sigs/kubefed/blob/master/docs/ingressdns-with-externaldns.md) | Alpha | FederatedIngress | false |
 | [Replica Scheduling Preferences](https://github.com/kubernetes-sigs/kubefed/blob/master/docs/userguide.md#replicaschedulingpreference) | Alpha | SchedulerPreferences | true |
 
diff --git a/charts/index.yaml b/charts/index.yaml
index a44b495c..78fd05c6 100644
--- a/charts/index.yaml
+++ b/charts/index.yaml
@@ -1,19 +1,6 @@
 apiVersion: v1
 entries:
   kubefed:
-  - apiVersion: v2
-    created: "2021-01-25T16:48:21.652921408+01:00"
-    dependencies:
-    - condition: controllermanager.enabled
-      name: controllermanager
-      repository: https://localhost/
-      version: 0.6.1
-    description: KubeFed helm chart
-    digest: 432ea46335f1f7d836e3c9d0bab6fb7ae71eeb54d0f1cb99c65c9ea3979745d6
-    name: kubefed
-    urls:
-    - https://github.com/kubernetes-sigs/kubefed/releases/download/v0.6.1/kubefed-0.6.1.tgz
-    version: 0.6.1
   - apiVersion: v2
     created: "2020-12-17T16:19:29.868524712+01:00"
     dependencies:
@@ -156,4 +143,4 @@ entries:
     urls:
     - https://github.com/kubernetes-sigs/kubefed/releases/download/v0.1.0-rc1/kubefed-0.1.0-rc1.tgz
     version: 0.1.0-rc1
-generated: "2021-01-25T16:48:21.651203778+01:00"
+generated: "2020-12-17T16:19:29.866175179+01:00"
diff --git a/charts/kubefed/README.md b/charts/kubefed/README.md
index 5ceef30e..503d9b37 100644
--- a/charts/kubefed/README.md
+++ b/charts/kubefed/README.md
@@ -98,7 +98,7 @@ chart and their default values.
 | Parameter                             | Description                                                                                                                                                                                 | Default                         |
 | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------|
 | controllermanager.enabled             | Specifies whether to enable the controller manager in KubeFed.                                                                                                                              | true                            |
-| controllermanager.imagePullSecrets    | Image pull secrets.                                                                                                                                                                         | []
+| controllermanager.imagePullSecrets    | Image pull secrets.                                                                                                                                                                         | [] 
 | controllermanager.commonTolerations   | Tolerations for all the pods.                                                                                                                                                               | []                              |
 | controllermanager.commonNodeSelector     | Node selector for all the pods.                                                                                                                                                          | {}                              |
 | controllermanager.controller.repository        | Repo of the KubeFed image.                                                                                                                                                         | quay.io/kubernetes-multicluster |
@@ -113,6 +113,7 @@ chart and their default values.
 | controllermanager.featureGates.PushReconciler               | Push reconciler feature.                                                                                                                                              | true                            |
 | controllermanager.featureGates.RawResourceStatusCollection               | Raw collection of resource status on target clusters feature.                                                                                                                                              | false                            |
 | controllermanager.featureGates.SchedulerPreferences         | Scheduler preferences feature.                                                                                                                                        | true                            |
+| controllermanager.featureGates.CrossClusterServiceDiscovery | Cross cluster service discovery feature.                                                                                                                              | false                           |
 | controllermanager.featureGates.FederatedIngress             | Federated ingress feature.                                                                                                                                            | false                           |
 | controllermanager.clusterAvailableDelay   | Time to wait before reconciling on a healthy cluster.                                                                                                                                   | 20s                             |
 | controllermanager.clusterUnavailableDelay | Time to wait before giving up on an unhealthy cluster.                                                                                                                                  | 60s                             |
diff --git a/charts/kubefed/charts/controllermanager/crds/crds.yaml b/charts/kubefed/charts/controllermanager/crds/crds.yaml
index b271a491..df3c48a0 100644
--- a/charts/kubefed/charts/controllermanager/crds/crds.yaml
+++ b/charts/kubefed/charts/controllermanager/crds/crds.yaml
@@ -114,83 +114,6 @@ spec:
                 status:
                   description: ServiceStatus represents the current status of a service.
                   properties:
-                    conditions:
-                      description: Current service state
-                      items:
-                        description: "Condition contains details for one aspect of
-                          the current state of this API Resource. --- This struct
-                          is intended for direct use as an array at the field path
-                          .status.conditions.  For example, type FooStatus struct{
-                          \    // Represents the observations of a foo's current state.
-                          \    // Known .status.conditions.type are: \"Available\",
-                          \"Progressing\", and \"Degraded\"     // +patchMergeKey=type
-                          \    // +patchStrategy=merge     // +listType=map     //
-                          +listMapKey=type     Conditions []metav1.Condition `json:\"conditions,omitempty\"
-                          patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`
-                          \n     // other fields }"
-                        properties:
-                          lastTransitionTime:
-                            description: lastTransitionTime is the last time the condition
-                              transitioned from one status to another. This should
-                              be when the underlying condition changed.  If that is
-                              not known, then using the time when the API field changed
-                              is acceptable.
-                            format: date-time
-                            type: string
-                          message:
-                            description: message is a human readable message indicating
-                              details about the transition. This may be an empty string.
-                            maxLength: 32768
-                            type: string
-                          observedGeneration:
-                            description: observedGeneration represents the .metadata.generation
-                              that the condition was set based upon. For instance,
-                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration
-                              is 9, the condition is out of date with respect to the
-                              current state of the instance.
-                            format: int64
-                            minimum: 0
-                            type: integer
-                          reason:
-                            description: reason contains a programmatic identifier
-                              indicating the reason for the condition's last transition.
-                              Producers of specific condition types may define expected
-                              values and meanings for this field, and whether the
-                              values are considered a guaranteed API. The value should
-                              be a CamelCase string. This field may not be empty.
-                            maxLength: 1024
-                            minLength: 1
-                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
-                            type: string
-                          status:
-                            description: status of the condition, one of True, False,
-                              Unknown.
-                            enum:
-                            - "True"
-                            - "False"
-                            - Unknown
-                            type: string
-                          type:
-                            description: type of condition in CamelCase or in foo.example.com/CamelCase.
-                              --- Many .condition.type values are consistent across
-                              resources like Available, but because arbitrary conditions
-                              can be useful (see .node.status.conditions), the ability
-                              to deconflict is important. The regex it matches is
-                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)
-                            maxLength: 316
-                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
-                            type: string
-                        required:
-                        - lastTransitionTime
-                        - message
-                        - reason
-                        - status
-                        - type
-                        type: object
-                      type: array
-                      x-kubernetes-list-map-keys:
-                      - type
-                      x-kubernetes-list-type: map
                     loadBalancer:
                       description: LoadBalancer contains the current status of the
                         load-balancer, if one is present.
@@ -212,40 +135,6 @@ spec:
                                 description: IP is set for load-balancer ingress points
                                   that are IP based (typically GCE or OpenStack load-balancers)
                                 type: string
-                              ports:
-                                description: Ports is a list of records of service
-                                  ports If used, every port defined in the service
-                                  should have an entry in it
-                                items:
-                                  properties:
-                                    error:
-                                      description: 'Error is to record the problem
-                                        with the service port The format of the error
-                                        shall comply with the following rules: - built-in
-                                        error values shall be specified in this file
-                                        and those shall use   CamelCase names - cloud
-                                        provider specific error values must have names
-                                        that comply with the   format foo.example.com/CamelCase.
-                                        --- The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)'
-                                      maxLength: 316
-                                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
-                                      type: string
-                                    port:
-                                      description: Port is the port number of the
-                                        service port of which status is recorded here
-                                      format: int32
-                                      type: integer
-                                    protocol:
-                                      description: 'Protocol is the protocol of the
-                                        service port of which status is recorded here
-                                        The supported values are: "TCP", "UDP", "SCTP"'
-                                      type: string
-                                  required:
-                                  - port
-                                  - protocol
-                                  type: object
-                                type: array
-                                x-kubernetes-list-type: atomic
                             type: object
                           type: array
                       type: object
@@ -1005,40 +894,6 @@ spec:
                                 description: IP is set for load-balancer ingress points
                                   that are IP based (typically GCE or OpenStack load-balancers)
                                 type: string
-                              ports:
-                                description: Ports is a list of records of service
-                                  ports If used, every port defined in the service
-                                  should have an entry in it
-                                items:
-                                  properties:
-                                    error:
-                                      description: 'Error is to record the problem
-                                        with the service port The format of the error
-                                        shall comply with the following rules: - built-in
-                                        error values shall be specified in this file
-                                        and those shall use   CamelCase names - cloud
-                                        provider specific error values must have names
-                                        that comply with the   format foo.example.com/CamelCase.
-                                        --- The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)'
-                                      maxLength: 316
-                                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
-                                      type: string
-                                    port:
-                                      description: Port is the port number of the
-                                        service port of which status is recorded here
-                                      format: int32
-                                      type: integer
-                                    protocol:
-                                      description: 'Protocol is the protocol of the
-                                        service port of which status is recorded here
-                                        The supported values are: "TCP", "UDP", "SCTP"'
-                                      type: string
-                                  required:
-                                  - port
-                                  - protocol
-                                  type: object
-                                type: array
-                                x-kubernetes-list-type: atomic
                             type: object
                           type: array
                       type: object
@@ -1160,40 +1015,6 @@ spec:
                                 description: IP is set for load-balancer ingress points
                                   that are IP based (typically GCE or OpenStack load-balancers)
                                 type: string
-                              ports:
-                                description: Ports is a list of records of service
-                                  ports If used, every port defined in the service
-                                  should have an entry in it
-                                items:
-                                  properties:
-                                    error:
-                                      description: 'Error is to record the problem
-                                        with the service port The format of the error
-                                        shall comply with the following rules: - built-in
-                                        error values shall be specified in this file
-                                        and those shall use   CamelCase names - cloud
-                                        provider specific error values must have names
-                                        that comply with the   format foo.example.com/CamelCase.
-                                        --- The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)'
-                                      maxLength: 316
-                                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
-                                      type: string
-                                    port:
-                                      description: Port is the port number of the
-                                        service port of which status is recorded here
-                                      format: int32
-                                      type: integer
-                                    protocol:
-                                      description: 'Protocol is the protocol of the
-                                        service port of which status is recorded here
-                                        The supported values are: "TCP", "UDP", "SCTP"'
-                                      type: string
-                                  required:
-                                  - port
-                                  - protocol
-                                  type: object
-                                type: array
-                                x-kubernetes-list-type: atomic
                             type: object
                           type: array
                       type: object
diff --git a/charts/kubefed/charts/controllermanager/templates/kubefedconfig.yaml b/charts/kubefed/charts/controllermanager/templates/kubefedconfig.yaml
index ad0c63ee..293175cf 100644
--- a/charts/kubefed/charts/controllermanager/templates/kubefedconfig.yaml
+++ b/charts/kubefed/charts/controllermanager/templates/kubefedconfig.yaml
@@ -24,11 +24,12 @@ spec:
 {{- if .Values.featureGates }}
   - name: PushReconciler
     configuration: {{ .Values.featureGates.PushReconciler | default "Enabled" | quote }}
+  - name: RawResourceStatusCollection
+    configuration: {{ .Values.featureGates.RawResourceStatusCollection | default "Disabled" | quote }}
   - name: SchedulerPreferences
     configuration: {{ .Values.featureGates.SchedulerPreferences | default "Enabled" | quote }}
+  - name: CrossClusterServiceDiscovery
+    configuration: {{ .Values.featureGates.CrossClusterServiceDiscovery | default "Disabled" | quote }}
   - name: FederatedIngress
     configuration: {{ .Values.featureGates.FederatedIngress | default "Disabled" | quote }}
-  # NOTE: Commented feature gate to fix https://github.com/kubernetes-sigs/kubefed/issues/1333
-  #- name: RawResourceStatusCollection
-  #  configuration: {{ .Values.featureGates.RawResourceStatusCollection | default "Disabled" | quote }}
 {{- end }}
diff --git a/charts/kubefed/charts/controllermanager/templates/post-install-job.yaml b/charts/kubefed/charts/controllermanager/templates/post-install-job.yaml
deleted file mode 100644
index 194cecf5..00000000
--- a/charts/kubefed/charts/controllermanager/templates/post-install-job.yaml
+++ /dev/null
@@ -1,103 +0,0 @@
----
-kind: ConfigMap
-apiVersion: v1
-metadata:
-  name: "{{ .Release.Name }}-kubefed-config-hook"
-  namespace: "{{ .Release.Namespace }}"
-  annotations:
-    "helm.sh/hook": post-install, post-upgrade
-    "helm.sh/hook-weight": "-5"
-    "helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation
-data:
-  setup.sh: |-
-    #!/bin/bash
-    set -euo pipefail
-
-    kubectl patch kubefedconfig -n {{ .Release.Namespace }} kubefed --type='json' -p='[{"op": "add", "path": "/spec/featureGates", "value":[{"configuration": {{ .Values.featureGates.PushReconciler | default "Enabled" | quote }},"name":"PushReconciler"},{"configuration": {{ .Values.featureGates.RawResourceStatusCollection | default "Disabled" | quote }},"name":"RawResourceStatusCollection"},{"configuration": {{ .Values.featureGates.FederatedIngress | default "Disabled" | quote }},"name":"FederatedIngress"},{"configuration": {{ .Values.featureGates.SchedulerPreferences | default "Enabled" | quote }},"name":"SchedulerPreferences"}]}]'
-
-    echo "Kubefedconfig patched successfully!"
-
-    kubectl rollout restart deployment/kubefed-controller-manager -n {{ .Release.Namespace }}
----
-apiVersion: batch/v1
-kind: Job
-metadata:
-  name: "{{ .Release.Name }}-{{ randAlphaNum 10 | lower }}"
-  namespace: "{{ .Release.Namespace }}"
-  labels:
-    app.kubernetes.io/managed-by: {{ .Release.Service | quote }}
-    app.kubernetes.io/instance: {{ .Release.Name | quote }}
-    app.kubernetes.io/version: {{ .Chart.AppVersion }}
-    helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
-  annotations:
-    "helm.sh/hook": post-install,post-upgrade
-    "helm.sh/hook-weight": "-4"
-    "helm.sh/hook-delete-policy": hook-succeeded,hook-failed
-spec:
-  template:
-    metadata:
-      name: "{{ .Release.Name }}"
-      labels:
-        app.kubernetes.io/managed-by: {{ .Release.Service | quote }}
-        app.kubernetes.io/instance: {{ .Release.Name | quote }}
-        helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
-    spec:
-      restartPolicy: Never
-      serviceAccountName: kubefed-config-hook
-      automountServiceAccountToken: true
-      containers:
-      - name: post-install-job
-        image: "bitnami/kubectl:1.17.16"
-        command: ["/bin/bash"]
-        args: ["/opt/scripts/setup.sh"]
-        volumeMounts:
-        - name: "scripts"
-          mountPath: "/opt/scripts"
-      volumes:
-      - name: "scripts"
-        configMap:
-          name: "{{ .Release.Name }}-kubefed-config-hook"
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: Role
-metadata:
-  name: kubefed-config-hook
-  namespace: {{ .Release.Namespace }}
-  annotations:
-    "helm.sh/hook": post-install, post-upgrade
-    "helm.sh/hook-weight": "-5"
-    "helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation
-rules:
-  - apiGroups: ["apps"]
-    resources: ["deployments"]
-    verbs: ["patch","get"]
-  - apiGroups: ["core.kubefed.io"]
-    resources: ["kubefedconfigs"]
-    verbs: ["patch","get"]
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: RoleBinding
-metadata:
-  name: kubefed-config-hook
-  namespace: {{ .Release.Namespace }}
-  annotations:
-    "helm.sh/hook": post-install, post-upgrade
-    "helm.sh/hook-weight": "-5"
-    "helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: Role
-  name: kubefed-config-hook
-subjects:
-  - kind: ServiceAccount
-    name: kubefed-config-hook
----
-apiVersion: v1
-kind: ServiceAccount
-metadata:
-  name: kubefed-config-hook
-  namespace: {{ .Release.Namespace }}
-  annotations:
-    "helm.sh/hook": post-install, post-upgrade
-    "helm.sh/hook-weight": "-5"
-    "helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation
diff --git a/charts/kubefed/values.yaml b/charts/kubefed/values.yaml
index 17195a86..a6cb46e7 100644
--- a/charts/kubefed/values.yaml
+++ b/charts/kubefed/values.yaml
@@ -23,6 +23,7 @@ controllermanager:
   featureGates:
     PushReconciler:
     SchedulerPreferences:
+    CrossClusterServiceDiscovery:
     FederatedIngress:
     RawResourceStatusCollection:
 
diff --git a/cmd/controller-manager/app/controller-manager.go b/cmd/controller-manager/app/controller-manager.go
index 3e794deb..2ee8ed58 100644
--- a/cmd/controller-manager/app/controller-manager.go
+++ b/cmd/controller-manager/app/controller-manager.go
@@ -40,7 +40,7 @@ import (
 	"k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/clientcmd"
 	"k8s.io/component-base/logs"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 	"sigs.k8s.io/controller-runtime/pkg/metrics"
 
 	"sigs.k8s.io/kubefed/cmd/controller-manager/app/leaderelection"
@@ -53,6 +53,7 @@ import (
 	"sigs.k8s.io/kubefed/pkg/controller/ingressdns"
 	"sigs.k8s.io/kubefed/pkg/controller/kubefedcluster"
 	"sigs.k8s.io/kubefed/pkg/controller/schedulingmanager"
+	"sigs.k8s.io/kubefed/pkg/controller/servicedns"
 	"sigs.k8s.io/kubefed/pkg/controller/util"
 	"sigs.k8s.io/kubefed/pkg/features"
 	kubefedmetrics "sigs.k8s.io/kubefed/pkg/metrics"
@@ -187,6 +188,18 @@ func startControllers(opts *options.Options, stopChan <-chan struct{}) {
 		}
 	}
 
+	if utilfeature.DefaultFeatureGate.Enabled(features.CrossClusterServiceDiscovery) {
+		klog.Warningf("The feature '%s' is deprecated and will be removed in a future release.", features.CrossClusterServiceDiscovery)
+
+		if err := servicedns.StartController(opts.Config, stopChan); err != nil {
+			klog.Fatalf("Error starting dns controller: %v", err)
+		}
+
+		if err := dnsendpoint.StartServiceDNSEndpointController(opts.Config, stopChan); err != nil {
+			klog.Fatalf("Error starting dns endpoint controller: %v", err)
+		}
+	}
+
 	if utilfeature.DefaultFeatureGate.Enabled(features.FederatedIngress) {
 		klog.Warningf("The feature '%s' is deprecated and will be removed in a future release.", features.FederatedIngress)
 
diff --git a/cmd/controller-manager/app/leaderelection/leaderelection.go b/cmd/controller-manager/app/leaderelection/leaderelection.go
index 2e3db499..c607faa3 100644
--- a/cmd/controller-manager/app/leaderelection/leaderelection.go
+++ b/cmd/controller-manager/app/leaderelection/leaderelection.go
@@ -29,7 +29,7 @@ import (
 	"k8s.io/client-go/tools/leaderelection"
 	"k8s.io/client-go/tools/leaderelection/resourcelock"
 	"k8s.io/client-go/tools/record"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	"sigs.k8s.io/kubefed/cmd/controller-manager/app/options"
 )
diff --git a/cmd/hyperfed/main.go b/cmd/hyperfed/main.go
index f34dad5c..d864e48c 100644
--- a/cmd/hyperfed/main.go
+++ b/cmd/hyperfed/main.go
@@ -35,7 +35,7 @@ import (
 	_ "k8s.io/client-go/plugin/pkg/client/auth" // Load all client auth plugins for GCP, Azure, Openstack, etc
 	utilflag "k8s.io/component-base/cli/flag"
 	"k8s.io/component-base/logs"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	ctrlapp "sigs.k8s.io/kubefed/cmd/controller-manager/app"
 	webhookapp "sigs.k8s.io/kubefed/cmd/webhook/app"
diff --git a/cmd/webhook/app/webhook.go b/cmd/webhook/app/webhook.go
index 6dfbe387..065d0fb9 100644
--- a/cmd/webhook/app/webhook.go
+++ b/cmd/webhook/app/webhook.go
@@ -9,7 +9,7 @@ import (
 	"github.com/spf13/cobra"
 	"k8s.io/client-go/tools/clientcmd"
 	"k8s.io/component-base/logs"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 	"sigs.k8s.io/controller-runtime/pkg/healthz"
 	"sigs.k8s.io/controller-runtime/pkg/manager"
 	"sigs.k8s.io/controller-runtime/pkg/manager/signals"
diff --git a/config/kubefedconfig.yaml b/config/kubefedconfig.yaml
index 8c54cfbd..3e0142f5 100644
--- a/config/kubefedconfig.yaml
+++ b/config/kubefedconfig.yaml
@@ -20,6 +20,8 @@ spec:
     configuration: "Enabled"
   - name: SchedulerPreferences
     configuration: "Enabled"
+  - name: CrossClusterServiceDiscovery
+    configuration: "Enabled"
   - name: FederatedIngress
     configuration: "Enabled"
   clusterHealthCheck:
diff --git a/docs/cluster-registration.md b/docs/cluster-registration.md
index f63a3076..52e41859 100644
--- a/docs/cluster-registration.md
+++ b/docs/cluster-registration.md
@@ -25,6 +25,7 @@ Repeat this step to join any additional clusters.
 
 **NOTE:** `cluster-context` will default to use the joining cluster name if not
 specified.
+**NOTE:** Before the [PR](https://github.com/kubernetes-sigs/kubefed/pull/1361), `kubefed` automatically fetches apiserver's `certificate-authority-data` from member cluster, after that kubefed will use `certificate-authority-data` in joining cluster's kubeconfig file.
 
 # Checking status of joined clusters
 
diff --git a/docs/ingress-service-dns-with-coredns.md b/docs/ingress-service-dns-with-coredns.md
index 6fff5803..4450d9e2 100644
--- a/docs/ingress-service-dns-with-coredns.md
+++ b/docs/ingress-service-dns-with-coredns.md
@@ -23,7 +23,7 @@ This tutorial describes how to set up a KubeFed cluster DNS with [ExternalDNS](h
 - Install [metallb](https://github.com/google/metallb) for your `kind` clusters to enable LoadBalancer Service
 
 You can use either Loadbalancer Service or Ingress resource or both in your environment, this tutorial includes guidance for both Loadbalancer Service and Ingress resource.
-For related conceptions of Multi-cluster Ingress and Service, you can refer to [ingressdns-with-externaldns.md](https://github.com/kubernetes-sigs/kubefed/blob/master/docs/ingressdns-with-externaldns.md) and [servicedns-with-externaldns.md](https://github.com/kubernetes-sigs/kubefed/blob/master/docs/servicedns-with-externaldns.md).
+For related conceptions of Muilti-cluster Ingress and Service, you can refer to [ingressdns-with-externaldns.md](https://github.com/kubernetes-sigs/kubefed/blob/master/docs/ingressdns-with-externaldns.md) and [servicedns-with-externaldns.md](https://github.com/kubernetes-sigs/kubefed/blob/master/docs/servicedns-with-externaldns.md).
 
 ## Creating KubeFed cluster
 
diff --git a/docs/userguide.md b/docs/userguide.md
index 7323637c..60e66eec 100644
--- a/docs/userguide.md
+++ b/docs/userguide.md
@@ -45,6 +45,7 @@
     - [ServiceAccount](#serviceaccount)
   - [Higher order behaviour](#higher-order-behaviour)
     - [Multi-Cluster Ingress DNS](#multi-cluster-ingress-dns)
+    - [Multi-Cluster Service DNS](#multi-cluster-service-dns)
     - [ReplicaSchedulingPreference](#replicaschedulingpreference)
       - [Distribute total replicas evenly in all available clusters](#distribute-total-replicas-evenly-in-all-available-clusters)
       - [Distribute total replicas in weighted proportions](#distribute-total-replicas-in-weighted-proportions)
@@ -110,7 +111,7 @@ See the [Cluster Registration documentation](./cluster-registration.md) for more
 You can enable federation of any Kubernetes API type (including CRDs) by using the
 `kubefedctl` command as follows.
 
-**NOTE:** Federation of a CRD requires the CRD Kubernetes type (`customresourcedefinitions`) to be enabled, to then
+**NOTE:** Federation of a CRD requires the CRD Kubernetes type (`customresourcedefinitions`) to be enabled, to then 
 federate any custom CRD on the member clusters. If the CRD Kubernetes type is not enabled and its Federated equivalent CRD is not federated, the propagation to that cluster will fail.
 
 ```bash
@@ -948,6 +949,14 @@ different DNS provider to learn more.
 - [Multi-Cluster Ingress DNS with ExternalDNS Guide for Google Cloud DNS](./ingressdns-with-externaldns.md)
 - [Multi-Cluster Ingress DNS with ExternalDNS Guide for CoreDNS in minikube](./ingress-service-dns-with-coredns.md)
 
+### Multi-Cluster Service DNS
+
+Multi-Cluster Service DNS provides the ability to programmatically manage DNS resource records of Service objects
+through [ExternalDNS](https://github.com/kubernetes-incubator/external-dns) integration. Review the guides below for
+different DNS provider to learn more.
+- [Multi-Cluster Service DNS with ExternalDNS Guide for Google Cloud DNS](./servicedns-with-externaldns.md)
+- [Multi-Cluster Service DNS with ExternalDNS Guide for CoreDNS in minikube](./ingress-service-dns-with-coredns.md)
+
 ### ReplicaSchedulingPreference
 
 ReplicaSchedulingPreference provides an automated mechanism of distributing
diff --git a/example/config/kubefedconfig.yaml b/example/config/kubefedconfig.yaml
index 3b345f16..f3efbc4c 100644
--- a/example/config/kubefedconfig.yaml
+++ b/example/config/kubefedconfig.yaml
@@ -21,5 +21,7 @@ spec:
     configuration: "Enabled"
   - name: SchedulerPreferences
     configuration: "Enabled"
+  - name: CrossClusterServiceDiscovery
+    configuration: "Enabled"
   - name: FederatedIngress
     configuration: "Enabled"
diff --git a/go.mod b/go.mod
index 029155cb..0a3b464d 100644
--- a/go.mod
+++ b/go.mod
@@ -17,16 +17,17 @@ require (
 	github.com/spf13/pflag v1.0.5
 	github.com/stretchr/testify v1.6.1
 	go.uber.org/zap v1.16.0 // indirect
-	k8s.io/api v0.20.2
-	k8s.io/apiextensions-apiserver v0.20.2
-	k8s.io/apimachinery v0.20.2
-	k8s.io/apiserver v0.20.2
-	k8s.io/client-go v0.20.2
-	k8s.io/component-base v0.20.2
-	k8s.io/klog/v2 v2.4.0
-	k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd
-	k8s.io/kubectl v0.20.2
-	k8s.io/utils v0.0.0-20201110183641-67b214c5f920
+	golang.org/x/text v0.3.4 // indirect
+	k8s.io/api v0.19.3
+	k8s.io/apiextensions-apiserver v0.19.3
+	k8s.io/apimachinery v0.19.3
+	k8s.io/apiserver v0.19.3
+	k8s.io/client-go v0.19.3
+	k8s.io/component-base v0.19.3
+	k8s.io/klog v1.0.0
+	k8s.io/kube-openapi v0.0.0-20200923155610-8b5066479488
+	k8s.io/kubectl v0.19.3
+	k8s.io/utils v0.0.0-20201005171033-6301aaf42dc7
 	sigs.k8s.io/controller-runtime v0.6.3
 	sigs.k8s.io/yaml v1.2.0
 )
diff --git a/go.sum b/go.sum
index fb0e550e..da24b91f 100644
--- a/go.sum
+++ b/go.sum
@@ -6,54 +6,36 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A
 cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
 cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
 cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
-cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
-cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
-cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
-cloud.google.com/go v0.54.0 h1:3ithwDMr7/3vpAMXiH+ZQnYbuIsh+OPhUPMFC9enmn0=
-cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.51.0 h1:PvKAVQWCtlGUSlZkGW3QLelKaWq7KYv/MW1EboG8bfM=
+cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
-cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
-cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
 cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
-cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
-cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
-cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
-cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
-github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
-github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
 github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs=
 github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
-github.com/Azure/go-autorest/autorest v0.11.1 h1:eVvIXUKiTgv++6YnWb42DUA1YL7qDugnKP0HljexdnQ=
-github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
+github.com/Azure/go-autorest/autorest v0.9.6 h1:5YWtOnckcudzIw8lPPBcWOnmIFWMtHci1ZWAZulMSx0=
+github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630=
 github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU=
 github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
-github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
-github.com/Azure/go-autorest/autorest/adal v0.9.5 h1:Y3bBUV4rTuxenJJs41HU3qmqsb+auo+a3Lz+PlJPpL0=
-github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
+github.com/Azure/go-autorest/autorest/adal v0.8.2 h1:O1X4oexUxnZCaEUGsvMnr8ZGj8HI37tNezwY4npRqA0=
+github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
 github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM=
 github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
-github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
-github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
+github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM=
+github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
 github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
 github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0=
 github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
-github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
-github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk=
-github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
+github.com/Azure/go-autorest/autorest/mocks v0.3.0 h1:qJumjCaCudz+OcqE9/XtEPfvtOjOmKaui4EOpFI6zZc=
+github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=
 github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY=
 github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
-github.com/Azure/go-autorest/logger v0.2.0 h1:e4RVHVZKC5p6UANLJHkM4OfR1UKZPj8Wt8Pcx+3oqrE=
-github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
 github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k=
 github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
-github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
-github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@@ -100,8 +82,6 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
 github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
 github.com/blang/semver v3.5.0+incompatible h1:CGxCgetQ64DKk7rdZ++Vfnb1+ogGNnB17OJKJXD2Cfs=
 github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
-github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
-github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
 github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -173,14 +153,11 @@ github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi
 github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
 github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
-github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
-github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
 github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
 github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@@ -188,7 +165,6 @@ github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0
 github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
@@ -199,6 +175,8 @@ github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg=
 github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
 github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY=
 github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
+github.com/go-logr/logr v0.2.1 h1:fV3MLmabKIZ383XifUjFSwcoGee0v9qgPp8wy5svibE=
+github.com/go-logr/logr v0.2.1/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
 github.com/go-logr/logr v0.3.0 h1:q4c+kbcR0d5rSurhBR8dIgieOaYpXtsdTYfx22Cu6rs=
 github.com/go-logr/logr v0.3.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
 github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54=
@@ -268,19 +246,14 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4er
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
-github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
 github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
 github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@@ -303,9 +276,6 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
-github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
 github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -313,14 +283,10 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
-github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
@@ -487,7 +453,7 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
 github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
 github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
-github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
 github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
 github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
 github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@@ -580,6 +546,8 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
+github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
+github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
 github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
 github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
@@ -589,6 +557,7 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
 github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
 github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
 github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
@@ -607,12 +576,14 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 h1:ndzgwN
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
 github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@@ -622,8 +593,8 @@ go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
 go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 h1:VcrIfasaLFkyjk6KNlXQSzO+B0fZcnECiDrKJsfxka0=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
-go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489 h1:1JFLBqwIgdyHN1ZtgjTBwO+blA6gVOmZurpiMEsETKo=
-go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg=
+go.etcd.io/etcd v0.5.0-alpha.5.0.20200819165624-17cef6e3e9d5 h1:Gqga3zA9tdAcfqobUGjSoCob5L3f8Dt5EuOp3ihNZko=
+go.etcd.io/etcd v0.5.0-alpha.5.0.20200819165624-17cef6e3e9d5/go.mod h1:skWido08r9w6Lq/w70DO5XYIKMu4QFu1+4VsqLQuJy8=
 go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
 go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
 go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
@@ -632,7 +603,6 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -667,22 +637,17 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE=
-golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
 golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
 golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
-golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -694,15 +659,11 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE=
 golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
-golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
-golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
 golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -723,35 +684,29 @@ golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M=
 golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
-golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs=
 golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
-golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -783,7 +738,6 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -791,24 +745,19 @@ golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
 golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8=
+golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88=
 golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
-golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -822,8 +771,6 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZe
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s=
-golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -852,31 +799,16 @@ golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200616133436-c1934b75d054 h1:HHeAlu5H9b71C+Fx0K+1dGgVFN1DM1/wz4aoGOA5qS8=
 golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0=
 gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
@@ -885,11 +817,7 @@ google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -909,19 +837,9 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2El
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
 google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
 google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
-google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a h1:pOwg4OoaRYScjmR4LlLgdtnyoHYTSAVhhqe5uPdpII8=
-google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
@@ -935,8 +853,6 @@ google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
 google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
-google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -948,8 +864,6 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
 google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
-google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
-google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -987,40 +901,37 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U=
-honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 k8s.io/api v0.18.6 h1:osqrAXbOQjkKIWDTjrqxWQ3w0GkKb1KA1XkUGHHYpeE=
 k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI=
-k8s.io/api v0.20.2 h1:y/HR22XDZY3pniu9hIFDLpUCPq2w5eQ6aV/VFQ7uJMw=
-k8s.io/api v0.20.2/go.mod h1:d7n6Ehyzx+S+cE3VhTGfVNNqtGc/oL9DCdYYahlurV8=
+k8s.io/api v0.19.3 h1:GN6ntFnv44Vptj/b+OnMW7FmzkpDoIDLZRvKX3XH9aU=
+k8s.io/api v0.19.3/go.mod h1:VF+5FT1B74Pw3KxMdKyinLo+zynBaMBiAfGMuldcNDs=
 k8s.io/apiextensions-apiserver v0.18.6 h1:vDlk7cyFsDyfwn2rNAO2DbmUbvXy5yT5GE3rrqOzaMo=
 k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M=
-k8s.io/apiextensions-apiserver v0.20.2 h1:rfrMWQ87lhd8EzQWRnbQ4gXrniL/yTRBgYH1x1+BLlo=
-k8s.io/apiextensions-apiserver v0.20.2/go.mod h1:F6TXp389Xntt+LUq3vw6HFOLttPa0V8821ogLGwb6Zs=
+k8s.io/apiextensions-apiserver v0.19.3 h1:WZxBypSHW4SdXHbdPTS/Jy7L2la6Niggs8BuU5o+avo=
+k8s.io/apiextensions-apiserver v0.19.3/go.mod h1:igVEkrE9TzInc1tYE7qSqxaLg/rEAp6B5+k9Q7+IC8Q=
 k8s.io/apimachinery v0.18.6 h1:RtFHnfGNfd1N0LeSrKCUznz5xtUP1elRGvHJbL3Ntag=
 k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
-k8s.io/apimachinery v0.20.2 h1:hFx6Sbt1oG0n6DZ+g4bFt5f6BoMkOjKWsQFu077M3Vg=
-k8s.io/apimachinery v0.20.2/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
+k8s.io/apimachinery v0.19.3 h1:bpIQXlKjB4cB/oNpnNnV+BybGPR7iP5oYpsOTEJ4hgc=
+k8s.io/apimachinery v0.19.3/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
 k8s.io/apiserver v0.18.6 h1:HcWwcOfhj4Yv6y2igP4ZUuovyPjVLGoZcG0Tsph4Mxo=
 k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg=
-k8s.io/apiserver v0.20.2 h1:lGno2t3gcZnLtzsKH4oG0xA9/4GTiBzMO1DGp+K+Bak=
-k8s.io/apiserver v0.20.2/go.mod h1:2nKd93WyMhZx4Hp3RfgH2K5PhwyTrprrkWYnI7id7jA=
-k8s.io/cli-runtime v0.20.2/go.mod h1:FjH6uIZZZP3XmwrXWeeYCbgxcrD6YXxoAykBaWH0VdM=
+k8s.io/apiserver v0.19.3 h1:H7KUbLD74rh8NOPMLBJPSEG3Djqcv6Zxn5Ud0AL5u/k=
+k8s.io/apiserver v0.19.3/go.mod h1:bx6dMm+H6ifgKFpCQT/SAhPwhzoeIMlHIaibomUDec0=
+k8s.io/cli-runtime v0.19.3/go.mod h1:q+l845i5/uWzcUpCrl+L4f3XLaJi8ZeLVQ/decwty0A=
 k8s.io/client-go v0.18.6 h1:I+oWqJbibLSGsZj8Xs8F0aWVXJVIoUHWaaJV3kUN/Zw=
 k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q=
-k8s.io/client-go v0.20.2 h1:uuf+iIAbfnCSw8IGAv/Rg0giM+2bOzHLOsbbrwrdhNQ=
-k8s.io/client-go v0.20.2/go.mod h1:kH5brqWqp7HDxUFKoEgiI4v8G1xzbe9giaCenUWJzgE=
+k8s.io/client-go v0.19.3 h1:ctqR1nQ52NUs6LpI0w+a5U+xjYwflFwA13OJKcicMxg=
+k8s.io/client-go v0.19.3/go.mod h1:+eEMktZM+MG0KO+PTkci8xnbCZHvj9TqR6Q1XDUIJOM=
 k8s.io/code-generator v0.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c=
-k8s.io/code-generator v0.20.2/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg=
+k8s.io/code-generator v0.19.3/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk=
 k8s.io/component-base v0.18.6 h1:Wd6cHGwJN2qpufnirVOB3oMhyhbioGsKEi5HeDBsV+s=
 k8s.io/component-base v0.18.6/go.mod h1:knSVsibPR5K6EW2XOjEHik6sdU5nCvKMrzMt2D4In14=
-k8s.io/component-base v0.20.2 h1:LMmu5I0pLtwjpp5009KLuMGFqSc2S2isGw8t1hpYKLE=
-k8s.io/component-base v0.20.2/go.mod h1:pzFtCiwe/ASD0iV7ySMu8SYVJjCapNM9bjvk7ptpKh0=
-k8s.io/component-helpers v0.20.2/go.mod h1:qeM6iAWGqIr+WE8n2QW2OK9XkpZkPNTxAoEv9jl40/I=
+k8s.io/component-base v0.19.3 h1:c+DzDNAQFlaoyX+yv8YuWi8xmlQvvY5DnJGbaz5U74o=
+k8s.io/component-base v0.19.3/go.mod h1:WhLWSIefQn8W8jxSLl5WNiR6z8oyMe/8Zywg7alOkRc=
 k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
-k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
+k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
 k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
 k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
@@ -1029,36 +940,36 @@ k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok=
 k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
 k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A=
 k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
-k8s.io/klog/v2 v2.4.0 h1:7+X0fUguPyrKEC4WjH8iGDg3laWgMo5tMnRTIGTTxGQ=
-k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
 k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY=
 k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
-k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd h1:sOHNzJIkytDF6qadMNKhhDRpc6ODik8lVC6nOur7B2c=
-k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=
-k8s.io/kubectl v0.20.2 h1:mXExF6N4eQUYmlfXJmfWIheCBLF6/n4VnwQKbQki5iE=
-k8s.io/kubectl v0.20.2/go.mod h1:/bchZw5fZWaGZxaRxxfDQKej/aDEtj/Tf9YSS4Jl0es=
-k8s.io/metrics v0.20.2/go.mod h1:yTck5nl5wt/lIeLcU6g0b8/AKJf2girwe0PQiaM4Mwk=
+k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 h1:+WnxoVtG8TMiudHBSEtrVL1egv36TkkJm+bA8AxicmQ=
+k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o=
+k8s.io/kube-openapi v0.0.0-20200923155610-8b5066479488 h1:mNpvQf4lkIHNOXCoM+Veu/UXwA56Yx1J7hY1Tvcs/oM=
+k8s.io/kube-openapi v0.0.0-20200923155610-8b5066479488/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o=
+k8s.io/kubectl v0.19.3 h1:T8IHHpg+uRIfn34wqJ8wHG5bbH+VV5FNPtJ+jKcho1U=
+k8s.io/kubectl v0.19.3/go.mod h1:t5cscfrAuHUvEGNyNJjPKt+rGlaJzk8jrKYHXxEsANE=
+k8s.io/metrics v0.19.3/go.mod h1:Eap/Lk1FiAIjkaArFuv41v+ph6dbDpVGwAg7jMI+4vg=
 k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
 k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 h1:v8ud2Up6QK1lNOKFgiIVrZdMg7MpmSnvtrOieolJKoE=
 k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
-k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw=
-k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
+k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
+k8s.io/utils v0.0.0-20201005171033-6301aaf42dc7 h1:XQ0OMFdRDkDIu0b1zqEKSZdWUD7I4bZ4d4nqr8CLKbQ=
+k8s.io/utils v0.0.0-20201005171033-6301aaf42dc7/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
-rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
-rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7 h1:uuHDyjllyzRyCIvvn0OBjiRB0SgBZGqHNYAmjR7fO50=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
-sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14 h1:TihvEz9MPj2u0KWds6E2OBUXfwaL4qRJ33c7HGiJpqk=
-sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
+sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.9 h1:rusRLrDhjBp6aYtl9sGEvQJr6faoHoDLd0YcUBTZguI=
+sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.9/go.mod h1:dzAXnQbTRyDlZPJX2SUPEqvnB+j7AJjtlox7PEwigU0=
 sigs.k8s.io/controller-runtime v0.6.3 h1:SBbr+inLPEKhvlJtrvDcwIpm+uhDvp63Bl72xYJtoOE=
 sigs.k8s.io/controller-runtime v0.6.3/go.mod h1:WlZNXcM0++oyaQt4B7C2lEE5JYRs8vJUzRP4N4JpdAY=
 sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
 sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
 sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
 sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
-sigs.k8s.io/structured-merge-diff/v4 v4.0.2 h1:YHQV7Dajm86OuqnIR6zAelnDWBRjo+YhYV9PmGrh1s8=
-sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
+sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA=
+sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
 sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
 sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
 sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
+vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=
diff --git a/pkg/apis/core/v1beta1/validation/validation.go b/pkg/apis/core/v1beta1/validation/validation.go
index 4e8f8a1a..dce9d364 100644
--- a/pkg/apis/core/v1beta1/validation/validation.go
+++ b/pkg/apis/core/v1beta1/validation/validation.go
@@ -311,7 +311,7 @@ func ValidateKubeFedConfig(kubeFedConfig, oldKubeFedConfig *v1beta1.KubeFedConfi
 
 			allErrs = append(allErrs, validateEnumStrings(gatesPath.Child("name"), string(gate.Name),
 				[]string{string(features.PushReconciler), string(features.RawResourceStatusCollection), string(features.SchedulerPreferences),
-					string(features.FederatedIngress)})...)
+					string(features.CrossClusterServiceDiscovery), string(features.FederatedIngress)})...)
 
 			allErrs = append(allErrs, validateEnumStrings(gatesPath.Child("configuration"), string(gate.Configuration),
 				[]string{string(v1beta1.ConfigurationEnabled), string(v1beta1.ConfigurationDisabled)})...)
diff --git a/pkg/client/generic/genericclient.go b/pkg/client/generic/genericclient.go
index 96bfe012..eff5602d 100644
--- a/pkg/client/generic/genericclient.go
+++ b/pkg/client/generic/genericclient.go
@@ -31,7 +31,7 @@ type Client interface {
 	Create(ctx context.Context, obj runtime.Object) error
 	Get(ctx context.Context, obj runtime.Object, namespace, name string) error
 	Update(ctx context.Context, obj runtime.Object) error
-	Delete(ctx context.Context, obj runtime.Object, namespace, name string, opts ...client.DeleteOption) error
+	Delete(ctx context.Context, obj runtime.Object, namespace, name string) error
 	List(ctx context.Context, obj runtime.Object, namespace string, opts ...client.ListOption) error
 	UpdateStatus(ctx context.Context, obj runtime.Object) error
 }
@@ -71,14 +71,14 @@ func (c *genericClient) Update(ctx context.Context, obj runtime.Object) error {
 	return c.client.Update(ctx, obj)
 }
 
-func (c *genericClient) Delete(ctx context.Context, obj runtime.Object, namespace, name string, opts ...client.DeleteOption) error {
+func (c *genericClient) Delete(ctx context.Context, obj runtime.Object, namespace, name string) error {
 	accessor, err := meta.Accessor(obj)
 	if err != nil {
 		return err
 	}
 	accessor.SetNamespace(namespace)
 	accessor.SetName(name)
-	return c.client.Delete(ctx, obj, opts...)
+	return c.client.Delete(ctx, obj)
 }
 
 func (c *genericClient) List(ctx context.Context, obj runtime.Object, namespace string, opts ...client.ListOption) error {
diff --git a/pkg/controller/dnsendpoint/common.go b/pkg/controller/dnsendpoint/common.go
index 4fc5aa6c..06369112 100644
--- a/pkg/controller/dnsendpoint/common.go
+++ b/pkg/controller/dnsendpoint/common.go
@@ -22,7 +22,7 @@ import (
 
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/util/sets"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	feddnsv1a1 "sigs.k8s.io/kubefed/pkg/apis/multiclusterdns/v1alpha1"
 )
diff --git a/pkg/controller/dnsendpoint/controller.go b/pkg/controller/dnsendpoint/controller.go
index b2ed6889..313b97c3 100644
--- a/pkg/controller/dnsendpoint/controller.go
+++ b/pkg/controller/dnsendpoint/controller.go
@@ -22,7 +22,7 @@ import (
 	"time"
 
 	"github.com/pkg/errors"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/pkg/controller/federatedtypeconfig/controller.go b/pkg/controller/federatedtypeconfig/controller.go
index 465532a6..990aa649 100644
--- a/pkg/controller/federatedtypeconfig/controller.go
+++ b/pkg/controller/federatedtypeconfig/controller.go
@@ -30,7 +30,7 @@ import (
 	"k8s.io/apimachinery/pkg/util/sets"
 	restclient "k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/cache"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	corev1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
 	genericclient "sigs.k8s.io/kubefed/pkg/client/generic"
diff --git a/pkg/controller/ingressdns/controller.go b/pkg/controller/ingressdns/controller.go
index 9015dcef..45a257a0 100644
--- a/pkg/controller/ingressdns/controller.go
+++ b/pkg/controller/ingressdns/controller.go
@@ -32,7 +32,7 @@ import (
 	pkgruntime "k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/util/runtime"
 	"k8s.io/client-go/tools/cache"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
 	dnsv1a1 "sigs.k8s.io/kubefed/pkg/apis/multiclusterdns/v1alpha1"
diff --git a/pkg/controller/kubefedcluster/clusterclient.go b/pkg/controller/kubefedcluster/clusterclient.go
index 2d11a25d..8fe5612c 100644
--- a/pkg/controller/kubefedcluster/clusterclient.go
+++ b/pkg/controller/kubefedcluster/clusterclient.go
@@ -29,7 +29,7 @@ import (
 	"k8s.io/apimachinery/pkg/util/sets"
 	kubeclientset "k8s.io/client-go/kubernetes"
 	restclient "k8s.io/client-go/rest"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	fedcommon "sigs.k8s.io/kubefed/pkg/apis/core/common"
 	fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
diff --git a/pkg/controller/kubefedcluster/controller.go b/pkg/controller/kubefedcluster/controller.go
index 1f581dcc..7aa70586 100644
--- a/pkg/controller/kubefedcluster/controller.go
+++ b/pkg/controller/kubefedcluster/controller.go
@@ -28,17 +28,19 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 	"k8s.io/apimachinery/pkg/util/wait"
+	utilfeature "k8s.io/apiserver/pkg/util/feature"
 	kubeclient "k8s.io/client-go/kubernetes"
 	typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
 	restclient "k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/tools/record"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
 	genericclient "sigs.k8s.io/kubefed/pkg/client/generic"
 	genscheme "sigs.k8s.io/kubefed/pkg/client/generic/scheme"
 	"sigs.k8s.io/kubefed/pkg/controller/util"
+	"sigs.k8s.io/kubefed/pkg/features"
 	"sigs.k8s.io/kubefed/pkg/metrics"
 )
 
@@ -249,6 +251,10 @@ func (cc *ClusterController) updateIndividualClusterStatus(cluster *fedv1b1.Kube
 
 	currentClusterStatus = thresholdAdjustedClusterStatus(currentClusterStatus, storedData, cc.clusterHealthCheckConfig)
 
+	if utilfeature.DefaultFeatureGate.Enabled(features.CrossClusterServiceDiscovery) {
+		currentClusterStatus = cc.updateClusterZonesAndRegion(currentClusterStatus, cluster, clusterClient)
+	}
+
 	storedData.clusterStatus = currentClusterStatus
 	cluster.Status = *currentClusterStatus
 	if err := cc.client.UpdateStatus(context.TODO(), cluster); err != nil {
@@ -295,6 +301,31 @@ func thresholdAdjustedClusterStatus(clusterStatus *fedv1b1.KubeFedClusterStatus,
 	return clusterStatus
 }
 
+func (cc *ClusterController) updateClusterZonesAndRegion(clusterStatus *fedv1b1.KubeFedClusterStatus, cluster *fedv1b1.KubeFedCluster,
+	clusterClient *ClusterClient) *fedv1b1.KubeFedClusterStatus {
+	if !util.IsClusterReady(clusterStatus) {
+		return clusterStatus
+	}
+
+	zones, region, err := clusterClient.GetClusterZones()
+	if err != nil {
+		cc.RecordError(cluster, "RetrievingRegionZonesFailed", errors.Wrap(err, "Failed to get zones and region for the cluster"))
+		return clusterStatus
+	}
+
+	// If new zone & region are empty, preserve the old ones so that user configured zone & region
+	// labels are effective
+	if len(zones) == 0 {
+		zones = cluster.Status.Zones
+	}
+	if len(region) == 0 && cluster.Status.Region != nil {
+		region = *cluster.Status.Region
+	}
+	clusterStatus.Zones = zones
+	clusterStatus.Region = &region
+	return clusterStatus
+}
+
 func clusterStatusEqual(newClusterStatus, oldClusterStatus *fedv1b1.KubeFedClusterStatus) bool {
 	return util.IsClusterReady(newClusterStatus) == util.IsClusterReady(oldClusterStatus)
 }
diff --git a/pkg/controller/schedulingmanager/controller.go b/pkg/controller/schedulingmanager/controller.go
index 8b5a3392..ae4a5b52 100644
--- a/pkg/controller/schedulingmanager/controller.go
+++ b/pkg/controller/schedulingmanager/controller.go
@@ -24,7 +24,7 @@ import (
 	"k8s.io/apimachinery/pkg/util/runtime"
 	restclient "k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/cache"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	corev1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
 	"sigs.k8s.io/kubefed/pkg/controller/schedulingpreference"
diff --git a/pkg/controller/schedulingpreference/controller.go b/pkg/controller/schedulingpreference/controller.go
index 508061eb..a8a5675e 100644
--- a/pkg/controller/schedulingpreference/controller.go
+++ b/pkg/controller/schedulingpreference/controller.go
@@ -31,7 +31,7 @@ import (
 	restclient "k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/tools/record"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
 	"sigs.k8s.io/kubefed/pkg/controller/util"
diff --git a/pkg/controller/servicedns/controller.go b/pkg/controller/servicedns/controller.go
new file mode 100644
index 00000000..36e2a57f
--- /dev/null
+++ b/pkg/controller/servicedns/controller.go
@@ -0,0 +1,445 @@
+/*
+Copyright 2018 The Kubernetes 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 servicedns
+
+import (
+	"context"
+	"encoding/json"
+	"reflect"
+	"sort"
+	"time"
+
+	"github.com/pkg/errors"
+
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	pkgruntime "k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/util/runtime"
+	"k8s.io/client-go/tools/cache"
+	"k8s.io/klog"
+
+	fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
+	dnsv1a1 "sigs.k8s.io/kubefed/pkg/apis/multiclusterdns/v1alpha1"
+	genericclient "sigs.k8s.io/kubefed/pkg/client/generic"
+	"sigs.k8s.io/kubefed/pkg/controller/util"
+	"sigs.k8s.io/kubefed/pkg/metrics"
+)
+
+const (
+	allClustersKey = "ALL_CLUSTERS"
+)
+
+// Controller manages ServiceDNSRecord resources in the host cluster.
+type Controller struct {
+	client genericclient.Client
+
+	// For triggering reconciliation of all target resources. This is
+	// used when a new cluster becomes available.
+	clusterDeliverer *util.DelayingDeliverer
+
+	// informer for service resources in member clusters
+	serviceInformer util.FederatedInformer
+
+	// informer for endpoint resources in member clusters
+	endpointInformer util.FederatedInformer
+
+	// Store for the ServiceDNSRecord objects
+	serviceDNSStore cache.Store
+	// Informer for the ServiceDNSRecord objects
+	serviceDNSController cache.Controller
+
+	// Store for the Domain objects
+	domainStore cache.Store
+	// Informer for the Domain objects
+	domainController cache.Controller
+
+	worker util.ReconcileWorker
+
+	clusterAvailableDelay   time.Duration
+	clusterUnavailableDelay time.Duration
+	smallDelay              time.Duration
+
+	fedNamespace string
+}
+
+// StartController starts the Controller for managing ServiceDNSRecord objects.
+func StartController(config *util.ControllerConfig, stopChan <-chan struct{}) error {
+	controller, err := newController(config)
+	if err != nil {
+		return err
+	}
+	if config.MinimizeLatency {
+		controller.minimizeLatency()
+	}
+	klog.Infof("Starting ServiceDNS controller")
+	controller.Run(stopChan)
+	return nil
+}
+
+// newController returns a new controller to manage ServiceDNSRecord objects.
+func newController(config *util.ControllerConfig) (*Controller, error) {
+	client := genericclient.NewForConfigOrDieWithUserAgent(config.KubeConfig, "ServiceDNS")
+	s := &Controller{
+		client:                  client,
+		clusterAvailableDelay:   config.ClusterAvailableDelay,
+		clusterUnavailableDelay: config.ClusterUnavailableDelay,
+		smallDelay:              time.Second * 3,
+		fedNamespace:            config.KubeFedNamespace,
+	}
+
+	s.worker = util.NewReconcileWorker(s.reconcile, util.WorkerTiming{
+		ClusterSyncDelay: s.clusterAvailableDelay,
+	})
+
+	// Build deliverer for triggering cluster reconciliations.
+	s.clusterDeliverer = util.NewDelayingDeliverer()
+
+	// Informer for ServiceDNSRecord resources in the host cluster
+	var err error
+	s.serviceDNSStore, s.serviceDNSController, err = util.NewGenericInformer(
+		config.KubeConfig,
+		config.TargetNamespace,
+		&dnsv1a1.ServiceDNSRecord{},
+		util.NoResyncPeriod,
+		s.worker.EnqueueObject,
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	// Informer for the Domain resource
+	s.domainStore, s.domainController, err = util.NewGenericInformer(
+		config.KubeConfig,
+		config.KubeFedNamespace,
+		&dnsv1a1.Domain{},
+		util.NoResyncPeriod,
+		func(pkgruntime.Object) {
+			s.clusterDeliverer.DeliverAt(allClustersKey, nil, time.Now())
+		},
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	// Federated informer for service resources in member clusters
+	s.serviceInformer, err = util.NewFederatedInformer(
+		config,
+		client,
+		&metav1.APIResource{
+			Group:        "",
+			Version:      "v1",
+			Kind:         "Service",
+			Name:         "services",
+			SingularName: "service",
+			Namespaced:   true},
+		s.worker.EnqueueObject,
+		&util.ClusterLifecycleHandlerFuncs{
+			ClusterAvailable: func(cluster *fedv1b1.KubeFedCluster) {
+				// When new cluster becomes available process all the target resources again.
+				s.clusterDeliverer.DeliverAt(allClustersKey, nil, time.Now().Add(s.clusterAvailableDelay))
+			},
+			// When a cluster becomes unavailable process all the target resources again.
+			ClusterUnavailable: func(cluster *fedv1b1.KubeFedCluster, _ []interface{}) {
+				s.clusterDeliverer.DeliverAt(allClustersKey, nil, time.Now().Add(s.clusterUnavailableDelay))
+			},
+		},
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	// Federated informers on endpoints in federated clusters.
+	// This will enable to check if service ingress endpoints in federated clusters are reachable
+	s.endpointInformer, err = util.NewFederatedInformer(
+		config,
+		client,
+		&metav1.APIResource{
+			Group:        "",
+			Version:      "v1",
+			Kind:         "Endpoints",
+			Name:         "endpoints",
+			SingularName: "endpoint",
+			Namespaced:   true},
+		s.worker.EnqueueObject,
+		&util.ClusterLifecycleHandlerFuncs{},
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	return s, nil
+}
+
+// minimizeLatency reduces delays and timeouts to make the controller more responsive (useful for testing).
+func (c *Controller) minimizeLatency() {
+	c.clusterAvailableDelay = time.Second
+	c.clusterUnavailableDelay = time.Second
+	c.smallDelay = 20 * time.Millisecond
+	c.worker.SetDelay(50*time.Millisecond, c.clusterAvailableDelay)
+}
+
+// Run runs the Controller.
+func (c *Controller) Run(stopChan <-chan struct{}) {
+	go c.serviceDNSController.Run(stopChan)
+	go c.domainController.Run(stopChan)
+	c.serviceInformer.Start()
+	c.endpointInformer.Start()
+	c.clusterDeliverer.StartWithHandler(func(_ *util.DelayingDelivererItem) {
+		c.reconcileOnClusterChange()
+	})
+
+	c.worker.Run(stopChan)
+
+	// Ensure all goroutines are cleaned up when the stop channel closes
+	go func() {
+		<-stopChan
+		c.serviceInformer.Stop()
+		c.endpointInformer.Stop()
+		c.clusterDeliverer.Stop()
+	}()
+}
+
+// Check whether all data stores are in sync. False is returned if any of the serviceInformer/stores is not yet
+// synced with the corresponding api server.
+func (c *Controller) isSynced() bool {
+	if !c.serviceInformer.ClustersSynced() {
+		klog.V(2).Infof("Cluster list not synced")
+		return false
+	}
+	clusters, err := c.serviceInformer.GetReadyClusters()
+	if err != nil {
+		runtime.HandleError(errors.Wrap(err, "Failed to get ready clusters"))
+		return false
+	}
+	if !c.serviceInformer.GetTargetStore().ClustersSynced(clusters) {
+		return false
+	}
+
+	if !c.endpointInformer.ClustersSynced() {
+		klog.V(2).Infof("Cluster list not synced")
+		return false
+	}
+	clusters, err = c.endpointInformer.GetReadyClusters()
+	if err != nil {
+		runtime.HandleError(errors.Wrap(err, "Failed to get ready clusters"))
+		return false
+	}
+	if !c.endpointInformer.GetTargetStore().ClustersSynced(clusters) {
+		return false
+	}
+
+	return true
+}
+
+// The function triggers reconciliation of all target federated resources.
+func (c *Controller) reconcileOnClusterChange() {
+	if !c.isSynced() {
+		c.clusterDeliverer.DeliverAt(allClustersKey, nil, time.Now().Add(c.clusterAvailableDelay))
+	}
+	for _, obj := range c.serviceDNSStore.List() {
+		qualifiedName := util.NewQualifiedName(obj.(pkgruntime.Object))
+		c.worker.EnqueueWithDelay(qualifiedName, c.smallDelay)
+	}
+}
+
+func (c *Controller) reconcile(qualifiedName util.QualifiedName) util.ReconciliationStatus {
+	defer metrics.UpdateControllerReconcileDurationFromStart("servicednscontroller", time.Now())
+
+	if !c.isSynced() {
+		return util.StatusNotSynced
+	}
+
+	key := qualifiedName.String()
+
+	klog.V(4).Infof("Starting to reconcile ServiceDNS resource: %v", key)
+	startTime := time.Now()
+	defer func() {
+		klog.V(4).Infof("Finished reconciling ServiceDNS resource %v (duration: %v)", key, time.Since(startTime))
+	}()
+
+	cachedObj, exist, err := c.serviceDNSStore.GetByKey(key)
+	if err != nil {
+		runtime.HandleError(errors.Wrapf(err, "Failed to query ServiceDNS store for %q", key))
+		return util.StatusError
+	}
+	if !exist {
+		return util.StatusAllOK
+	}
+	cachedDNS := cachedObj.(*dnsv1a1.ServiceDNSRecord)
+
+	domainKey := util.QualifiedName{Namespace: c.fedNamespace, Name: cachedDNS.Spec.DomainRef}.String()
+	cachedDomain, exist, err := c.domainStore.GetByKey(domainKey)
+	if err != nil {
+		runtime.HandleError(errors.Wrapf(err, "Failed to query Domain store for %q", domainKey))
+		return util.StatusError
+	}
+	if !exist {
+		return util.StatusAllOK
+	}
+	domainObj := cachedDomain.(*dnsv1a1.Domain)
+
+	fedDNS := &dnsv1a1.ServiceDNSRecord{
+		ObjectMeta: util.DeepCopyRelevantObjectMeta(cachedDNS.ObjectMeta),
+		Spec:       *cachedDNS.Spec.DeepCopy(),
+	}
+
+	clusters, err := c.serviceInformer.GetReadyClusters()
+	if err != nil {
+		runtime.HandleError(errors.Wrap(err, "Failed to get ready cluster list"))
+		return util.StatusError
+	}
+
+	fedDNSStatus := make([]dnsv1a1.ClusterDNS, 0, len(clusters))
+	// Iterate through all ready clusters and aggregate the service status for the key
+	for _, cluster := range clusters {
+		if cluster.Status.Region == nil || *cluster.Status.Region == "" || len(cluster.Status.Zones) == 0 {
+			klog.Errorf("Cluster %q does not have Region or Zones Attributes", cluster.Name)
+			return util.StatusError
+		}
+		clusterDNS := dnsv1a1.ClusterDNS{
+			Cluster: cluster.Name,
+			Region:  *cluster.Status.Region,
+			Zones:   cluster.Status.Zones,
+		}
+
+		// If there are no endpoints for the service, the service is not backed by pods
+		// and traffic is not routable to the service. We avoid such service shards while
+		// writing DNS records, except when user specified to AllowServiceWithoutEndpoints
+		endpointsExist, err := c.serviceBackedByEndpointsInCluster(cluster.Name, key)
+		if err != nil {
+			return util.StatusError
+		}
+		if cachedDNS.Spec.AllowServiceWithoutEndpoints || endpointsExist {
+			lbStatus, err := c.getServiceStatusInCluster(cluster.Name, key)
+			if err != nil {
+				return util.StatusError
+			}
+			clusterDNS.LoadBalancer = *lbStatus
+		}
+		fedDNSStatus = append(fedDNSStatus, clusterDNS)
+	}
+
+	// We should preserve ClusterDNS entries (without loadbalancers) for offline clusters
+	// so that any user within the offline cluster targeting the federated service can
+	// be redirected (via CNAME DNS record) to any of the service shards of the federated
+	// service in online clusters.
+	offlineClusters, err := c.serviceInformer.GetUnreadyClusters()
+	if err != nil {
+		runtime.HandleError(errors.Wrap(err, "Failed to get unready cluster list"))
+		return util.StatusError
+	}
+	for _, cluster := range offlineClusters {
+		for _, clusterDNS := range fedDNS.Status.DNS {
+			if clusterDNS.Cluster == cluster.Name {
+				offlineClusterDNS := clusterDNS
+				offlineClusterDNS.LoadBalancer = corev1.LoadBalancerStatus{}
+				fedDNSStatus = append(fedDNSStatus, offlineClusterDNS)
+				klog.V(5).Infof("Cluster %s is Offline, Preserving previously available status for Service %s", cluster.Name, key)
+				break
+			}
+		}
+	}
+
+	sort.Slice(fedDNSStatus, func(i, j int) bool {
+		return fedDNSStatus[i].Cluster < fedDNSStatus[j].Cluster
+	})
+	fedDNS.Status.DNS = fedDNSStatus
+	fedDNS.Status.Domain = domainObj.Domain
+
+	if !reflect.DeepEqual(cachedDNS.Status, fedDNS.Status) {
+		err = c.client.UpdateStatus(context.TODO(), fedDNS)
+		if err != nil {
+			runtime.HandleError(errors.Wrapf(err, "Error updating the ServiceDNS object %s", key))
+			return util.StatusError
+		}
+	}
+
+	return util.StatusAllOK
+}
+
+// getServiceStatusInCluster returns service status in federated cluster
+func (c *Controller) getServiceStatusInCluster(cluster, key string) (*corev1.LoadBalancerStatus, error) {
+	lbStatus := &corev1.LoadBalancerStatus{}
+
+	clusterServiceObj, serviceFound, err := c.serviceInformer.GetTargetStore().GetByKey(cluster, key)
+	if err != nil {
+		runtime.HandleError(errors.Wrapf(err, "Failed to get %s service from %s", key, cluster))
+		return lbStatus, err
+	}
+	if serviceFound {
+		// TODO(shashi): Find better alternative to convert Unstructured to a given type
+		clusterService, ok := clusterServiceObj.(*unstructured.Unstructured)
+		if !ok {
+			runtime.HandleError(errors.Errorf("Failed to cast the object to unstructured object: %v", clusterServiceObj))
+			return lbStatus, err
+		}
+		content, err := clusterService.MarshalJSON()
+		if err != nil {
+			runtime.HandleError(errors.Errorf("Failed to marshall the unstructured object: %v", clusterService))
+			return lbStatus, err
+		}
+		service := corev1.Service{}
+		err = json.Unmarshal(content, &service)
+		if err == nil {
+			// Sort the ingress slice, so that we return comparable service status.
+			ingress := service.Status.LoadBalancer.Ingress
+			sort.Slice(ingress, func(i, j int) bool {
+				if ingress[i].IP == ingress[j].IP {
+					return ingress[i].Hostname < ingress[j].Hostname
+				}
+				return ingress[i].IP < ingress[j].IP
+			})
+			lbStatus = &service.Status.LoadBalancer
+		}
+	}
+	return lbStatus, nil
+}
+
+// serviceBackedByEndpointsInCluster returns ready endpoints corresponding to service in federated cluster
+func (c *Controller) serviceBackedByEndpointsInCluster(cluster, key string) (bool, error) {
+	addresses := []corev1.EndpointAddress{}
+
+	clusterEndpointObj, endpointFound, err := c.endpointInformer.GetTargetStore().GetByKey(cluster, key)
+	if err != nil {
+		runtime.HandleError(errors.Wrapf(err, "Failed to get %s endpoint from %s", key, cluster))
+		return false, err
+	}
+	if endpointFound {
+		// TODO(shashi): Find better alternative to convert Unstructured to a given type
+		clusterEndpoints, ok := clusterEndpointObj.(*unstructured.Unstructured)
+		if !ok {
+			runtime.HandleError(errors.Errorf("Failed to cast the object to unstructured object: %v", clusterEndpointObj))
+			return false, err
+		}
+		content, err := clusterEndpoints.MarshalJSON()
+		if err != nil {
+			runtime.HandleError(errors.Errorf("Failed to marshall the unstructured object: %v", clusterEndpoints))
+			return false, err
+		}
+		endpoints := corev1.Endpoints{}
+		err = json.Unmarshal(content, &endpoints)
+		if err == nil {
+			for _, subset := range endpoints.Subsets {
+				if len(subset.Addresses) > 0 {
+					addresses = append(addresses, subset.Addresses...)
+				}
+			}
+		}
+	}
+	return (len(addresses) > 0), nil
+}
diff --git a/pkg/controller/status/controller.go b/pkg/controller/status/controller.go
index 2a2cfae9..aed62ee4 100644
--- a/pkg/controller/status/controller.go
+++ b/pkg/controller/status/controller.go
@@ -32,7 +32,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/util/runtime"
 	"k8s.io/client-go/tools/cache"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	"sigs.k8s.io/kubefed/pkg/apis/core/typeconfig"
 	fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
diff --git a/pkg/controller/sync/accessor.go b/pkg/controller/sync/accessor.go
index 848a9ff9..c2c15471 100644
--- a/pkg/controller/sync/accessor.go
+++ b/pkg/controller/sync/accessor.go
@@ -22,7 +22,7 @@ import (
 	pkgruntime "k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/tools/record"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	corev1 "k8s.io/api/core/v1"
 
diff --git a/pkg/controller/sync/controller.go b/pkg/controller/sync/controller.go
index 8311f4da..ffa529ed 100644
--- a/pkg/controller/sync/controller.go
+++ b/pkg/controller/sync/controller.go
@@ -37,9 +37,7 @@ import (
 	"k8s.io/client-go/kubernetes/scheme"
 	typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
 	"k8s.io/client-go/tools/record"
-	"k8s.io/klog/v2"
-
-	"sigs.k8s.io/controller-runtime/pkg/client"
+	"k8s.io/klog"
 
 	"sigs.k8s.io/kubefed/pkg/apis/core/typeconfig"
 	fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
@@ -483,16 +481,8 @@ func (s *KubeFedSyncController) ensureDeletion(fedResource FederatedResource) ut
 		return util.StatusAllOK
 	}
 
-	klog.V(2).Infof("Deserializing delete options of %s %q", kind, key)
-	opts, err := util.GetDeleteOptions(obj)
-	if err != nil {
-		wrappedErr := errors.Wrapf(err, "failed to deserialize delete options of %s %q", kind, key)
-		runtime.HandleError(wrappedErr)
-		return util.StatusError
-	}
-
 	klog.V(2).Infof("Deleting resources managed by %s %q from member clusters.", kind, key)
-	recheckRequired, err := s.deleteFromClusters(fedResource, opts...)
+	recheckRequired, err := s.deleteFromClusters(fedResource)
 	if err != nil {
 		wrappedErr := errors.Wrapf(err, "failed to delete %s %q", kind, key)
 		runtime.HandleError(wrappedErr)
@@ -523,7 +513,7 @@ func (s *KubeFedSyncController) removeManagedLabel(gvk schema.GroupVersionKind,
 	return nil
 }
 
-func (s *KubeFedSyncController) deleteFromClusters(fedResource FederatedResource, opts ...client.DeleteOption) (bool, error) {
+func (s *KubeFedSyncController) deleteFromClusters(fedResource FederatedResource) (bool, error) {
 	gvk := fedResource.TargetGVK()
 	qualifiedName := fedResource.TargetName()
 
@@ -552,7 +542,7 @@ func (s *KubeFedSyncController) deleteFromClusters(fedResource FederatedResource
 			// namespace is no longer cached.
 			dispatcher.RemoveManagedLabel(clusterName, clusterObj)
 		} else {
-			dispatcher.Delete(clusterName, opts...)
+			dispatcher.Delete(clusterName)
 		}
 	})
 	if err != nil {
diff --git a/pkg/controller/sync/dispatch/checkunmanaged.go b/pkg/controller/sync/dispatch/checkunmanaged.go
index 92bb5cc4..b174eecc 100644
--- a/pkg/controller/sync/dispatch/checkunmanaged.go
+++ b/pkg/controller/sync/dispatch/checkunmanaged.go
@@ -26,7 +26,7 @@ import (
 	pkgruntime "k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/util/runtime"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	"sigs.k8s.io/kubefed/pkg/client/generic"
 	"sigs.k8s.io/kubefed/pkg/controller/util"
diff --git a/pkg/controller/sync/dispatch/managed.go b/pkg/controller/sync/dispatch/managed.go
index 3ec241d2..ec5f39af 100644
--- a/pkg/controller/sync/dispatch/managed.go
+++ b/pkg/controller/sync/dispatch/managed.go
@@ -30,9 +30,7 @@ import (
 	pkgruntime "k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/util/sets"
-	"k8s.io/klog/v2"
-
-	"sigs.k8s.io/controller-runtime/pkg/client"
+	"k8s.io/klog"
 
 	"sigs.k8s.io/kubefed/pkg/client/generic"
 	"sigs.k8s.io/kubefed/pkg/controller/sync/status"
@@ -247,10 +245,10 @@ func (d *managedDispatcherImpl) Update(clusterName string, clusterObj *unstructu
 	})
 }
 
-func (d *managedDispatcherImpl) Delete(clusterName string, opts ...client.DeleteOption) {
+func (d *managedDispatcherImpl) Delete(clusterName string) {
 	d.RecordStatus(clusterName, status.DeletionTimedOut, nil)
 
-	d.unmanagedDispatcher.Delete(clusterName, opts...)
+	d.unmanagedDispatcher.Delete(clusterName)
 }
 
 func (d *managedDispatcherImpl) RemoveManagedLabel(clusterName string, clusterObj *unstructured.Unstructured) {
diff --git a/pkg/controller/sync/dispatch/retain.go b/pkg/controller/sync/dispatch/retain.go
index a258b165..d22ccf17 100644
--- a/pkg/controller/sync/dispatch/retain.go
+++ b/pkg/controller/sync/dispatch/retain.go
@@ -46,17 +46,6 @@ func RetainClusterFields(targetKind string, desiredObj, clusterObj, fedObj *unst
 }
 
 func retainServiceFields(desiredObj, clusterObj *unstructured.Unstructured) error {
-	// healthCheckNodePort is allocated by APIServer and unchangeable, so it should be retained while updating
-	healthCheckNodePort, ok, err := unstructured.NestedInt64(clusterObj.Object, "spec", "healthCheckNodePort")
-	if err != nil {
-		return errors.Wrap(err, "Error retrieving healthCheckNodePort from service")
-	}
-	if ok && healthCheckNodePort > 0 {
-		if err = unstructured.SetNestedField(desiredObj.Object, healthCheckNodePort, "spec", "healthCheckNodePort"); err != nil {
-			return errors.Wrap(err, "Error setting healthCheckNodePort for service")
-		}
-	}
-
 	// ClusterIP and NodePort are allocated to Service by cluster, so retain the same if any while updating
 
 	// Retain clusterip
diff --git a/pkg/controller/sync/dispatch/unmanaged.go b/pkg/controller/sync/dispatch/unmanaged.go
index de08bf0a..374ddf1c 100644
--- a/pkg/controller/sync/dispatch/unmanaged.go
+++ b/pkg/controller/sync/dispatch/unmanaged.go
@@ -26,9 +26,7 @@ import (
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/util/runtime"
-	"k8s.io/klog/v2"
-
-	"sigs.k8s.io/controller-runtime/pkg/client"
+	"k8s.io/klog"
 
 	"sigs.k8s.io/kubefed/pkg/client/generic"
 	"sigs.k8s.io/kubefed/pkg/controller/sync/status"
@@ -43,7 +41,7 @@ const eventTemplate = "%s %s %q in cluster %q"
 type UnmanagedDispatcher interface {
 	OperationDispatcher
 
-	Delete(clusterName string, opts ...client.DeleteOption)
+	Delete(clusterName string)
 	RemoveManagedLabel(clusterName string, clusterObj *unstructured.Unstructured)
 }
 
@@ -74,7 +72,7 @@ func (d *unmanagedDispatcherImpl) Wait() (bool, error) {
 	return d.dispatcher.Wait()
 }
 
-func (d *unmanagedDispatcherImpl) Delete(clusterName string, opts ...client.DeleteOption) {
+func (d *unmanagedDispatcherImpl) Delete(clusterName string) {
 	start := time.Now()
 	d.dispatcher.incrementOperationsInitiated()
 	const op = "delete"
@@ -89,7 +87,7 @@ func (d *unmanagedDispatcherImpl) Delete(clusterName string, opts ...client.Dele
 
 		obj := &unstructured.Unstructured{}
 		obj.SetGroupVersionKind(d.targetGVK)
-		err := client.Delete(context.Background(), obj, targetName.Namespace, targetName.Name, opts...)
+		err := client.Delete(context.Background(), obj, targetName.Namespace, targetName.Name)
 		if apierrors.IsNotFound(err) {
 			err = nil
 		}
diff --git a/pkg/controller/sync/status/status.go b/pkg/controller/sync/status/status.go
index 0614b9ad..e8451510 100644
--- a/pkg/controller/sync/status/status.go
+++ b/pkg/controller/sync/status/status.go
@@ -26,7 +26,7 @@ import (
 	apiv1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	"sigs.k8s.io/kubefed/pkg/controller/util"
 )
diff --git a/pkg/controller/sync/version/manager.go b/pkg/controller/sync/version/manager.go
index e37c29ab..33845647 100644
--- a/pkg/controller/sync/version/manager.go
+++ b/pkg/controller/sync/version/manager.go
@@ -32,7 +32,7 @@ import (
 	"k8s.io/apimachinery/pkg/util/runtime"
 	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/apimachinery/pkg/util/wait"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	"sigs.k8s.io/kubefed/pkg/apis/core/common"
 	fedv1a1 "sigs.k8s.io/kubefed/pkg/apis/core/v1alpha1"
diff --git a/pkg/controller/util/cluster_util.go b/pkg/controller/util/cluster_util.go
index 1ca1fef9..dd2a5d73 100644
--- a/pkg/controller/util/cluster_util.go
+++ b/pkg/controller/util/cluster_util.go
@@ -32,7 +32,7 @@ import (
 	restclient "k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/clientcmd"
 	"k8s.io/client-go/transport"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
 	"sigs.k8s.io/kubefed/pkg/client/generic"
diff --git a/pkg/controller/util/deletionannotation.go b/pkg/controller/util/deletionannotation.go
deleted file mode 100644
index ee85b35b..00000000
--- a/pkg/controller/util/deletionannotation.go
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
-Copyright 2021 The Kubernetes 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 util
-
-import (
-	"encoding/json"
-
-	"github.com/pkg/errors"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-)
-
-const (
-	// DeleteOptionAnnotation contains options for delete
-	// while deleting resources for member clusters.
-	DeleteOptionAnnotation = "kubefed.io/deleteoption"
-)
-
-// GetDeleteOptions return delete options from the annotation
-func GetDeleteOptions(obj *unstructured.Unstructured) ([]client.DeleteOption, error) {
-	options := make([]client.DeleteOption, 0)
-	annotations := obj.GetAnnotations()
-	if annotations == nil {
-		return options, nil
-	}
-
-	if optStr, ok := annotations[DeleteOptionAnnotation]; ok {
-		opt := &metav1.DeleteOptions{}
-		if err := json.Unmarshal([]byte(optStr), opt); err != nil {
-			return nil, errors.Wrapf(err, "could not deserialize delete options from annotation value '%s'", optStr)
-		}
-		clientOpt := &client.DeleteOptions{}
-		clientOpt.GracePeriodSeconds = opt.GracePeriodSeconds
-		clientOpt.PropagationPolicy = opt.PropagationPolicy
-		clientOpt.Preconditions = opt.Preconditions
-		options = append(options, clientOpt)
-	}
-	return options, nil
-}
-
-// ApplyDeleteOptions set the DeleteOptions on the annotation
-func ApplyDeleteOptions(obj *unstructured.Unstructured, opts ...client.DeleteOption) error {
-	opt := client.DeleteOptions{}
-	opt.ApplyOptions(opts)
-	deleteOpts := opt.AsDeleteOptions()
-	optBytes, err := json.Marshal(deleteOpts)
-	if err != nil {
-		return errors.Wrapf(err, "could not serialize delete options from object '%v'", deleteOpts)
-	}
-
-	annotations := obj.GetAnnotations()
-	if annotations == nil {
-		annotations = make(map[string]string)
-	}
-	annotations[DeleteOptionAnnotation] = string(optBytes)
-	obj.SetAnnotations(annotations)
-	return nil
-}
diff --git a/pkg/controller/util/federated_informer.go b/pkg/controller/util/federated_informer.go
index 50a6b6eb..92f07c81 100644
--- a/pkg/controller/util/federated_informer.go
+++ b/pkg/controller/util/federated_informer.go
@@ -29,7 +29,7 @@ import (
 	pkgruntime "k8s.io/apimachinery/pkg/runtime"
 	restclient "k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/cache"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	fedcommon "sigs.k8s.io/kubefed/pkg/apis/core/common"
 	fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
diff --git a/pkg/controller/webhook/federatedtypeconfig/webhook.go b/pkg/controller/webhook/federatedtypeconfig/webhook.go
index c80bfcd3..ab31c54f 100644
--- a/pkg/controller/webhook/federatedtypeconfig/webhook.go
+++ b/pkg/controller/webhook/federatedtypeconfig/webhook.go
@@ -24,7 +24,7 @@ import (
 	admissionv1beta1 "k8s.io/api/admission/v1beta1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/validation/field"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
 
 	"sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
diff --git a/pkg/controller/webhook/kubefedcluster/webhook.go b/pkg/controller/webhook/kubefedcluster/webhook.go
index 9b911bef..701e6251 100644
--- a/pkg/controller/webhook/kubefedcluster/webhook.go
+++ b/pkg/controller/webhook/kubefedcluster/webhook.go
@@ -24,7 +24,7 @@ import (
 	admissionv1beta1 "k8s.io/api/admission/v1beta1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/validation/field"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
 
 	"sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
diff --git a/pkg/controller/webhook/kubefedconfig/webhook.go b/pkg/controller/webhook/kubefedconfig/webhook.go
index bc631dcf..d3ccfe8b 100644
--- a/pkg/controller/webhook/kubefedconfig/webhook.go
+++ b/pkg/controller/webhook/kubefedconfig/webhook.go
@@ -26,7 +26,7 @@ import (
 	admissionv1beta1 "k8s.io/api/admission/v1beta1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/validation/field"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
 
 	"sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
diff --git a/pkg/features/features.go b/pkg/features/features.go
index 2ee513fd..d05b6eb3 100644
--- a/pkg/features/features.go
+++ b/pkg/features/features.go
@@ -19,7 +19,7 @@ package features
 import (
 	utilfeature "k8s.io/apiserver/pkg/util/feature"
 	"k8s.io/component-base/featuregate"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 )
 
 const (
@@ -41,6 +41,13 @@ const (
 	// Scheduler controllers which dynamically schedules workloads based on user preferences.
 	SchedulerPreferences featuregate.Feature = "SchedulerPreferences"
 
+	// owner: @kubernetes-sigs/kubefed-maintainers
+	// alpha: v0.1
+	//
+	// DNS based cross cluster service discovery.
+	// https://github.com/kubernetes/community/blob/master/contributors/design-proposals/multicluster/federated-services.md
+	CrossClusterServiceDiscovery featuregate.Feature = "CrossClusterServiceDiscovery"
+
 	// owner: @shashidharatd
 	// alpha: v0.1
 	//
@@ -64,8 +71,9 @@ func init() {
 // feature keys.  To add a new feature, define a key for it above and
 // add it here.
 var DefaultKubeFedFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
-	SchedulerPreferences:        {Default: true, PreRelease: featuregate.Alpha},
-	PushReconciler:              {Default: true, PreRelease: featuregate.Beta},
-	FederatedIngress:            {Default: false, PreRelease: featuregate.Alpha},
-	RawResourceStatusCollection: {Default: false, PreRelease: featuregate.Beta},
+	SchedulerPreferences:         {Default: true, PreRelease: featuregate.Alpha},
+	PushReconciler:               {Default: true, PreRelease: featuregate.Beta},
+	CrossClusterServiceDiscovery: {Default: false, PreRelease: featuregate.Alpha},
+	FederatedIngress:             {Default: false, PreRelease: featuregate.Alpha},
+	RawResourceStatusCollection:  {Default: false, PreRelease: featuregate.Beta},
 }
diff --git a/pkg/kubefedctl/disable.go b/pkg/kubefedctl/disable.go
index d14cde5f..2f1bb1c5 100644
--- a/pkg/kubefedctl/disable.go
+++ b/pkg/kubefedctl/disable.go
@@ -32,7 +32,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/client-go/rest"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	"sigs.k8s.io/kubefed/pkg/apis/core/typeconfig"
 	fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
diff --git a/pkg/kubefedctl/enable/enable.go b/pkg/kubefedctl/enable/enable.go
index a2ea602a..e318d16b 100644
--- a/pkg/kubefedctl/enable/enable.go
+++ b/pkg/kubefedctl/enable/enable.go
@@ -33,7 +33,7 @@ import (
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	pkgruntime "k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/rest"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	"sigs.k8s.io/kubefed/pkg/apis/core/typeconfig"
 	fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
diff --git a/pkg/kubefedctl/enable/util.go b/pkg/kubefedctl/enable/util.go
index e5e63a95..3ce0a71f 100644
--- a/pkg/kubefedctl/enable/util.go
+++ b/pkg/kubefedctl/enable/util.go
@@ -22,9 +22,6 @@ import (
 	"os"
 	"strings"
 
-	apierrors "k8s.io/apimachinery/pkg/util/errors"
-	"k8s.io/klog/v2"
-
 	"github.com/pkg/errors"
 
 	apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -92,10 +89,9 @@ func CrdForAPIResource(apiResource metav1.APIResource, validation *apiextv1.Cust
 }
 
 func LookupAPIResource(config *rest.Config, key, targetVersion string) (*metav1.APIResource, error) {
-	errs := []error{}
 	resourceLists, err := GetServerPreferredResources(config)
 	if err != nil {
-		errs = append(errs, err)
+		return nil, err
 	}
 
 	var targetResource *metav1.APIResource
@@ -128,19 +124,15 @@ func LookupAPIResource(config *rest.Config, key, targetVersion string) (*metav1.
 			}
 		}
 	}
-
 	if len(matchedResources) > 1 {
-		errs = append(errs, errors.Errorf("Multiple resources are matched by %q: %s. A group-qualified plural name must be provided.", key, strings.Join(matchedResources, ", ")))
-		return nil, apierrors.NewAggregate(errs)
+		return nil, errors.Errorf("Multiple resources are matched by %q: %s. A group-qualified plural name must be provided.", key, strings.Join(matchedResources, ", "))
 	}
 
 	if targetResource != nil {
-		klog.Warningf("Api resource found with err %v, and error could ignore.", apierrors.NewAggregate(errs))
 		return targetResource, nil
 	}
 
-	errs = append(errs, errors.Errorf("Unable to find api resource named %q.", key))
-	return nil, apierrors.NewAggregate(errs)
+	return nil, errors.Errorf("Unable to find api resource named %q.", key)
 }
 
 func NameMatchesResource(name string, apiResource metav1.APIResource, group string) bool {
@@ -169,7 +161,7 @@ func GetServerPreferredResources(config *rest.Config) ([]*metav1.APIResourceList
 
 	resourceLists, err := client.ServerPreferredResources()
 	if err != nil {
-		return resourceLists, errors.Wrap(err, "Error listing api resources")
+		return nil, errors.Wrap(err, "Error listing api resources")
 	}
 	return resourceLists, nil
 }
diff --git a/pkg/kubefedctl/federate/federate.go b/pkg/kubefedctl/federate/federate.go
index a0a9099b..6dec00aa 100644
--- a/pkg/kubefedctl/federate/federate.go
+++ b/pkg/kubefedctl/federate/federate.go
@@ -31,7 +31,7 @@ import (
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/client-go/rest"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	"sigs.k8s.io/kubefed/pkg/apis/core/typeconfig"
 	fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
diff --git a/pkg/kubefedctl/federate/util.go b/pkg/kubefedctl/federate/util.go
index 14044ed8..df4cae76 100644
--- a/pkg/kubefedctl/federate/util.go
+++ b/pkg/kubefedctl/federate/util.go
@@ -32,7 +32,7 @@ import (
 	utilyaml "k8s.io/apimachinery/pkg/util/yaml"
 	versionhelper "k8s.io/apimachinery/pkg/version"
 	"k8s.io/client-go/rest"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 	"sigs.k8s.io/yaml"
 
 	"sigs.k8s.io/kubefed/pkg/apis/core/typeconfig"
diff --git a/pkg/kubefedctl/join.go b/pkg/kubefedctl/join.go
index 57bd24c6..9f76e191 100644
--- a/pkg/kubefedctl/join.go
+++ b/pkg/kubefedctl/join.go
@@ -36,7 +36,7 @@ import (
 	"k8s.io/apimachinery/pkg/util/wait"
 	kubeclient "k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/rest"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
 	genericclient "sigs.k8s.io/kubefed/pkg/client/generic"
@@ -272,6 +272,10 @@ func joinClusterForNamespace(hostConfig, clusterConfig *rest.Config, kubefedName
 		disabledTLSValidations = append(disabledTLSValidations, fedv1b1.TLSAll)
 	}
 
+	if clusterConfig.CAData != nil {
+		caBundle = clusterConfig.CAData
+	}
+
 	kubefedCluster, err := createKubeFedCluster(client, joiningClusterName, clusterConfig.Host,
 		secret.Name, kubefedNamespace, caBundle, disabledTLSValidations, dryRun, errorOnExisting)
 	if err != nil {
diff --git a/pkg/kubefedctl/kubefedctl.go b/pkg/kubefedctl/kubefedctl.go
index a4594077..941c6580 100644
--- a/pkg/kubefedctl/kubefedctl.go
+++ b/pkg/kubefedctl/kubefedctl.go
@@ -25,7 +25,7 @@ import (
 
 	"k8s.io/client-go/tools/clientcmd"
 	apiserverflag "k8s.io/component-base/cli/flag"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	"sigs.k8s.io/kubefed/pkg/kubefedctl/enable"
 	"sigs.k8s.io/kubefed/pkg/kubefedctl/federate"
diff --git a/pkg/kubefedctl/orphaning/disable.go b/pkg/kubefedctl/orphaning/disable.go
index b034c8d1..c17a3e38 100644
--- a/pkg/kubefedctl/orphaning/disable.go
+++ b/pkg/kubefedctl/orphaning/disable.go
@@ -24,7 +24,7 @@ import (
 	"github.com/spf13/cobra"
 
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	ctlutil "sigs.k8s.io/kubefed/pkg/controller/util"
 	"sigs.k8s.io/kubefed/pkg/kubefedctl/util"
diff --git a/pkg/kubefedctl/orphaning/enable.go b/pkg/kubefedctl/orphaning/enable.go
index 600bd190..cb657c97 100644
--- a/pkg/kubefedctl/orphaning/enable.go
+++ b/pkg/kubefedctl/orphaning/enable.go
@@ -24,7 +24,7 @@ import (
 	"github.com/spf13/cobra"
 
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	ctlutil "sigs.k8s.io/kubefed/pkg/controller/util"
 	"sigs.k8s.io/kubefed/pkg/kubefedctl/util"
diff --git a/pkg/kubefedctl/orphaning/orphaning.go b/pkg/kubefedctl/orphaning/orphaning.go
index d361a8ea..876fa6d8 100644
--- a/pkg/kubefedctl/orphaning/orphaning.go
+++ b/pkg/kubefedctl/orphaning/orphaning.go
@@ -28,7 +28,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/client-go/dynamic"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	"sigs.k8s.io/kubefed/pkg/apis/core/typeconfig"
 	ctlutil "sigs.k8s.io/kubefed/pkg/controller/util"
diff --git a/pkg/kubefedctl/orphaning/status.go b/pkg/kubefedctl/orphaning/status.go
index 66ea7dbd..87096b42 100644
--- a/pkg/kubefedctl/orphaning/status.go
+++ b/pkg/kubefedctl/orphaning/status.go
@@ -21,7 +21,7 @@ import (
 
 	"github.com/spf13/cobra"
 
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	ctlutil "sigs.k8s.io/kubefed/pkg/controller/util"
 	"sigs.k8s.io/kubefed/pkg/kubefedctl/util"
diff --git a/pkg/kubefedctl/unjoin.go b/pkg/kubefedctl/unjoin.go
index f78eec3e..3899da6a 100644
--- a/pkg/kubefedctl/unjoin.go
+++ b/pkg/kubefedctl/unjoin.go
@@ -31,7 +31,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	kubeclient "k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/rest"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
 	genericclient "sigs.k8s.io/kubefed/pkg/client/generic"
diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go
index b17d9af6..3af97d6b 100644
--- a/pkg/metrics/metrics.go
+++ b/pkg/metrics/metrics.go
@@ -20,7 +20,7 @@ import (
 	"time"
 
 	"github.com/prometheus/client_golang/prometheus"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 	"sigs.k8s.io/controller-runtime/pkg/metrics"
 )
 
diff --git a/pkg/schedulingtypes/plugin.go b/pkg/schedulingtypes/plugin.go
index 75ce79ab..34f5ba8a 100644
--- a/pkg/schedulingtypes/plugin.go
+++ b/pkg/schedulingtypes/plugin.go
@@ -29,7 +29,7 @@ import (
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/util/runtime"
 	"k8s.io/client-go/tools/cache"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	"sigs.k8s.io/kubefed/pkg/apis/core/typeconfig"
 	genericclient "sigs.k8s.io/kubefed/pkg/client/generic"
diff --git a/pkg/schedulingtypes/replicascheduler.go b/pkg/schedulingtypes/replicascheduler.go
index c30af5b4..6dac0c34 100644
--- a/pkg/schedulingtypes/replicascheduler.go
+++ b/pkg/schedulingtypes/replicascheduler.go
@@ -29,7 +29,7 @@ import (
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	pkgruntime "k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/util/runtime"
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 	crclient "sigs.k8s.io/controller-runtime/pkg/client"
 
 	"sigs.k8s.io/kubefed/pkg/apis/core/typeconfig"
@@ -286,7 +286,7 @@ func schedule(planner *planner.Planner, key string, clusterNames []string, curre
 		result[clusterName] += replicas
 	}
 
-	if klog.V(4).Enabled() {
+	if klog.V(4) {
 		buf := bytes.NewBufferString(fmt.Sprintf("Schedule - %q\n", key))
 		sort.Strings(clusterNames)
 		for _, clusterName := range clusterNames {
diff --git a/scripts/deploy-kubefed.sh b/scripts/deploy-kubefed.sh
index 32e8f6ac..18ba0324 100755
--- a/scripts/deploy-kubefed.sh
+++ b/scripts/deploy-kubefed.sh
@@ -80,7 +80,7 @@ function helm-deploy-cmd {
         --set controllermanager.webhook.repository=${repo} \
         --set controllermanager.webhook.image=${image} \
         --set controllermanager.webhook.tag=${tag} \
-        --set controllermanager.featureGates.FederatedIngress=Enabled,controllermanager.featureGates.RawResourceStatusCollection=Enabled \
+        --set controllermanager.featureGates.CrossClusterServiceDiscovery=Enabled,controllermanager.featureGates.FederatedIngress=Enabled,controllermanager.featureGates.RawResourceStatusCollection=Enabled \
         ${force_redeploy_values:-} \
         --create-namespace \
         --wait"
@@ -92,6 +92,27 @@ function kubefed-admission-webhook-ready() {
   [[ "${readyReplicas}" -ge "1" ]]
 }
 
+function deployment-image-as-expected() {
+  local namespace="${1}"
+  local deployment="${2}"
+  local container="${3}"
+  local expected_image="${4}"
+
+  local deployed_image
+  deployed_image="$(kubectl -n "${namespace}" get deployment "${deployment}" -o jsonpath='{.spec.template.spec.containers[?(@.name=="'"${container}"'")].image}')"
+  [[ "${deployed_image}" == "${expected_image}" ]]
+}
+
+function check-command-installed() {
+  local cmdName="${1}"
+
+  command -v "${cmdName}" >/dev/null 2>&1 ||
+  {
+    echo "${cmdName} command not found. Please download dependencies using ${BASH_SOURCE%/*}/download-binaries.sh and install it in your PATH." >&2
+    exit 1
+  }
+}
+
 NS="${KUBEFED_NAMESPACE:-kube-federation-system}"
 IMAGE_NAME="${1:-}"
 NAMESPACED="${NAMESPACED:-}"
diff --git a/scripts/pre-commit.sh b/scripts/pre-commit.sh
index e3bc31c1..0632c567 100755
--- a/scripts/pre-commit.sh
+++ b/scripts/pre-commit.sh
@@ -38,8 +38,6 @@ E2E_TEST_CMD="${TEMP_DIR}/e2e-${PLATFORM} ${COMMON_TEST_ARGS}"
 # given control plane scope.
 IN_MEMORY_E2E_TEST_CMD="go test -v -timeout 900s -race ./test/e2e -args ${COMMON_TEST_ARGS} -in-memory-controllers=true -limited-scope-in-memory-controllers=false"
 
-KUBEFED_UPGRADE_TEST_NS="upgrade-test"
-
 function build-binaries() {
   ${MAKE_CMD} hyperfed
   ${MAKE_CMD} controller
@@ -64,43 +62,6 @@ function run-e2e-tests() {
   ${E2E_TEST_CMD}
 }
 
-function run-e2e-upgrade-test() {
-  HOST_CLUSTER="$(kubectl config current-context)"
-
-  echo "Adding a repo to install an older kubefed version"
-  helm repo add kubefed-charts https://raw.githubusercontent.com/kubernetes-sigs/kubefed/master/charts
-  helm repo  update
-
-  # Get the previous version prior to our latest stable version
-  KUBEFED_UPGRADE_TEST_VERSION=$(helm search repo kubefed-charts/kubefed  --versions | awk '{print $2}' | head -3 | tail -1)
-
-  echo "Installing an older kubefed version v${KUBEFED_UPGRADE_TEST_VERSION}"
-  helm install kubefed kubefed-charts/kubefed --namespace ${KUBEFED_UPGRADE_TEST_NS} --version=v${KUBEFED_UPGRADE_TEST_VERSION} --create-namespace --wait
-
-  deployment-image-as-expected "${KUBEFED_UPGRADE_TEST_NS}" kubefed-admission-webhook admission-webhook "quay.io/kubernetes-multicluster/kubefed:v${KUBEFED_UPGRADE_TEST_VERSION}"
-  deployment-image-as-expected "${KUBEFED_UPGRADE_TEST_NS}" kubefed-controller-manager controller-manager "quay.io/kubernetes-multicluster/kubefed:v${KUBEFED_UPGRADE_TEST_VERSION}"
-
-  echo "Upgrading kubefed to current version"
-  IMAGE_NAME="local/kubefed:e2e"
-  local repository=${IMAGE_NAME%/*}
-  local image_tag=${IMAGE_NAME##*/}
-  local image=${image_tag%:*}
-  local tag=${image_tag#*:}
-
-  helm upgrade -i kubefed charts/kubefed --namespace ${KUBEFED_UPGRADE_TEST_NS} \
-  --set controllermanager.controller.repository=${repository} \
-  --set controllermanager.controller.image=${image} \
-  --set controllermanager.controller.tag=${tag} \
-  --set controllermanager.webhook.repository=${repository} \
-  --set controllermanager.webhook.image=${image} \
-  --set controllermanager.webhook.tag=${tag} \
-  --set controllermanager.featureGates.FederatedIngress=Enabled,controllermanager.featureGates.RawResourceStatusCollection=Enabled \
-  --wait
-
-  deployment-image-as-expected "${KUBEFED_UPGRADE_TEST_NS}" kubefed-admission-webhook admission-webhook "local/kubefed:e2e"
-  deployment-image-as-expected "${KUBEFED_UPGRADE_TEST_NS}" kubefed-controller-manager controller-manager "local/kubefed:e2e"
-}
-
 function run-e2e-tests-with-in-memory-controllers() {
   ${IN_MEMORY_E2E_TEST_CMD}
 }
@@ -177,7 +138,7 @@ echo "Downloading e2e test dependencies"
 
 KIND_TAG="v1.19.4@sha256:796d09e217d93bed01ecf8502633e48fd806fe42f9d02fdd468b81cd4e3bd40b" ./scripts/create-clusters.sh
 
-declare -a join_cluster_list=()
+declare -a join_cluster_list=() 
 if [[ -z "${JOIN_CLUSTERS}" ]]; then
   for i in $(seq 2 "${NUM_CLUSTERS}"); do
     join_cluster_list+=("cluster${i}")
@@ -217,9 +178,3 @@ run-namespaced-e2e-tests
 
 echo "Deleting namespace-scoped kubefed"
 KUBEFED_NAMESPACE=foo NAMESPACED=y DELETE_CLUSTER_RESOURCE=y ./scripts/delete-kubefed.sh
-
-echo "Running e2e upgrade test"
-run-e2e-upgrade-test
-
-echo "Deleting kubefed"
-KUBEFED_NAMESPACE=${KUBEFED_UPGRADE_TEST_NS} DELETE_CLUSTER_RESOURCE=y ./scripts/delete-kubefed.sh
diff --git a/scripts/util.sh b/scripts/util.sh
index 3787fb3b..5cab71f9 100644
--- a/scripts/util.sh
+++ b/scripts/util.sh
@@ -98,24 +98,3 @@ function util::wait-for-condition() {
   fi
 }
 readonly -f util::wait-for-condition
-
-function deployment-image-as-expected() {
-  local namespace="${1}"
-  local deployment="${2}"
-  local container="${3}"
-  local expected_image="${4}"
-
-  local deployed_image
-  deployed_image="$(kubectl -n "${namespace}" get deployment "${deployment}" -o jsonpath='{.spec.template.spec.containers[?(@.name=="'"${container}"'")].image}')"
-  [[ "${deployed_image}" == "${expected_image}" ]]
-}
-
-function check-command-installed() {
-  local cmdName="${1}"
-
-  command -v "${cmdName}" >/dev/null 2>&1 ||
-  {
-    echo "${cmdName} command not found. Please download dependencies using ${BASH_SOURCE%/*}/download-binaries.sh and install it in your PATH." >&2
-    exit 1
-  }
-}


Test patch:
diff --git a/pkg/controller/sync/dispatch/retain_test.go b/pkg/controller/sync/dispatch/retain_test.go
index 83ea57d5..3359c457 100644
--- a/pkg/controller/sync/dispatch/retain_test.go
+++ b/pkg/controller/sync/dispatch/retain_test.go
@@ -20,7 +20,6 @@ import (
 	"testing"
 
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/utils/pointer"
 
 	"sigs.k8s.io/kubefed/pkg/controller/util"
 )
@@ -86,88 +85,3 @@ func TestRetainClusterFields(t *testing.T) {
 		})
 	}
 }
-
-func TestRetainHealthCheckNodePortInServiceFields(t *testing.T) {
-	tests := []struct {
-		name          string
-		desiredObj    *unstructured.Unstructured
-		clusterObj    *unstructured.Unstructured
-		retainSucceed bool
-		expectedValue *int64
-	}{
-		{
-			"cluster object has no healthCheckNodePort",
-			&unstructured.Unstructured{
-				Object: map[string]interface{}{},
-			},
-			&unstructured.Unstructured{
-				Object: map[string]interface{}{},
-			},
-			true,
-			nil,
-		},
-		{
-			"cluster object has invalid healthCheckNodePort",
-			&unstructured.Unstructured{
-				Object: map[string]interface{}{},
-			},
-			&unstructured.Unstructured{
-				Object: map[string]interface{}{
-					"spec": map[string]interface{}{
-						"healthCheckNodePort": "invalid string",
-					},
-				},
-			},
-			false,
-			nil,
-		},
-		{
-			"cluster object has healthCheckNodePort 0",
-			&unstructured.Unstructured{
-				Object: map[string]interface{}{},
-			},
-			&unstructured.Unstructured{
-				Object: map[string]interface{}{
-					"spec": map[string]interface{}{
-						"healthCheckNodePort": int64(0),
-					},
-				},
-			},
-			true,
-			nil,
-		},
-		{
-			"cluster object has healthCheckNodePort 1000",
-			&unstructured.Unstructured{
-				Object: map[string]interface{}{},
-			},
-			&unstructured.Unstructured{
-				Object: map[string]interface{}{
-					"spec": map[string]interface{}{
-						"healthCheckNodePort": int64(1000),
-					},
-				},
-			},
-			true,
-			pointer.Int64Ptr(1000),
-		},
-	}
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			if err := retainServiceFields(test.desiredObj, test.clusterObj); (err == nil) != test.retainSucceed {
-				t.Fatalf("test %s fails: unexpected returned error %v", test.name, err)
-			}
-
-			currentValue, ok, err := unstructured.NestedInt64(test.desiredObj.Object, "spec", "healthCheckNodePort")
-			if err != nil {
-				t.Fatalf("test %s fails: %v", test.name, err)
-			}
-			if !ok && test.expectedValue != nil {
-				t.Fatalf("test %s fails: expect specified healthCheckNodePort but not found", test.name)
-			}
-			if ok && (test.expectedValue == nil || *test.expectedValue != currentValue) {
-				t.Fatalf("test %s fails: unexpected current healthCheckNodePort %d", test.name, currentValue)
-			}
-		})
-	}
-}
diff --git a/pkg/controller/util/deletionannotation_test.go b/pkg/controller/util/deletionannotation_test.go
deleted file mode 100644
index 717e9f79..00000000
--- a/pkg/controller/util/deletionannotation_test.go
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
-Copyright 2021 The Kubernetes 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 util
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-)
-
-func TestDeleteOptions(t *testing.T) {
-	fedObj := &unstructured.Unstructured{}
-
-	fedObjOrphan := &unstructured.Unstructured{
-		Object: map[string]interface{}{
-			"metadata": map[string]interface{}{
-				"annotations": map[string]interface{}{
-					DeleteOptionAnnotation: "{\"propagationPolicy\":\"Orphan\"}",
-				},
-			},
-		},
-	}
-
-	fedObjGrace := &unstructured.Unstructured{
-		Object: map[string]interface{}{
-			"metadata": map[string]interface{}{
-				"annotations": map[string]interface{}{
-					DeleteOptionAnnotation: "{\"gracePeriodSeconds\":5}",
-				},
-			},
-		},
-	}
-
-	fedObjGraceAndOrphan := &unstructured.Unstructured{
-		Object: map[string]interface{}{
-			"metadata": map[string]interface{}{
-				"annotations": map[string]interface{}{
-					DeleteOptionAnnotation: "{\"propagationPolicy\":\"Orphan\",\"gracePeriodSeconds\":5}",
-				},
-			},
-		},
-	}
-
-	actOpt0 := client.DeleteOptions{}
-	opt0, _ := GetDeleteOptions(fedObj)
-	actOpt0.ApplyOptions(opt0)
-	expOpt0 := client.DeleteOptions{}
-	assert.Equal(t, expOpt0.AsDeleteOptions(), actOpt0.AsDeleteOptions())
-
-	actOpt1 := client.DeleteOptions{}
-	opt1, _ := GetDeleteOptions(fedObjOrphan)
-	actOpt1.ApplyOptions(opt1)
-	prop := metav1.DeletePropagationOrphan
-	expOpt1 := client.DeleteOptions{PropagationPolicy: &prop}
-	assert.Equal(t, expOpt1.AsDeleteOptions(), actOpt1.AsDeleteOptions())
-
-	actOpt2 := client.DeleteOptions{}
-	opt2, _ := GetDeleteOptions(fedObjGrace)
-	actOpt2.ApplyOptions(opt2)
-	seconds := int64(5)
-	expOpt2 := client.DeleteOptions{GracePeriodSeconds: &seconds}
-	assert.Equal(t, expOpt2.AsDeleteOptions(), actOpt2.AsDeleteOptions())
-
-	actOpt3 := client.DeleteOptions{}
-	opt3, _ := GetDeleteOptions(fedObjGraceAndOrphan)
-	actOpt3.ApplyOptions(opt3)
-	expOpt3 := client.DeleteOptions{GracePeriodSeconds: &seconds, PropagationPolicy: &prop}
-	assert.Equal(t, expOpt3.AsDeleteOptions(), actOpt3.AsDeleteOptions())
-}
diff --git a/test/common/bindata.go b/test/common/bindata.go
index d1b8483d..a3685ac5 100644
--- a/test/common/bindata.go
+++ b/test/common/bindata.go
@@ -394,6 +394,8 @@ spec:
     configuration: "Enabled"
   - name: SchedulerPreferences
     configuration: "Enabled"
+  - name: CrossClusterServiceDiscovery
+    configuration: "Enabled"
   - name: FederatedIngress
     configuration: "Enabled"
   clusterHealthCheck:
diff --git a/test/common/crudtester.go b/test/common/crudtester.go
index 7c5a5ab5..83d493c6 100644
--- a/test/common/crudtester.go
+++ b/test/common/crudtester.go
@@ -25,7 +25,6 @@ import (
 	jsonpatch "github.com/evanphx/json-patch"
 	"github.com/pkg/errors"
 
-	appsv1 "k8s.io/api/apps/v1"
 	apiv1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -36,8 +35,6 @@ import (
 	kubeclientset "k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/rest"
 
-	"sigs.k8s.io/controller-runtime/pkg/client"
-
 	"sigs.k8s.io/kubefed/pkg/apis/core/common"
 	"sigs.k8s.io/kubefed/pkg/apis/core/typeconfig"
 	fedv1a1 "sigs.k8s.io/kubefed/pkg/apis/core/v1alpha1"
@@ -365,66 +362,6 @@ func (c *FederatedTypeCrudTester) CheckDelete(fedObject *unstructured.Unstructur
 	}
 }
 
-func (c *FederatedTypeCrudTester) SetDeleteOption(fedObject *unstructured.Unstructured, opts ...client.DeleteOption) {
-	apiResource := c.typeConfig.GetFederatedType()
-	qualifiedName := util.NewQualifiedName(fedObject)
-	kind := apiResource.Kind
-	_, err := c.updateObject(apiResource, fedObject, func(obj *unstructured.Unstructured) {
-		err := util.ApplyDeleteOptions(obj, opts...)
-		if err != nil {
-			c.tl.Fatalf("Error apply delete options for %s %q: %v", kind, qualifiedName, err)
-		}
-	})
-	if err != nil {
-		c.tl.Fatalf("Error updating %s %q: %v", kind, qualifiedName, err)
-	}
-}
-
-func (c *FederatedTypeCrudTester) CheckReplicaSet(fedObject *unstructured.Unstructured) {
-	lb, ok, _ := unstructured.NestedStringMap(fedObject.Object, "spec", "selector", "matchLabels")
-	if !ok {
-		c.tl.Fatal("Failed to get matchLabels on the target deployment")
-	}
-
-	matchingLabels := (client.MatchingLabels)(lb)
-
-	for clusterName := range c.testClusters {
-		clusterConfig := c.testClusters[clusterName].Config
-
-		kubeClient := kubeclientset.NewForConfigOrDie(clusterConfig)
-		WaitForNamespaceOrDie(c.tl, kubeClient, clusterName, fedObject.GetNamespace(),
-			c.waitInterval, 30*time.Second)
-
-		clusterClient := genericclient.NewForConfigOrDie(clusterConfig)
-
-		c.tl.Log("Checking that the ReplicaSet still exists in every cluster")
-
-		err := wait.PollImmediate(c.waitInterval, wait.ForeverTestTimeout, func() (bool, error) {
-			objList := &appsv1.ReplicaSetList{}
-			err := clusterClient.List(context.TODO(), objList, fedObject.GetNamespace(), matchingLabels)
-			if err != nil {
-				return false, errors.Errorf("Error retrieving ReplicatSet: %v", err)
-			}
-
-			if len(objList.Items) == 0 {
-				return false, errors.Errorf("ReplicatSet was unexpectedly deleted from cluster %q", clusterName)
-			}
-
-			c.tl.Log("Checking that OwnerReferences has been removed from the ReplicaSet")
-			hasOwner := false
-			for _, rs := range objList.Items {
-				if len(rs.OwnerReferences) > 0 {
-					hasOwner = true
-				}
-			}
-			return !hasOwner, nil
-		})
-		if err != nil {
-			c.tl.Fatalf("Failed to confirm whether ReplicatSet is in cluster %q: %v", clusterName, err)
-		}
-	}
-}
-
 // CheckPropagation checks propagation for the crud tester's clients
 func (c *FederatedTypeCrudTester) CheckPropagation(fedObject *unstructured.Unstructured) {
 	federatedKind := c.typeConfig.GetFederatedType().Kind
diff --git a/test/e2e/deleteoptions.go b/test/e2e/deleteoptions.go
deleted file mode 100644
index 9abd7cb6..00000000
--- a/test/e2e/deleteoptions.go
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
-Copyright 2021 The Kubernetes 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 e2e
-
-import (
-	. "github.com/onsi/ginkgo" //nolint:stylecheck
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-
-	"sigs.k8s.io/kubefed/test/common"
-	"sigs.k8s.io/kubefed/test/e2e/framework"
-)
-
-var typeConfigName = "deployments.apps"
-
-var _ = Describe("DeleteOptions", func() {
-	f := framework.NewKubeFedFramework("delete-options")
-
-	tl := framework.NewE2ELogger()
-
-	typeConfigFixtures := common.TypeConfigFixturesOrDie(tl)
-
-	fixture := typeConfigFixtures[typeConfigName]
-
-	It("Deployment should be created and deleted successfully, but ReplicaSet that created by Deployment won't be deleted", func() {
-
-		typeConfig, testObjectsFunc := getCrudTestInput(f, tl, typeConfigName, fixture)
-		crudTester, targetObject, overrides := initCrudTest(f, tl, typeConfig, testObjectsFunc)
-		fedObject := crudTester.CheckCreate(targetObject, overrides)
-
-		By("Set PropagationPolicy property as 'Orphan' on the DeleteOptions for Federated Deployment")
-		orphan := metav1.DeletePropagationOrphan
-		prop := client.PropagationPolicy(orphan)
-
-		crudTester.SetDeleteOption(fedObject, prop)
-
-		crudTester.CheckDelete(fedObject, false)
-
-		By("Checking ReplicatSet stutus for every cluster")
-		crudTester.CheckReplicaSet(targetObject)
-	})
-})
diff --git a/test/e2e/e2e.go b/test/e2e/e2e.go
index ac762ace..a6773a8d 100644
--- a/test/e2e/e2e.go
+++ b/test/e2e/e2e.go
@@ -19,7 +19,7 @@ package e2e
 import (
 	"testing"
 
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	"sigs.k8s.io/kubefed/test/e2e/framework"
 	"sigs.k8s.io/kubefed/test/e2e/framework/ginkgowrapper"
diff --git a/test/e2e/framework/controller.go b/test/e2e/framework/controller.go
index 698a2596..3f3db32b 100644
--- a/test/e2e/framework/controller.go
+++ b/test/e2e/framework/controller.go
@@ -29,6 +29,7 @@ import (
 	"sigs.k8s.io/kubefed/pkg/controller/ingressdns"
 	"sigs.k8s.io/kubefed/pkg/controller/kubefedcluster"
 	"sigs.k8s.io/kubefed/pkg/controller/schedulingmanager"
+	"sigs.k8s.io/kubefed/pkg/controller/servicedns"
 	"sigs.k8s.io/kubefed/pkg/controller/status"
 	"sigs.k8s.io/kubefed/pkg/controller/sync"
 	"sigs.k8s.io/kubefed/pkg/controller/util"
@@ -78,6 +79,22 @@ func NewFederatedTypeConfigControllerFixture(tl common.TestLogger, config *util.
 	return f
 }
 
+// NewServiceDNSControllerFixture initializes a new service-dns controller fixture.
+func NewServiceDNSControllerFixture(tl common.TestLogger, config *util.ControllerConfig) *ControllerFixture {
+	f := &ControllerFixture{
+		stopChan: make(chan struct{}),
+	}
+	err := servicedns.StartController(config, f.stopChan)
+	if err != nil {
+		tl.Fatalf("Error starting service dns controller: %v", err)
+	}
+	err = dnsendpoint.StartServiceDNSEndpointController(config, f.stopChan)
+	if err != nil {
+		tl.Fatalf("Error starting service dns endpoint controller: %v", err)
+	}
+	return f
+}
+
 // NewIngressDNSControllerFixture initializes a new ingress-dns controller fixture.
 func NewIngressDNSControllerFixture(tl common.TestLogger, config *util.ControllerConfig) *ControllerFixture {
 	f := &ControllerFixture{
diff --git a/test/e2e/framework/test_context.go b/test/e2e/framework/test_context.go
index 8ab5676d..81adcac2 100644
--- a/test/e2e/framework/test_context.go
+++ b/test/e2e/framework/test_context.go
@@ -22,7 +22,7 @@ import (
 	"os"
 	"time"
 
-	"k8s.io/klog/v2"
+	"k8s.io/klog"
 
 	"sigs.k8s.io/kubefed/pkg/controller/util"
 )
diff --git a/test/e2e/framework/unmanaged.go b/test/e2e/framework/unmanaged.go
index 715e5c97..133398e4 100644
--- a/test/e2e/framework/unmanaged.go
+++ b/test/e2e/framework/unmanaged.go
@@ -65,10 +65,11 @@ func SetUpFeatureGates() {
 	// Enable all the feature gates when running the control plane
 	// in-memory.
 	resetDefaultFeatureGates := map[string]bool{
-		string(features.SchedulerPreferences):        true,
-		string(features.PushReconciler):              true,
-		string(features.FederatedIngress):            true,
-		string(features.RawResourceStatusCollection): true,
+		string(features.SchedulerPreferences):         true,
+		string(features.PushReconciler):               true,
+		string(features.CrossClusterServiceDiscovery): true,
+		string(features.FederatedIngress):             true,
+		string(features.RawResourceStatusCollection):  true,
 	}
 	err := utilfeature.DefaultMutableFeatureGate.SetFromMap(resetDefaultFeatureGates)
 	Expect(err).NotTo(HaveOccurred())
diff --git a/test/e2e/servicedns.go b/test/e2e/servicedns.go
new file mode 100644
index 00000000..0a054854
--- /dev/null
+++ b/test/e2e/servicedns.go
@@ -0,0 +1,256 @@
+/*
+Copyright 2018 The Kubernetes 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 e2e
+
+import (
+	"context"
+	"fmt"
+	"sort"
+	"strings"
+
+	"github.com/pkg/errors"
+
+	apiv1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	pkgruntime "k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/util/wait"
+
+	fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
+	dnsv1a1 "sigs.k8s.io/kubefed/pkg/apis/multiclusterdns/v1alpha1"
+	genericclient "sigs.k8s.io/kubefed/pkg/client/generic"
+	"sigs.k8s.io/kubefed/pkg/controller/dnsendpoint"
+	"sigs.k8s.io/kubefed/test/common"
+	"sigs.k8s.io/kubefed/test/e2e/framework"
+
+	. "github.com/onsi/ginkgo" //nolint:stylecheck
+)
+
+var _ = Describe("ServiceDNS", func() {
+	f := framework.NewKubeFedFramework("multicluster-service-dns")
+	tl := framework.NewE2ELogger()
+
+	const userAgent = "test-service-dns"
+	const baseName = "test-service-dns-"
+	const federationPrefix = "galactic"
+	const Domain = "example.com"
+
+	var client genericclient.Client
+	var clusterRegionZones map[string]fedv1b1.KubeFedClusterStatus
+	var federation string
+	var namespace string
+
+	objectGetter := func(namespace, name string) (pkgruntime.Object, error) {
+		serviceDNSRecords := &dnsv1a1.ServiceDNSRecord{}
+		err := client.Get(context.TODO(), serviceDNSRecords, namespace, name)
+		return serviceDNSRecords, err
+	}
+
+	BeforeEach(func() {
+		client = f.Client(userAgent)
+		namespace = f.TestNamespaceName()
+
+		clusterRegionZones = ensureClustersHaveRegionZoneAttributes(tl, client, f.KubeFedSystemNamespace())
+		if framework.TestContext.RunControllers() {
+			fixture := framework.NewServiceDNSControllerFixture(tl, f.ControllerConfig())
+			f.RegisterFixture(fixture)
+		}
+		f.EnsureTestNamespacePropagation()
+		domainObj := common.NewDomainObject(federationPrefix, Domain)
+		domainObj.Namespace = f.KubeFedSystemNamespace()
+		err := client.Create(context.TODO(), domainObj)
+		framework.ExpectNoError(err, "Error creating Domain object")
+		federation = domainObj.Name
+	})
+
+	AfterEach(func() {
+		domainObj := &dnsv1a1.Domain{}
+		err := client.Delete(context.TODO(), domainObj, f.KubeFedSystemNamespace(), federation)
+		framework.ExpectNoError(err, "Error deleting Domain object")
+	})
+
+	It("ServiceDNS object status should be updated correctly when there are no service and/or endpoint in member clusters", func() {
+		By("Creating the ServiceDNS object")
+		serviceDNS := common.NewServiceDNSObject(baseName, namespace)
+		serviceDNS.Spec.DomainRef = federation
+		err := client.Create(context.TODO(), serviceDNS)
+		framework.ExpectNoError(err, "Error creating ServiceDNS object: %v", serviceDNS)
+
+		serviceDNSStatus := dnsv1a1.ServiceDNSRecordStatus{Domain: Domain, DNS: []dnsv1a1.ClusterDNS{}}
+		for _, clusterName := range f.ClusterNames(userAgent) {
+			serviceDNSStatus.DNS = append(serviceDNSStatus.DNS, dnsv1a1.ClusterDNS{
+				Cluster: clusterName,
+				Region:  *clusterRegionZones[clusterName].Region,
+				Zones:   clusterRegionZones[clusterName].Zones,
+			})
+		}
+		sort.Slice(serviceDNSStatus.DNS, func(i, j int) bool {
+			return serviceDNSStatus.DNS[i].Cluster < serviceDNSStatus.DNS[j].Cluster
+		})
+
+		serviceDNS.Status = serviceDNSStatus
+		By("Waiting for the ServiceDNS object to have correct status")
+		framework.WaitForObject(tl, namespace, serviceDNS.Name, objectGetter, serviceDNS, common.Equivalent)
+	})
+
+	Context("When ServiceDNS object is created", func() {
+		const (
+			RecordTypeA = "A"
+			RecordTTL   = 300
+		)
+
+		It("DNSEndpoint object should be created with correct status when ServiceDNS object is created", func() {
+			By("Creating the ServiceDNS object")
+			serviceDNS := common.NewServiceDNSObject(baseName, namespace)
+			serviceDNS.Spec.DomainRef = federation
+			serviceDNS.Spec.RecordTTL = RecordTTL
+			err := client.Create(context.TODO(), serviceDNS)
+			framework.ExpectNoError(err, "Error creating ServiceDNS object %v", serviceDNS)
+			name := serviceDNS.Name
+
+			By("Creating corresponding service and endpoint for the ServiceDNS object in member clusters")
+			serviceDNSStatus := createClusterServiceAndEndpoints(f, name, namespace, Domain, clusterRegionZones)
+			serviceDNS.Status = *serviceDNSStatus
+
+			By("Waiting for the ServiceDNS object to have correct status")
+			framework.WaitForObject(tl, namespace, name, objectGetter, serviceDNS, common.Equivalent)
+
+			By("Waiting for the DNSEndpoint object to be created")
+			endpointObjectGetter := func(namespace, name string) (pkgruntime.Object, error) {
+				dnsEndpoint := &dnsv1a1.DNSEndpoint{}
+				err := client.Get(context.TODO(), dnsEndpoint, namespace, name)
+				return dnsEndpoint, err
+			}
+
+			endpoints := []*dnsv1a1.Endpoint{}
+			for _, cluster := range serviceDNS.Status.DNS {
+				zones := clusterRegionZones[cluster.Cluster].Zones
+				region := *clusterRegionZones[cluster.Cluster].Region
+				lbs := dnsendpoint.ExtractLoadBalancerTargets(cluster.LoadBalancer)
+
+				endpoint := common.NewDNSEndpoint(
+					strings.Join([]string{name, namespace, federation, "svc", zones[0], region, Domain}, "."),
+					lbs, RecordTypeA, RecordTTL)
+				endpoints = append(endpoints, endpoint)
+				endpoint = common.NewDNSEndpoint(
+					strings.Join([]string{name, namespace, federation, "svc", region, Domain}, "."),
+					lbs, RecordTypeA, RecordTTL)
+				endpoints = append(endpoints, endpoint)
+				endpoint = common.NewDNSEndpoint(
+					strings.Join([]string{name, namespace, federation, "svc", Domain}, "."),
+					lbs, RecordTypeA, RecordTTL)
+				endpoints = append(endpoints, endpoint)
+			}
+			desiredDNSEndpoint := &dnsv1a1.DNSEndpoint{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "service-" + name,
+					Namespace: namespace,
+				},
+				Spec: dnsv1a1.DNSEndpointSpec{
+					Endpoints: dnsendpoint.DedupeAndMergeEndpoints(endpoints),
+				},
+			}
+
+			framework.WaitForObject(tl, namespace, "service-"+name, endpointObjectGetter, desiredDNSEndpoint, common.Equivalent)
+		})
+	})
+})
+
+func createClusterServiceAndEndpoints(f framework.KubeFedFramework, name, namespace string, domain string,
+	clusterRegionZones map[string]fedv1b1.KubeFedClusterStatus) *dnsv1a1.ServiceDNSRecordStatus {
+	const userAgent = "test-service-dns"
+
+	service := common.NewServiceObject(name, namespace)
+	endpoint := common.NewEndpointObject(name, namespace)
+	lbsuffix := 1
+
+	serviceDNSStatus := &dnsv1a1.ServiceDNSRecordStatus{Domain: domain, DNS: []dnsv1a1.ClusterDNS{}}
+	for clusterName, client := range f.ClusterKubeClients(userAgent) {
+		clusterLb := fmt.Sprintf("10.20.30.%d", lbsuffix)
+		lbsuffix++
+
+		loadbalancerStatus := apiv1.LoadBalancerStatus{Ingress: []apiv1.LoadBalancerIngress{{IP: clusterLb}}}
+		serviceDNSStatus.DNS = append(serviceDNSStatus.DNS, dnsv1a1.ClusterDNS{
+			Cluster:      clusterName,
+			LoadBalancer: loadbalancerStatus,
+			Region:       *clusterRegionZones[clusterName].Region,
+			Zones:        clusterRegionZones[clusterName].Zones,
+		})
+
+		common.WaitForNamespaceOrDie(framework.NewE2ELogger(), client, clusterName, namespace,
+			framework.PollInterval, framework.TestContext.SingleCallTimeout)
+
+		createdService, err := client.CoreV1().Services(namespace).Create(context.Background(), service, metav1.CreateOptions{})
+		framework.ExpectNoError(err, "Error creating service in cluster %q", clusterName)
+
+		createdService.Status = apiv1.ServiceStatus{
+			LoadBalancer: loadbalancerStatus,
+		}
+
+		// Fake out provisioning LoadBalancer by updating the service status in member cluster.
+		_, err = client.CoreV1().Services(namespace).UpdateStatus(context.Background(), createdService, metav1.UpdateOptions{})
+		framework.ExpectNoError(err, "Error updating service status in cluster %q", clusterName)
+
+		// Fake out pods backing service by creating endpoint in member cluster.
+		_, err = client.CoreV1().Endpoints(namespace).Create(context.Background(), endpoint, metav1.CreateOptions{})
+		framework.ExpectNoError(err, "Error creating endpoint in cluster %q", clusterName)
+	}
+
+	sort.Slice(serviceDNSStatus.DNS, func(i, j int) bool {
+		return serviceDNSStatus.DNS[i].Cluster < serviceDNSStatus.DNS[j].Cluster
+	})
+
+	return serviceDNSStatus
+}
+
+func ensureClustersHaveRegionZoneAttributes(tl common.TestLogger, client genericclient.Client, clusterNamespace string) map[string]fedv1b1.KubeFedClusterStatus {
+	clusters := &fedv1b1.KubeFedClusterList{}
+	err := client.List(context.TODO(), clusters, clusterNamespace)
+	framework.ExpectNoError(err, "Error listing federated clusters")
+
+	clusterRegionZones := make(map[string]fedv1b1.KubeFedClusterStatus)
+	for i, cluster := range clusters.Items {
+		err := wait.PollImmediate(framework.PollInterval, framework.TestContext.SingleCallTimeout, func() (bool, error) {
+			region := fmt.Sprintf("r%d", i)
+			cluster.Status.Region = &region
+			cluster.Status.Zones = []string{fmt.Sprintf("z%d", i)}
+
+			err := client.UpdateStatus(context.TODO(), &cluster)
+			if apierrors.IsConflict(err) {
+				clusterName := cluster.Name
+				tl.Logf("Failed to update status for federated cluster %q: %v", clusterName, err)
+				err = client.Get(context.TODO(), &cluster, clusterNamespace, clusterName)
+				if err != nil {
+					return false, errors.Wrapf(err, "failed to retrieve cluster object")
+				}
+				return false, nil
+			}
+			if err != nil {
+				return false, err
+			}
+			return true, nil
+		})
+		framework.ExpectNoError(err, "Error updating federated cluster status")
+
+		clusterRegionZones[cluster.Name] = fedv1b1.KubeFedClusterStatus{
+			Region: cluster.Status.Region,
+			Zones:  cluster.Status.Zones,
+		}
+	}
+
+	return clusterRegionZones
+}