diff --git a/config/core/resources/certificate.yaml b/config/core/resources/certificate.yaml index 9080b896013c..68e4f385ddf2 100644 --- a/config/core/resources/certificate.yaml +++ b/config/core/resources/certificate.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -apiVersion: apiextensions.k8s.io/v1beta1 +apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: certificates.networking.internal.knative.dev @@ -21,7 +21,27 @@ metadata: knative.dev/crd-install: "true" spec: group: networking.internal.knative.dev - version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + # this is a work around so we don't need to flush out the + # schema for each version at this time + # + # see issue: https://github.com/knative/serving/issues/912 + x-kubernetes-preserve-unknown-fields: true + additionalPrinterColumns: + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type==\"Ready\")].reason" names: kind: Certificate plural: certificates @@ -32,12 +52,3 @@ spec: shortNames: - kcert scope: Namespaced - subresources: - status: {} - additionalPrinterColumns: - - name: Ready - type: string - JSONPath: ".status.conditions[?(@.type==\"Ready\")].status" - - name: Reason - type: string - JSONPath: ".status.conditions[?(@.type==\"Ready\")].reason" diff --git a/config/core/resources/configuration.yaml b/config/core/resources/configuration.yaml index bd56c42e497b..d2a47986f9ab 100644 --- a/config/core/resources/configuration.yaml +++ b/config/core/resources/configuration.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -apiVersion: apiextensions.k8s.io/v1beta1 +apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: configurations.serving.knative.dev @@ -22,24 +22,38 @@ metadata: duck.knative.dev/podspecable: "true" spec: group: serving.knative.dev - preserveUnknownFields: false - validation: - openAPIV3Schema: - type: object - # this is a work around so we don't need to flush out the - # schema for each version at this time - # - # see issue: https://github.com/knative/serving/issues/912 - x-kubernetes-preserve-unknown-fields: true versions: - - name: v1alpha1 + - &version + name: v1alpha1 served: true storage: false - - name: v1beta1 - served: true - storage: false - - name: v1 - served: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + # this is a work around so we don't need to flush out the + # schema for each version at this time + # + # see issue: https://github.com/knative/serving/issues/912 + x-kubernetes-preserve-unknown-fields: true + additionalPrinterColumns: + - name: LatestCreated + type: string + jsonPath: .status.latestCreatedRevisionName + - name: LatestReady + type: string + jsonPath: .status.latestReadyRevisionName + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + - << : *version + name: v1beta1 + - << : *version + name: v1 storage: true names: kind: Configuration @@ -53,24 +67,11 @@ spec: - config - cfg scope: Namespaced - subresources: - status: {} conversion: strategy: Webhook - webhookClientConfig: - service: - name: webhook - namespace: knative-serving - additionalPrinterColumns: - - name: LatestCreated - type: string - JSONPath: .status.latestCreatedRevisionName - - name: LatestReady - type: string - JSONPath: .status.latestReadyRevisionName - - name: Ready - type: string - JSONPath: ".status.conditions[?(@.type=='Ready')].status" - - name: Reason - type: string - JSONPath: ".status.conditions[?(@.type=='Ready')].reason" + webhook: + conversionReviewVersions: ["v1", "v1beta1"] + clientConfig: + service: + name: webhook + namespace: knative-serving diff --git a/config/core/resources/ingress.yaml b/config/core/resources/ingress.yaml index 26e95465f7e9..e9bc3af52b0a 100644 --- a/config/core/resources/ingress.yaml +++ b/config/core/resources/ingress.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -apiVersion: apiextensions.k8s.io/v1beta1 +apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: ingresses.networking.internal.knative.dev @@ -25,6 +25,23 @@ spec: - name: v1alpha1 served: true storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + # this is a work around so we don't need to flush out the + # schema for each version at this time + # + # see issue: https://github.com/knative/serving/issues/912 + x-kubernetes-preserve-unknown-fields: true + additionalPrinterColumns: + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" names: kind: Ingress plural: ingresses @@ -36,12 +53,3 @@ spec: - kingress - king scope: Namespaced - subresources: - status: {} - additionalPrinterColumns: - - name: Ready - type: string - JSONPath: ".status.conditions[?(@.type=='Ready')].status" - - name: Reason - type: string - JSONPath: ".status.conditions[?(@.type=='Ready')].reason" diff --git a/config/core/resources/metric.yaml b/config/core/resources/metric.yaml index 5f57a94a536c..5952b74b2d48 100644 --- a/config/core/resources/metric.yaml +++ b/config/core/resources/metric.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -apiVersion: apiextensions.k8s.io/v1beta1 +apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: metrics.autoscaling.internal.knative.dev @@ -21,7 +21,27 @@ metadata: knative.dev/crd-install: "true" spec: group: autoscaling.internal.knative.dev - version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + # this is a work around so we don't need to flush out the + # schema for each version at this time + # + # see issue: https://github.com/knative/serving/issues/912 + x-kubernetes-preserve-unknown-fields: true + additionalPrinterColumns: + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" names: kind: Metric plural: metrics @@ -30,12 +50,3 @@ spec: - knative-internal - autoscaling scope: Namespaced - subresources: - status: {} - additionalPrinterColumns: - - name: Ready - type: string - JSONPath: ".status.conditions[?(@.type=='Ready')].status" - - name: Reason - type: string - JSONPath: ".status.conditions[?(@.type=='Ready')].reason" diff --git a/config/core/resources/podautoscaler.yaml b/config/core/resources/podautoscaler.yaml index c8340849c900..ca4dbdaea22f 100644 --- a/config/core/resources/podautoscaler.yaml +++ b/config/core/resources/podautoscaler.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -apiVersion: apiextensions.k8s.io/v1beta1 +apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: podautoscalers.autoscaling.internal.knative.dev @@ -25,6 +25,29 @@ spec: - name: v1alpha1 served: true storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + # this is a work around so we don't need to flush out the + # schema for each version at this time + # + # see issue: https://github.com/knative/serving/issues/912 + x-kubernetes-preserve-unknown-fields: true + additionalPrinterColumns: + - name: DesiredScale + type: integer + jsonPath: ".status.desiredScale" + - name: ActualScale + type: integer + jsonPath: ".status.actualScale" + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" names: kind: PodAutoscaler plural: podautoscalers @@ -36,18 +59,3 @@ spec: - kpa - pa scope: Namespaced - subresources: - status: {} - additionalPrinterColumns: - - name: DesiredScale - type: integer - JSONPath: ".status.desiredScale" - - name: ActualScale - type: integer - JSONPath: ".status.actualScale" - - name: Ready - type: string - JSONPath: ".status.conditions[?(@.type=='Ready')].status" - - name: Reason - type: string - JSONPath: ".status.conditions[?(@.type=='Ready')].reason" diff --git a/config/core/resources/revision.yaml b/config/core/resources/revision.yaml index 5a8da20f0c9d..1767fb546f5d 100644 --- a/config/core/resources/revision.yaml +++ b/config/core/resources/revision.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -apiVersion: apiextensions.k8s.io/v1beta1 +apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: revisions.serving.knative.dev @@ -21,24 +21,41 @@ metadata: knative.dev/crd-install: "true" spec: group: serving.knative.dev - preserveUnknownFields: false - validation: - openAPIV3Schema: - type: object - # this is a work around so we don't need to flush out the - # schema for each version at this time - # - # see issue: https://github.com/knative/serving/issues/912 - x-kubernetes-preserve-unknown-fields: true versions: - - name: v1alpha1 + - &version + name: v1alpha1 served: true storage: false - - name: v1beta1 - served: true - storage: false - - name: v1 - served: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + # this is a work around so we don't need to flush out the + # schema for each version at this time + # + # see issue: https://github.com/knative/serving/issues/912 + x-kubernetes-preserve-unknown-fields: true + additionalPrinterColumns: + - name: Config Name + type: string + jsonPath: ".metadata.labels['serving\\.knative\\.dev/configuration']" + - name: K8s Service Name + type: string + jsonPath: ".status.serviceName" + - name: Generation + type: string # int in string form :( + jsonPath: ".metadata.labels['serving\\.knative\\.dev/configurationGeneration']" + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + - << : *version + name: v1beta1 + - << : *version + name: v1 storage: true names: kind: Revision @@ -51,27 +68,11 @@ spec: shortNames: - rev scope: Namespaced - subresources: - status: {} conversion: strategy: Webhook - webhookClientConfig: - service: - name: webhook - namespace: knative-serving - additionalPrinterColumns: - - name: Config Name - type: string - JSONPath: ".metadata.labels['serving\\.knative\\.dev/configuration']" - - name: K8s Service Name - type: string - JSONPath: ".status.serviceName" - - name: Generation - type: string # int in string form :( - JSONPath: ".metadata.labels['serving\\.knative\\.dev/configurationGeneration']" - - name: Ready - type: string - JSONPath: ".status.conditions[?(@.type=='Ready')].status" - - name: Reason - type: string - JSONPath: ".status.conditions[?(@.type=='Ready')].reason" + webhook: + conversionReviewVersions: ["v1", "v1beta1"] + clientConfig: + service: + name: webhook + namespace: knative-serving diff --git a/config/core/resources/route.yaml b/config/core/resources/route.yaml index 3138036a5243..96f9d809c098 100644 --- a/config/core/resources/route.yaml +++ b/config/core/resources/route.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -apiVersion: apiextensions.k8s.io/v1beta1 +apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: routes.serving.knative.dev @@ -22,24 +22,35 @@ metadata: duck.knative.dev/addressable: "true" spec: group: serving.knative.dev - preserveUnknownFields: false - validation: - openAPIV3Schema: - type: object - # this is a work around so we don't need to flush out the - # schema for each version at this time - # - # see issue: https://github.com/knative/serving/issues/912 - x-kubernetes-preserve-unknown-fields: true versions: - - name: v1alpha1 + - &version + name: v1alpha1 served: true storage: false - - name: v1beta1 - served: true - storage: false - - name: v1 - served: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + # this is a work around so we don't need to flush out the + # schema for each version at this time + # + # see issue: https://github.com/knative/serving/issues/912 + x-kubernetes-preserve-unknown-fields: true + additionalPrinterColumns: + - name: URL + type: string + jsonPath: .status.url + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + - << : *version + name: v1beta1 + - << : *version + name: v1 storage: true names: kind: Route @@ -52,21 +63,11 @@ spec: shortNames: - rt scope: Namespaced - subresources: - status: {} conversion: strategy: Webhook - webhookClientConfig: - service: - name: webhook - namespace: knative-serving - additionalPrinterColumns: - - name: URL - type: string - JSONPath: .status.url - - name: Ready - type: string - JSONPath: ".status.conditions[?(@.type=='Ready')].status" - - name: Reason - type: string - JSONPath: ".status.conditions[?(@.type=='Ready')].reason" + webhook: + conversionReviewVersions: ["v1", "v1beta1"] + clientConfig: + service: + name: webhook + namespace: knative-serving diff --git a/config/core/resources/serverlessservice.yaml b/config/core/resources/serverlessservice.yaml index c7e7326c9a54..cfb327f005c7 100644 --- a/config/core/resources/serverlessservice.yaml +++ b/config/core/resources/serverlessservice.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -apiVersion: apiextensions.k8s.io/v1beta1 +apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: serverlessservices.networking.internal.knative.dev @@ -25,6 +25,35 @@ spec: - name: v1alpha1 served: true storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + # this is a work around so we don't need to flush out the + # schema for each version at this time + # + # see issue: https://github.com/knative/serving/issues/912 + x-kubernetes-preserve-unknown-fields: true + additionalPrinterColumns: + - name: Mode + type: string + jsonPath: ".spec.mode" + - name: Activators + type: integer + jsonPath: ".spec.numActivators" + - name: ServiceName + type: string + jsonPath: ".status.serviceName" + - name: PrivateServiceName + type: string + jsonPath: ".status.privateServiceName" + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" names: kind: ServerlessService plural: serverlessservices @@ -35,24 +64,3 @@ spec: shortNames: - sks scope: Namespaced - subresources: - status: {} - additionalPrinterColumns: - - name: Mode - type: string - JSONPath: ".spec.mode" - - name: Activators - type: integer - JSONPath: ".spec.numActivators" - - name: ServiceName - type: string - JSONPath: ".status.serviceName" - - name: PrivateServiceName - type: string - JSONPath: ".status.privateServiceName" - - name: Ready - type: string - JSONPath: ".status.conditions[?(@.type=='Ready')].status" - - name: Reason - type: string - JSONPath: ".status.conditions[?(@.type=='Ready')].reason" diff --git a/config/core/resources/service.yaml b/config/core/resources/service.yaml index d6bde97d6ea0..9623fbd165e8 100644 --- a/config/core/resources/service.yaml +++ b/config/core/resources/service.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -apiVersion: apiextensions.k8s.io/v1beta1 +apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: services.serving.knative.dev @@ -23,24 +23,41 @@ metadata: duck.knative.dev/podspecable: "true" spec: group: serving.knative.dev - preserveUnknownFields: false - validation: - openAPIV3Schema: - type: object - # this is a work around so we don't need to flush out the - # schema for each version at this time - # - # see issue: https://github.com/knative/serving/issues/912 - x-kubernetes-preserve-unknown-fields: true versions: - - name: v1alpha1 + - &version + name: v1alpha1 served: true storage: false - - name: v1beta1 - served: true - storage: false - - name: v1 - served: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + # this is a work around so we don't need to flush out the + # schema for each version at this time + # + # see issue: https://github.com/knative/serving/issues/912 + x-kubernetes-preserve-unknown-fields: true + additionalPrinterColumns: + - name: URL + type: string + jsonPath: .status.url + - name: LatestCreated + type: string + jsonPath: .status.latestCreatedRevisionName + - name: LatestReady + type: string + jsonPath: .status.latestReadyRevisionName + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + - << : *version + name: v1beta1 + - << : *version + name: v1 storage: true names: kind: Service @@ -54,27 +71,11 @@ spec: - kservice - ksvc scope: Namespaced - subresources: - status: {} conversion: strategy: Webhook - webhookClientConfig: - service: - name: webhook - namespace: knative-serving - additionalPrinterColumns: - - name: URL - type: string - JSONPath: .status.url - - name: LatestCreated - type: string - JSONPath: .status.latestCreatedRevisionName - - name: LatestReady - type: string - JSONPath: .status.latestReadyRevisionName - - name: Ready - type: string - JSONPath: ".status.conditions[?(@.type=='Ready')].status" - - name: Reason - type: string - JSONPath: ".status.conditions[?(@.type=='Ready')].reason" + webhook: + conversionReviewVersions: ["v1", "v1beta1"] + clientConfig: + service: + name: webhook + namespace: knative-serving diff --git a/go.mod b/go.mod index d5111034b206..13bebe95cbe7 100644 --- a/go.mod +++ b/go.mod @@ -38,9 +38,9 @@ require ( k8s.io/code-generator v0.18.0 k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29 knative.dev/caching v0.0.0-20200606210318-787aec80f71c - knative.dev/networking v0.0.0-20200619041525-1faac2ec5d38 - knative.dev/pkg v0.0.0-20200619182625-b6a13e2894ee - knative.dev/test-infra v0.0.0-20200619200026-0b0587234302 + knative.dev/networking v0.0.0-20200622163826-421cd312c651 + knative.dev/pkg v0.0.0-20200622193027-602857dcc5f4 + knative.dev/test-infra v0.0.0-20200622185426-9cd9379661ea ) replace ( diff --git a/go.sum b/go.sum index f03154251af0..866c73b7cdc5 100644 --- a/go.sum +++ b/go.sum @@ -71,14 +71,12 @@ github.com/Azure/go-autorest v13.4.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSW github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg= github.com/Azure/go-autorest/autorest v0.2.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest v0.9.3 h1:OZEIaBbMdUE/Js+BQKlpO81XlISgipr6yDJ+PSwsgi4= github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= 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.1.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= -github.com/Azure/go-autorest/autorest/adal v0.8.1 h1:pZdL8o72rK+avFWl+p9nE8RWi1JInZrWJYlnpfXJwHk= github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= 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= @@ -93,6 +91,7 @@ github.com/Azure/go-autorest/autorest/to v0.1.0/go.mod h1:GunWKJp1AEqgMaGLV+iocm github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc= github.com/Azure/go-autorest/autorest/to v0.3.0 h1:zebkZaadz7+wIQYgC7GXaz3Wb28yKYfVkkBKwc38VF8= github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= +github.com/Azure/go-autorest/autorest/validation v0.1.0 h1:ISSNzGUh+ZSzizJWOWzs8bwpXIePbGLW4z/AmUFGH5A= github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8= github.com/Azure/go-autorest/autorest/validation v0.2.0 h1:15vMO4y76dehZSq7pAaOLQxC6dZYsSrj2GQpflyM/L4= github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= @@ -161,10 +160,8 @@ github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi github.com/aws/aws-sdk-go v1.19.45/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.23.22/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.27.1 h1:MXnqY6SlWySaZAqNnXThOvjRFdiiOuKtC6i7baFdNdU= github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.29.32/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= -github.com/aws/aws-sdk-go v1.29.34 h1:yrzwfDaZFe9oT4AmQeNNunSQA7c0m2chz0B43+bJ1ok= github.com/aws/aws-sdk-go v1.29.34/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= github.com/aws/aws-sdk-go v1.30.4/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go v1.30.5 h1:i+sSesaMrSxiUt3NJddOApe2mXK+VNBgfcmRTvNFrXM= @@ -178,7 +175,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -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= @@ -898,7 +894,6 @@ 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.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -1382,8 +1377,8 @@ knative.dev/caching v0.0.0-20200606210318-787aec80f71c h1:4w9QOkxY1VNH/82LIefx8u knative.dev/caching v0.0.0-20200606210318-787aec80f71c/go.mod h1:rWD+0zSqcE7L//jJyt866xtXsOi9hCdB0FoD4w2Rygg= knative.dev/eventing-contrib v0.6.1-0.20190723221543-5ce18048c08b/go.mod h1:SnXZgSGgMSMLNFTwTnpaOH7hXDzTFtw0J8OmHflNx3g= knative.dev/eventing-contrib v0.11.2/go.mod h1:SnXZgSGgMSMLNFTwTnpaOH7hXDzTFtw0J8OmHflNx3g= -knative.dev/networking v0.0.0-20200619041525-1faac2ec5d38 h1:wxMxsdf8VI45h7ARlO1YOWjsielbBL3hl1FM2VCavUo= -knative.dev/networking v0.0.0-20200619041525-1faac2ec5d38/go.mod h1:ruIIg4U34uk2+LdS09aibF4ewgWn4PzwYcYCeyCY4K0= +knative.dev/networking v0.0.0-20200622163826-421cd312c651 h1:k0diNnPM6Bh7kvnHOGFOtt5VGYjD2ksjh8gh7J8TSTU= +knative.dev/networking v0.0.0-20200622163826-421cd312c651/go.mod h1:Jkh1EZcfkw1zcsrjLslk7/vsaAiG4LRyRP1l494UMEg= knative.dev/pkg v0.0.0-20191101194912-56c2594e4f11/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q= knative.dev/pkg v0.0.0-20191111150521-6d806b998379/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q= knative.dev/pkg v0.0.0-20200207155214-fef852970f43/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q= @@ -1395,11 +1390,10 @@ knative.dev/pkg v0.0.0-20200519155757-14eb3ae3a5a7/go.mod h1:QgNZTxnwpB/oSpNcfnL knative.dev/pkg v0.0.0-20200520073958-94316e20e860/go.mod h1:QgNZTxnwpB/oSpNcfnLVlw+WpEwwyKAvJlvR3hgeltA= knative.dev/pkg v0.0.0-20200603222317-b79e4a24ca50/go.mod h1:8IfPj/lpuKHHg82xZCl2wuFZ3BM96To72sN1W8T9wjQ= knative.dev/pkg v0.0.0-20200611204322-2ddcfef739a2/go.mod h1:rA+FklsrVahwF4a+D63NyHJlzDoAFH81K4J5CYuE3bA= -knative.dev/pkg v0.0.0-20200618002824-96c250871fac/go.mod h1:4ty6MSlNjZk5qBaGb3Gt4gopjMD4gRknfTABblcFpQ8= -knative.dev/pkg v0.0.0-20200619020725-7df8fc5d7743 h1:W1NKMizoXYYX5e2mkFXnn21T7X6ROKKwL8YetGu7xCQ= knative.dev/pkg v0.0.0-20200619020725-7df8fc5d7743/go.mod h1:DquzK0hsLDcg2q63Sn+CngAyRwv4cKMpt5F19YzBfb0= -knative.dev/pkg v0.0.0-20200619182625-b6a13e2894ee h1:nudgQQo72NJuzaB4gBHE7Vm2BJVBTmpRt7M2psgvKxY= -knative.dev/pkg v0.0.0-20200619182625-b6a13e2894ee/go.mod h1:DquzK0hsLDcg2q63Sn+CngAyRwv4cKMpt5F19YzBfb0= +knative.dev/pkg v0.0.0-20200622135826-98f8a949a106/go.mod h1:DquzK0hsLDcg2q63Sn+CngAyRwv4cKMpt5F19YzBfb0= +knative.dev/pkg v0.0.0-20200622193027-602857dcc5f4 h1:UmhaAdTVmFB76WmgH2KDthOdOd4S85lbAbz5qWwGzUk= +knative.dev/pkg v0.0.0-20200622193027-602857dcc5f4/go.mod h1:DquzK0hsLDcg2q63Sn+CngAyRwv4cKMpt5F19YzBfb0= knative.dev/sample-controller v0.0.0-20200510050845-bf7c19498b7e/go.mod h1:D2ZDLrR9Dq9LiiVN7TatzI7WMcEPgk1MHbbhgBKE6W8= knative.dev/test-infra v0.0.0-20200407185800-1b88cb3b45a5/go.mod h1:xcdUkMJrLlBswIZqL5zCuBFOC22WIPMQoVX1L35i0vQ= knative.dev/test-infra v0.0.0-20200505052144-5ea2f705bb55/go.mod h1:WqF1Azka+FxPZ20keR2zCNtiQA1MP9ZB4BH4HuI+SIU= @@ -1410,12 +1404,10 @@ knative.dev/test-infra v0.0.0-20200519015156-82551620b0a9/go.mod h1:A5b2OAXTOeHT knative.dev/test-infra v0.0.0-20200519161858-554a95a37986/go.mod h1:LeNa1Wvn47efeQUkpkn3XG7Fx9Ga+rhAP13SZyjaEGg= knative.dev/test-infra v0.0.0-20200522180958-6a0a9b9d893a/go.mod h1:n9eQkzmSNj8BiqNFl1lzoz68D09uMeJfyOjc132Gbik= knative.dev/test-infra v0.0.0-20200606045118-14ebc4a42974/go.mod h1://I6IZIF0QDgs5wotU243ZZ5cTpm6/GthayjUenBBc0= -knative.dev/test-infra v0.0.0-20200615231324-3a016f44102c/go.mod h1:+BfrTJpc++rH30gX/C0QY6NT2eYVzycll52uw6CrQnc= knative.dev/test-infra v0.0.0-20200617235125-6382dba95484/go.mod h1:+BfrTJpc++rH30gX/C0QY6NT2eYVzycll52uw6CrQnc= -knative.dev/test-infra v0.0.0-20200618184825-a7b2980a8884 h1:qGxu/U/8VxhAuyFedrrne4s0vfY+YfoRwJJCY0AKpbw= -knative.dev/test-infra v0.0.0-20200618184825-a7b2980a8884/go.mod h1:qeiTuhDKO/HHheqVfepbxy5/q+O9toSJW6CO/DgjxFY= -knative.dev/test-infra v0.0.0-20200619200026-0b0587234302 h1:nGw173QprRCSZab6KT4uSj0GTp3WNRo0nfk9Lpo3F1I= knative.dev/test-infra v0.0.0-20200619200026-0b0587234302/go.mod h1:H8QEB2Y35+vAuVtDbn7QBD+NQr9zQbbxNiovCLNH7F4= +knative.dev/test-infra v0.0.0-20200622185426-9cd9379661ea h1:5Gs66BQiVWJfsTn1yaSxvca14aPFTIfdzpMrU2yCvq8= +knative.dev/test-infra v0.0.0-20200622185426-9cd9379661ea/go.mod h1:H8QEB2Y35+vAuVtDbn7QBD+NQr9zQbbxNiovCLNH7F4= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= diff --git a/pkg/client/injection/reconciler/autoscaling/v1alpha1/metric/controller.go b/pkg/client/injection/reconciler/autoscaling/v1alpha1/metric/controller.go index 4a83a64e7973..3b4632e18661 100644 --- a/pkg/client/injection/reconciler/autoscaling/v1alpha1/metric/controller.go +++ b/pkg/client/injection/reconciler/autoscaling/v1alpha1/metric/controller.go @@ -25,6 +25,8 @@ import ( strings "strings" corev1 "k8s.io/api/core/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" scheme "k8s.io/client-go/kubernetes/scheme" v1 "k8s.io/client-go/kubernetes/typed/core/v1" @@ -32,6 +34,7 @@ import ( kubeclient "knative.dev/pkg/client/injection/kube/client" controller "knative.dev/pkg/controller" logging "knative.dev/pkg/logging" + reconciler "knative.dev/pkg/reconciler" versionedscheme "knative.dev/serving/pkg/client/clientset/versioned/scheme" client "knative.dev/serving/pkg/client/injection/client" metric "knative.dev/serving/pkg/client/injection/informers/autoscaling/v1alpha1/metric" @@ -56,9 +59,27 @@ func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsF metricInformer := metric.Get(ctx) + lister := metricInformer.Lister() + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, Client: client.Get(ctx), - Lister: metricInformer.Lister(), + Lister: lister, reconciler: r, finalizerName: defaultFinalizerName, } diff --git a/pkg/client/injection/reconciler/autoscaling/v1alpha1/metric/reconciler.go b/pkg/client/injection/reconciler/autoscaling/v1alpha1/metric/reconciler.go index 3a0cf28f9533..dd7d091a7ffe 100644 --- a/pkg/client/injection/reconciler/autoscaling/v1alpha1/metric/reconciler.go +++ b/pkg/client/injection/reconciler/autoscaling/v1alpha1/metric/reconciler.go @@ -29,6 +29,7 @@ import ( equality "k8s.io/apimachinery/pkg/api/equality" errors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" types "k8s.io/apimachinery/pkg/types" sets "k8s.io/apimachinery/pkg/util/sets" cache "k8s.io/client-go/tools/cache" @@ -64,8 +65,30 @@ type Finalizer interface { FinalizeKind(ctx context.Context, o *v1alpha1.Metric) reconciler.Event } +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1alpha1.Metric if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1alpha1.Metric. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1alpha1.Metric) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1alpha1.Metric if they want to process tombstoned resources +// even when they are not the leader. Due to the nature of how finalizers are handled +// there are no guarantees that this will be called. +type ReadOnlyFinalizer interface { + // ObserveFinalizeKind implements custom logic to observe the final state of v1alpha1.Metric. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1alpha1.Metric) reconciler.Event +} + // reconcilerImpl implements controller.Reconciler for v1alpha1.Metric resources. type reconcilerImpl struct { + // LeaderAwareFuncs is inlined to help us implement reconciler.LeaderAware + reconciler.LeaderAwareFuncs + // Client is used to write back status updates. Client versioned.Interface @@ -90,13 +113,39 @@ type reconcilerImpl struct { // Check that our Reconciler implements controller.Reconciler var _ controller.Reconciler = (*reconcilerImpl)(nil) +// Check that our generated Reconciler is always LeaderAware. +var _ reconciler.LeaderAware = (*reconcilerImpl)(nil) + func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versioned.Interface, lister autoscalingv1alpha1.MetricLister, recorder record.EventRecorder, r Interface, options ...controller.Options) controller.Reconciler { // Check the options function input. It should be 0 or 1. if len(options) > 1 { logger.Fatalf("up to one options struct is supported, found %d", len(options)) } + // Fail fast when users inadvertently implement the other LeaderAware interface. + // For the typed reconcilers, Promote shouldn't take any arguments. + if _, ok := r.(reconciler.LeaderAware); ok { + logger.Fatalf("%T implements the incorrect LeaderAware interface. Promote() should not take an argument as genreconciler handles the enqueuing automatically.", r) + } + // TODO: Consider validating when folks implement ReadOnlyFinalizer, but not Finalizer. + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, Client: client, Lister: lister, Recorder: recorder, @@ -120,6 +169,25 @@ func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versio func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { logger := logging.FromContext(ctx) + // Convert the namespace/name string into a distinct namespace and name + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + logger.Errorf("invalid resource key: %s", key) + return nil + } + // Establish whether we are the leader for use below. + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer) + if !isLeader && !isROI && !isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return nil + } + // If configStore is set, attach the frozen configuration to the context. if r.configStore != nil { ctx = r.configStore.ToContext(ctx) @@ -128,15 +196,6 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // Add the recorder to context. ctx = controller.WithEventRecorder(ctx, r.Recorder) - // Convert the namespace/name string into a distinct namespace and name - - namespace, name, err := cache.SplitMetaNamespaceKey(key) - - if err != nil { - logger.Errorf("invalid resource key: %s", key) - return nil - } - // Get the resource with this namespace/name. getter := r.Lister.Metrics(namespace) @@ -156,24 +215,32 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { var reconcileEvent reconciler.Event if resource.GetDeletionTimestamp().IsZero() { - // Append the target method to the logger. - logger = logger.With(zap.String("targetMethod", "ReconcileKind")) + if isLeader { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ReconcileKind")) + + // Set and update the finalizer on resource if r.reconciler + // implements Finalizer. + if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { + return fmt.Errorf("failed to set finalizers: %w", err) + } - // Set and update the finalizer on resource if r.reconciler - // implements Finalizer. - if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { - return fmt.Errorf("failed to set finalizers: %w", err) - } + reconciler.PreProcessReconcile(ctx, resource) - reconciler.PreProcessReconcile(ctx, resource) + // Reconcile this copy of the resource and then write back any status + // updates regardless of whether the reconciliation errored out. + reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) - // Reconcile this copy of the resource and then write back any status - // updates regardless of whether the reconciliation errored out. - reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) + reconciler.PostProcessReconcile(ctx, resource, original) - reconciler.PostProcessReconcile(ctx, resource, original) + } else if isROI { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ObserveKind")) - } else if fin, ok := r.reconciler.(Finalizer); ok { + // Observe any changes to this resource, since we are not the leader. + reconcileEvent = roi.ObserveKind(ctx, resource) + } + } else if fin, ok := r.reconciler.(Finalizer); isLeader && ok { // Append the target method to the logger. logger = logger.With(zap.String("targetMethod", "FinalizeKind")) @@ -183,6 +250,12 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil { return fmt.Errorf("failed to clear finalizers: %w", err) } + } else if !isLeader && isROF { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ObserveFinalizeKind")) + + // For finalizing reconcilers, just observe when we aren't the leader. + reconcileEvent = rof.ObserveFinalizeKind(ctx, resource) } // Synchronize the status. @@ -191,6 +264,9 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // This is important because the copy we loaded from the injectionInformer's // cache may be stale and we don't want to overwrite a prior update // to status with this stale state. + } else if !isLeader { + logger.Warn("Saw status changes when we aren't the leader!") + // TODO: Consider logging the diff at Debug? } else if err = r.updateStatus(original, resource); err != nil { logger.Warnw("Failed to update resource status", zap.Error(err)) r.Recorder.Eventf(resource, v1.EventTypeWarning, "UpdateFailed", diff --git a/pkg/client/injection/reconciler/autoscaling/v1alpha1/metric/stub/reconciler.go b/pkg/client/injection/reconciler/autoscaling/v1alpha1/metric/stub/reconciler.go index 019e234b562d..9e1b65e9ec93 100644 --- a/pkg/client/injection/reconciler/autoscaling/v1alpha1/metric/stub/reconciler.go +++ b/pkg/client/injection/reconciler/autoscaling/v1alpha1/metric/stub/reconciler.go @@ -46,6 +46,15 @@ var _ metric.Interface = (*Reconciler)(nil) // Optionally check that our Reconciler implements Finalizer //var _ metric.Finalizer = (*Reconciler)(nil) +// Optionally check that our Reconciler implements ReadOnlyInterface +// Implement this to observe resources even when we are not the leader. +//var _ metric.ReadOnlyInterface = (*Reconciler)(nil) + +// Optionally check that our Reconciler implements ReadOnlyFinalizer +// Implement this to observe tombstoned resources even when we are not +// the leader (best effort). +//var _ metric.ReadOnlyFinalizer = (*Reconciler)(nil) + // ReconcileKind implements Interface.ReconcileKind. func (r *Reconciler) ReconcileKind(ctx context.Context, o *v1alpha1.Metric) reconciler.Event { // TODO: use this if the resource implements InitializeConditions. @@ -64,3 +73,15 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, o *v1alpha1.Metric) reco // // TODO: add custom finalization logic here. // return nil //} + +// Optionally, use ObserveKind to observe the resource when we are not the leader. +// func (r *Reconciler) ObserveKind(ctx context.Context, o *v1alpha1.Metric) reconciler.Event { +// // TODO: add custom observation logic here. +// return nil +// } + +// Optionally, use ObserveFinalizeKind to observe resources being finalized when we are no the leader. +//func (r *Reconciler) ObserveFinalizeKind(ctx context.Context, o *v1alpha1.Metric) reconciler.Event { +// // TODO: add custom observation logic here. +// return nil +//} diff --git a/pkg/client/injection/reconciler/autoscaling/v1alpha1/podautoscaler/controller.go b/pkg/client/injection/reconciler/autoscaling/v1alpha1/podautoscaler/controller.go index f9a33a79316c..4993e2243beb 100644 --- a/pkg/client/injection/reconciler/autoscaling/v1alpha1/podautoscaler/controller.go +++ b/pkg/client/injection/reconciler/autoscaling/v1alpha1/podautoscaler/controller.go @@ -25,6 +25,8 @@ import ( strings "strings" corev1 "k8s.io/api/core/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" scheme "k8s.io/client-go/kubernetes/scheme" v1 "k8s.io/client-go/kubernetes/typed/core/v1" @@ -32,6 +34,7 @@ import ( kubeclient "knative.dev/pkg/client/injection/kube/client" controller "knative.dev/pkg/controller" logging "knative.dev/pkg/logging" + reconciler "knative.dev/pkg/reconciler" versionedscheme "knative.dev/serving/pkg/client/clientset/versioned/scheme" client "knative.dev/serving/pkg/client/injection/client" podautoscaler "knative.dev/serving/pkg/client/injection/informers/autoscaling/v1alpha1/podautoscaler" @@ -59,9 +62,27 @@ func NewImpl(ctx context.Context, r Interface, classValue string, optionsFns ... podautoscalerInformer := podautoscaler.Get(ctx) + lister := podautoscalerInformer.Lister() + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, Client: client.Get(ctx), - Lister: podautoscalerInformer.Lister(), + Lister: lister, reconciler: r, finalizerName: defaultFinalizerName, classValue: classValue, diff --git a/pkg/client/injection/reconciler/autoscaling/v1alpha1/podautoscaler/reconciler.go b/pkg/client/injection/reconciler/autoscaling/v1alpha1/podautoscaler/reconciler.go index beeb71b4ccc2..96a1423bf53a 100644 --- a/pkg/client/injection/reconciler/autoscaling/v1alpha1/podautoscaler/reconciler.go +++ b/pkg/client/injection/reconciler/autoscaling/v1alpha1/podautoscaler/reconciler.go @@ -29,6 +29,7 @@ import ( equality "k8s.io/apimachinery/pkg/api/equality" errors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" types "k8s.io/apimachinery/pkg/types" sets "k8s.io/apimachinery/pkg/util/sets" cache "k8s.io/client-go/tools/cache" @@ -64,8 +65,30 @@ type Finalizer interface { FinalizeKind(ctx context.Context, o *v1alpha1.PodAutoscaler) reconciler.Event } +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1alpha1.PodAutoscaler if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1alpha1.PodAutoscaler. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1alpha1.PodAutoscaler) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1alpha1.PodAutoscaler if they want to process tombstoned resources +// even when they are not the leader. Due to the nature of how finalizers are handled +// there are no guarantees that this will be called. +type ReadOnlyFinalizer interface { + // ObserveFinalizeKind implements custom logic to observe the final state of v1alpha1.PodAutoscaler. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1alpha1.PodAutoscaler) reconciler.Event +} + // reconcilerImpl implements controller.Reconciler for v1alpha1.PodAutoscaler resources. type reconcilerImpl struct { + // LeaderAwareFuncs is inlined to help us implement reconciler.LeaderAware + reconciler.LeaderAwareFuncs + // Client is used to write back status updates. Client versioned.Interface @@ -93,13 +116,39 @@ type reconcilerImpl struct { // Check that our Reconciler implements controller.Reconciler var _ controller.Reconciler = (*reconcilerImpl)(nil) +// Check that our generated Reconciler is always LeaderAware. +var _ reconciler.LeaderAware = (*reconcilerImpl)(nil) + func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versioned.Interface, lister autoscalingv1alpha1.PodAutoscalerLister, recorder record.EventRecorder, r Interface, classValue string, options ...controller.Options) controller.Reconciler { // Check the options function input. It should be 0 or 1. if len(options) > 1 { logger.Fatalf("up to one options struct is supported, found %d", len(options)) } + // Fail fast when users inadvertently implement the other LeaderAware interface. + // For the typed reconcilers, Promote shouldn't take any arguments. + if _, ok := r.(reconciler.LeaderAware); ok { + logger.Fatalf("%T implements the incorrect LeaderAware interface. Promote() should not take an argument as genreconciler handles the enqueuing automatically.", r) + } + // TODO: Consider validating when folks implement ReadOnlyFinalizer, but not Finalizer. + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, Client: client, Lister: lister, Recorder: recorder, @@ -124,6 +173,25 @@ func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versio func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { logger := logging.FromContext(ctx) + // Convert the namespace/name string into a distinct namespace and name + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + logger.Errorf("invalid resource key: %s", key) + return nil + } + // Establish whether we are the leader for use below. + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer) + if !isLeader && !isROI && !isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return nil + } + // If configStore is set, attach the frozen configuration to the context. if r.configStore != nil { ctx = r.configStore.ToContext(ctx) @@ -132,15 +200,6 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // Add the recorder to context. ctx = controller.WithEventRecorder(ctx, r.Recorder) - // Convert the namespace/name string into a distinct namespace and name - - namespace, name, err := cache.SplitMetaNamespaceKey(key) - - if err != nil { - logger.Errorf("invalid resource key: %s", key) - return nil - } - // Get the resource with this namespace/name. getter := r.Lister.PodAutoscalers(namespace) @@ -167,24 +226,32 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { var reconcileEvent reconciler.Event if resource.GetDeletionTimestamp().IsZero() { - // Append the target method to the logger. - logger = logger.With(zap.String("targetMethod", "ReconcileKind")) + if isLeader { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ReconcileKind")) + + // Set and update the finalizer on resource if r.reconciler + // implements Finalizer. + if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { + return fmt.Errorf("failed to set finalizers: %w", err) + } - // Set and update the finalizer on resource if r.reconciler - // implements Finalizer. - if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { - return fmt.Errorf("failed to set finalizers: %w", err) - } + reconciler.PreProcessReconcile(ctx, resource) - reconciler.PreProcessReconcile(ctx, resource) + // Reconcile this copy of the resource and then write back any status + // updates regardless of whether the reconciliation errored out. + reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) - // Reconcile this copy of the resource and then write back any status - // updates regardless of whether the reconciliation errored out. - reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) + reconciler.PostProcessReconcile(ctx, resource, original) - reconciler.PostProcessReconcile(ctx, resource, original) + } else if isROI { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ObserveKind")) - } else if fin, ok := r.reconciler.(Finalizer); ok { + // Observe any changes to this resource, since we are not the leader. + reconcileEvent = roi.ObserveKind(ctx, resource) + } + } else if fin, ok := r.reconciler.(Finalizer); isLeader && ok { // Append the target method to the logger. logger = logger.With(zap.String("targetMethod", "FinalizeKind")) @@ -194,6 +261,12 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil { return fmt.Errorf("failed to clear finalizers: %w", err) } + } else if !isLeader && isROF { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ObserveFinalizeKind")) + + // For finalizing reconcilers, just observe when we aren't the leader. + reconcileEvent = rof.ObserveFinalizeKind(ctx, resource) } // Synchronize the status. @@ -202,6 +275,9 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // This is important because the copy we loaded from the injectionInformer's // cache may be stale and we don't want to overwrite a prior update // to status with this stale state. + } else if !isLeader { + logger.Warn("Saw status changes when we aren't the leader!") + // TODO: Consider logging the diff at Debug? } else if err = r.updateStatus(original, resource); err != nil { logger.Warnw("Failed to update resource status", zap.Error(err)) r.Recorder.Eventf(resource, v1.EventTypeWarning, "UpdateFailed", diff --git a/pkg/client/injection/reconciler/autoscaling/v1alpha1/podautoscaler/stub/reconciler.go b/pkg/client/injection/reconciler/autoscaling/v1alpha1/podautoscaler/stub/reconciler.go index edf7eff2554b..191a1092f809 100644 --- a/pkg/client/injection/reconciler/autoscaling/v1alpha1/podautoscaler/stub/reconciler.go +++ b/pkg/client/injection/reconciler/autoscaling/v1alpha1/podautoscaler/stub/reconciler.go @@ -46,6 +46,15 @@ var _ podautoscaler.Interface = (*Reconciler)(nil) // Optionally check that our Reconciler implements Finalizer //var _ podautoscaler.Finalizer = (*Reconciler)(nil) +// Optionally check that our Reconciler implements ReadOnlyInterface +// Implement this to observe resources even when we are not the leader. +//var _ podautoscaler.ReadOnlyInterface = (*Reconciler)(nil) + +// Optionally check that our Reconciler implements ReadOnlyFinalizer +// Implement this to observe tombstoned resources even when we are not +// the leader (best effort). +//var _ podautoscaler.ReadOnlyFinalizer = (*Reconciler)(nil) + // ReconcileKind implements Interface.ReconcileKind. func (r *Reconciler) ReconcileKind(ctx context.Context, o *v1alpha1.PodAutoscaler) reconciler.Event { // TODO: use this if the resource implements InitializeConditions. @@ -64,3 +73,15 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, o *v1alpha1.PodAutoscale // // TODO: add custom finalization logic here. // return nil //} + +// Optionally, use ObserveKind to observe the resource when we are not the leader. +// func (r *Reconciler) ObserveKind(ctx context.Context, o *v1alpha1.PodAutoscaler) reconciler.Event { +// // TODO: add custom observation logic here. +// return nil +// } + +// Optionally, use ObserveFinalizeKind to observe resources being finalized when we are no the leader. +//func (r *Reconciler) ObserveFinalizeKind(ctx context.Context, o *v1alpha1.PodAutoscaler) reconciler.Event { +// // TODO: add custom observation logic here. +// return nil +//} diff --git a/pkg/client/injection/reconciler/serving/v1/configuration/controller.go b/pkg/client/injection/reconciler/serving/v1/configuration/controller.go index ff990a2dabdd..f1eadbd3da27 100644 --- a/pkg/client/injection/reconciler/serving/v1/configuration/controller.go +++ b/pkg/client/injection/reconciler/serving/v1/configuration/controller.go @@ -25,6 +25,8 @@ import ( strings "strings" corev1 "k8s.io/api/core/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" scheme "k8s.io/client-go/kubernetes/scheme" v1 "k8s.io/client-go/kubernetes/typed/core/v1" @@ -32,6 +34,7 @@ import ( kubeclient "knative.dev/pkg/client/injection/kube/client" controller "knative.dev/pkg/controller" logging "knative.dev/pkg/logging" + reconciler "knative.dev/pkg/reconciler" versionedscheme "knative.dev/serving/pkg/client/clientset/versioned/scheme" client "knative.dev/serving/pkg/client/injection/client" configuration "knative.dev/serving/pkg/client/injection/informers/serving/v1/configuration" @@ -56,9 +59,27 @@ func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsF configurationInformer := configuration.Get(ctx) + lister := configurationInformer.Lister() + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, Client: client.Get(ctx), - Lister: configurationInformer.Lister(), + Lister: lister, reconciler: r, finalizerName: defaultFinalizerName, } diff --git a/pkg/client/injection/reconciler/serving/v1/configuration/reconciler.go b/pkg/client/injection/reconciler/serving/v1/configuration/reconciler.go index 87e3c10ce058..88d52aa5e098 100644 --- a/pkg/client/injection/reconciler/serving/v1/configuration/reconciler.go +++ b/pkg/client/injection/reconciler/serving/v1/configuration/reconciler.go @@ -29,6 +29,7 @@ import ( equality "k8s.io/apimachinery/pkg/api/equality" errors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" types "k8s.io/apimachinery/pkg/types" sets "k8s.io/apimachinery/pkg/util/sets" cache "k8s.io/client-go/tools/cache" @@ -64,8 +65,30 @@ type Finalizer interface { FinalizeKind(ctx context.Context, o *v1.Configuration) reconciler.Event } +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1.Configuration if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1.Configuration. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1.Configuration) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1.Configuration if they want to process tombstoned resources +// even when they are not the leader. Due to the nature of how finalizers are handled +// there are no guarantees that this will be called. +type ReadOnlyFinalizer interface { + // ObserveFinalizeKind implements custom logic to observe the final state of v1.Configuration. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1.Configuration) reconciler.Event +} + // reconcilerImpl implements controller.Reconciler for v1.Configuration resources. type reconcilerImpl struct { + // LeaderAwareFuncs is inlined to help us implement reconciler.LeaderAware + reconciler.LeaderAwareFuncs + // Client is used to write back status updates. Client versioned.Interface @@ -90,13 +113,39 @@ type reconcilerImpl struct { // Check that our Reconciler implements controller.Reconciler var _ controller.Reconciler = (*reconcilerImpl)(nil) +// Check that our generated Reconciler is always LeaderAware. +var _ reconciler.LeaderAware = (*reconcilerImpl)(nil) + func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versioned.Interface, lister servingv1.ConfigurationLister, recorder record.EventRecorder, r Interface, options ...controller.Options) controller.Reconciler { // Check the options function input. It should be 0 or 1. if len(options) > 1 { logger.Fatalf("up to one options struct is supported, found %d", len(options)) } + // Fail fast when users inadvertently implement the other LeaderAware interface. + // For the typed reconcilers, Promote shouldn't take any arguments. + if _, ok := r.(reconciler.LeaderAware); ok { + logger.Fatalf("%T implements the incorrect LeaderAware interface. Promote() should not take an argument as genreconciler handles the enqueuing automatically.", r) + } + // TODO: Consider validating when folks implement ReadOnlyFinalizer, but not Finalizer. + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, Client: client, Lister: lister, Recorder: recorder, @@ -120,6 +169,25 @@ func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versio func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { logger := logging.FromContext(ctx) + // Convert the namespace/name string into a distinct namespace and name + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + logger.Errorf("invalid resource key: %s", key) + return nil + } + // Establish whether we are the leader for use below. + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer) + if !isLeader && !isROI && !isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return nil + } + // If configStore is set, attach the frozen configuration to the context. if r.configStore != nil { ctx = r.configStore.ToContext(ctx) @@ -128,15 +196,6 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // Add the recorder to context. ctx = controller.WithEventRecorder(ctx, r.Recorder) - // Convert the namespace/name string into a distinct namespace and name - - namespace, name, err := cache.SplitMetaNamespaceKey(key) - - if err != nil { - logger.Errorf("invalid resource key: %s", key) - return nil - } - // Get the resource with this namespace/name. getter := r.Lister.Configurations(namespace) @@ -156,24 +215,32 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { var reconcileEvent reconciler.Event if resource.GetDeletionTimestamp().IsZero() { - // Append the target method to the logger. - logger = logger.With(zap.String("targetMethod", "ReconcileKind")) + if isLeader { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ReconcileKind")) + + // Set and update the finalizer on resource if r.reconciler + // implements Finalizer. + if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { + return fmt.Errorf("failed to set finalizers: %w", err) + } - // Set and update the finalizer on resource if r.reconciler - // implements Finalizer. - if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { - return fmt.Errorf("failed to set finalizers: %w", err) - } + reconciler.PreProcessReconcile(ctx, resource) - reconciler.PreProcessReconcile(ctx, resource) + // Reconcile this copy of the resource and then write back any status + // updates regardless of whether the reconciliation errored out. + reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) - // Reconcile this copy of the resource and then write back any status - // updates regardless of whether the reconciliation errored out. - reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) + reconciler.PostProcessReconcile(ctx, resource, original) - reconciler.PostProcessReconcile(ctx, resource, original) + } else if isROI { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ObserveKind")) - } else if fin, ok := r.reconciler.(Finalizer); ok { + // Observe any changes to this resource, since we are not the leader. + reconcileEvent = roi.ObserveKind(ctx, resource) + } + } else if fin, ok := r.reconciler.(Finalizer); isLeader && ok { // Append the target method to the logger. logger = logger.With(zap.String("targetMethod", "FinalizeKind")) @@ -183,6 +250,12 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil { return fmt.Errorf("failed to clear finalizers: %w", err) } + } else if !isLeader && isROF { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ObserveFinalizeKind")) + + // For finalizing reconcilers, just observe when we aren't the leader. + reconcileEvent = rof.ObserveFinalizeKind(ctx, resource) } // Synchronize the status. @@ -191,6 +264,9 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // This is important because the copy we loaded from the injectionInformer's // cache may be stale and we don't want to overwrite a prior update // to status with this stale state. + } else if !isLeader { + logger.Warn("Saw status changes when we aren't the leader!") + // TODO: Consider logging the diff at Debug? } else if err = r.updateStatus(original, resource); err != nil { logger.Warnw("Failed to update resource status", zap.Error(err)) r.Recorder.Eventf(resource, corev1.EventTypeWarning, "UpdateFailed", diff --git a/pkg/client/injection/reconciler/serving/v1/configuration/stub/reconciler.go b/pkg/client/injection/reconciler/serving/v1/configuration/stub/reconciler.go index 422e50c51e7a..8d2a7c0c6094 100644 --- a/pkg/client/injection/reconciler/serving/v1/configuration/stub/reconciler.go +++ b/pkg/client/injection/reconciler/serving/v1/configuration/stub/reconciler.go @@ -46,6 +46,15 @@ var _ configuration.Interface = (*Reconciler)(nil) // Optionally check that our Reconciler implements Finalizer //var _ configuration.Finalizer = (*Reconciler)(nil) +// Optionally check that our Reconciler implements ReadOnlyInterface +// Implement this to observe resources even when we are not the leader. +//var _ configuration.ReadOnlyInterface = (*Reconciler)(nil) + +// Optionally check that our Reconciler implements ReadOnlyFinalizer +// Implement this to observe tombstoned resources even when we are not +// the leader (best effort). +//var _ configuration.ReadOnlyFinalizer = (*Reconciler)(nil) + // ReconcileKind implements Interface.ReconcileKind. func (r *Reconciler) ReconcileKind(ctx context.Context, o *servingv1.Configuration) reconciler.Event { // TODO: use this if the resource implements InitializeConditions. @@ -64,3 +73,15 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, o *servingv1.Configurati // // TODO: add custom finalization logic here. // return nil //} + +// Optionally, use ObserveKind to observe the resource when we are not the leader. +// func (r *Reconciler) ObserveKind(ctx context.Context, o *servingv1.Configuration) reconciler.Event { +// // TODO: add custom observation logic here. +// return nil +// } + +// Optionally, use ObserveFinalizeKind to observe resources being finalized when we are no the leader. +//func (r *Reconciler) ObserveFinalizeKind(ctx context.Context, o *servingv1.Configuration) reconciler.Event { +// // TODO: add custom observation logic here. +// return nil +//} diff --git a/pkg/client/injection/reconciler/serving/v1/revision/controller.go b/pkg/client/injection/reconciler/serving/v1/revision/controller.go index affc15b54f77..f75ab8e2b73d 100644 --- a/pkg/client/injection/reconciler/serving/v1/revision/controller.go +++ b/pkg/client/injection/reconciler/serving/v1/revision/controller.go @@ -25,6 +25,8 @@ import ( strings "strings" corev1 "k8s.io/api/core/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" scheme "k8s.io/client-go/kubernetes/scheme" v1 "k8s.io/client-go/kubernetes/typed/core/v1" @@ -32,6 +34,7 @@ import ( kubeclient "knative.dev/pkg/client/injection/kube/client" controller "knative.dev/pkg/controller" logging "knative.dev/pkg/logging" + reconciler "knative.dev/pkg/reconciler" versionedscheme "knative.dev/serving/pkg/client/clientset/versioned/scheme" client "knative.dev/serving/pkg/client/injection/client" revision "knative.dev/serving/pkg/client/injection/informers/serving/v1/revision" @@ -56,9 +59,27 @@ func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsF revisionInformer := revision.Get(ctx) + lister := revisionInformer.Lister() + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, Client: client.Get(ctx), - Lister: revisionInformer.Lister(), + Lister: lister, reconciler: r, finalizerName: defaultFinalizerName, } diff --git a/pkg/client/injection/reconciler/serving/v1/revision/reconciler.go b/pkg/client/injection/reconciler/serving/v1/revision/reconciler.go index ffb3afee9919..73805e89364c 100644 --- a/pkg/client/injection/reconciler/serving/v1/revision/reconciler.go +++ b/pkg/client/injection/reconciler/serving/v1/revision/reconciler.go @@ -29,6 +29,7 @@ import ( equality "k8s.io/apimachinery/pkg/api/equality" errors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" types "k8s.io/apimachinery/pkg/types" sets "k8s.io/apimachinery/pkg/util/sets" cache "k8s.io/client-go/tools/cache" @@ -64,8 +65,30 @@ type Finalizer interface { FinalizeKind(ctx context.Context, o *v1.Revision) reconciler.Event } +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1.Revision if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1.Revision. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1.Revision) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1.Revision if they want to process tombstoned resources +// even when they are not the leader. Due to the nature of how finalizers are handled +// there are no guarantees that this will be called. +type ReadOnlyFinalizer interface { + // ObserveFinalizeKind implements custom logic to observe the final state of v1.Revision. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1.Revision) reconciler.Event +} + // reconcilerImpl implements controller.Reconciler for v1.Revision resources. type reconcilerImpl struct { + // LeaderAwareFuncs is inlined to help us implement reconciler.LeaderAware + reconciler.LeaderAwareFuncs + // Client is used to write back status updates. Client versioned.Interface @@ -90,13 +113,39 @@ type reconcilerImpl struct { // Check that our Reconciler implements controller.Reconciler var _ controller.Reconciler = (*reconcilerImpl)(nil) +// Check that our generated Reconciler is always LeaderAware. +var _ reconciler.LeaderAware = (*reconcilerImpl)(nil) + func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versioned.Interface, lister servingv1.RevisionLister, recorder record.EventRecorder, r Interface, options ...controller.Options) controller.Reconciler { // Check the options function input. It should be 0 or 1. if len(options) > 1 { logger.Fatalf("up to one options struct is supported, found %d", len(options)) } + // Fail fast when users inadvertently implement the other LeaderAware interface. + // For the typed reconcilers, Promote shouldn't take any arguments. + if _, ok := r.(reconciler.LeaderAware); ok { + logger.Fatalf("%T implements the incorrect LeaderAware interface. Promote() should not take an argument as genreconciler handles the enqueuing automatically.", r) + } + // TODO: Consider validating when folks implement ReadOnlyFinalizer, but not Finalizer. + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, Client: client, Lister: lister, Recorder: recorder, @@ -120,6 +169,25 @@ func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versio func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { logger := logging.FromContext(ctx) + // Convert the namespace/name string into a distinct namespace and name + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + logger.Errorf("invalid resource key: %s", key) + return nil + } + // Establish whether we are the leader for use below. + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer) + if !isLeader && !isROI && !isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return nil + } + // If configStore is set, attach the frozen configuration to the context. if r.configStore != nil { ctx = r.configStore.ToContext(ctx) @@ -128,15 +196,6 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // Add the recorder to context. ctx = controller.WithEventRecorder(ctx, r.Recorder) - // Convert the namespace/name string into a distinct namespace and name - - namespace, name, err := cache.SplitMetaNamespaceKey(key) - - if err != nil { - logger.Errorf("invalid resource key: %s", key) - return nil - } - // Get the resource with this namespace/name. getter := r.Lister.Revisions(namespace) @@ -156,24 +215,32 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { var reconcileEvent reconciler.Event if resource.GetDeletionTimestamp().IsZero() { - // Append the target method to the logger. - logger = logger.With(zap.String("targetMethod", "ReconcileKind")) + if isLeader { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ReconcileKind")) + + // Set and update the finalizer on resource if r.reconciler + // implements Finalizer. + if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { + return fmt.Errorf("failed to set finalizers: %w", err) + } - // Set and update the finalizer on resource if r.reconciler - // implements Finalizer. - if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { - return fmt.Errorf("failed to set finalizers: %w", err) - } + reconciler.PreProcessReconcile(ctx, resource) - reconciler.PreProcessReconcile(ctx, resource) + // Reconcile this copy of the resource and then write back any status + // updates regardless of whether the reconciliation errored out. + reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) - // Reconcile this copy of the resource and then write back any status - // updates regardless of whether the reconciliation errored out. - reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) + reconciler.PostProcessReconcile(ctx, resource, original) - reconciler.PostProcessReconcile(ctx, resource, original) + } else if isROI { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ObserveKind")) - } else if fin, ok := r.reconciler.(Finalizer); ok { + // Observe any changes to this resource, since we are not the leader. + reconcileEvent = roi.ObserveKind(ctx, resource) + } + } else if fin, ok := r.reconciler.(Finalizer); isLeader && ok { // Append the target method to the logger. logger = logger.With(zap.String("targetMethod", "FinalizeKind")) @@ -183,6 +250,12 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil { return fmt.Errorf("failed to clear finalizers: %w", err) } + } else if !isLeader && isROF { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ObserveFinalizeKind")) + + // For finalizing reconcilers, just observe when we aren't the leader. + reconcileEvent = rof.ObserveFinalizeKind(ctx, resource) } // Synchronize the status. @@ -191,6 +264,9 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // This is important because the copy we loaded from the injectionInformer's // cache may be stale and we don't want to overwrite a prior update // to status with this stale state. + } else if !isLeader { + logger.Warn("Saw status changes when we aren't the leader!") + // TODO: Consider logging the diff at Debug? } else if err = r.updateStatus(original, resource); err != nil { logger.Warnw("Failed to update resource status", zap.Error(err)) r.Recorder.Eventf(resource, corev1.EventTypeWarning, "UpdateFailed", diff --git a/pkg/client/injection/reconciler/serving/v1/revision/stub/reconciler.go b/pkg/client/injection/reconciler/serving/v1/revision/stub/reconciler.go index 504f392dea86..3975ff2b73a1 100644 --- a/pkg/client/injection/reconciler/serving/v1/revision/stub/reconciler.go +++ b/pkg/client/injection/reconciler/serving/v1/revision/stub/reconciler.go @@ -46,6 +46,15 @@ var _ revision.Interface = (*Reconciler)(nil) // Optionally check that our Reconciler implements Finalizer //var _ revision.Finalizer = (*Reconciler)(nil) +// Optionally check that our Reconciler implements ReadOnlyInterface +// Implement this to observe resources even when we are not the leader. +//var _ revision.ReadOnlyInterface = (*Reconciler)(nil) + +// Optionally check that our Reconciler implements ReadOnlyFinalizer +// Implement this to observe tombstoned resources even when we are not +// the leader (best effort). +//var _ revision.ReadOnlyFinalizer = (*Reconciler)(nil) + // ReconcileKind implements Interface.ReconcileKind. func (r *Reconciler) ReconcileKind(ctx context.Context, o *servingv1.Revision) reconciler.Event { // TODO: use this if the resource implements InitializeConditions. @@ -64,3 +73,15 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, o *servingv1.Revision) r // // TODO: add custom finalization logic here. // return nil //} + +// Optionally, use ObserveKind to observe the resource when we are not the leader. +// func (r *Reconciler) ObserveKind(ctx context.Context, o *servingv1.Revision) reconciler.Event { +// // TODO: add custom observation logic here. +// return nil +// } + +// Optionally, use ObserveFinalizeKind to observe resources being finalized when we are no the leader. +//func (r *Reconciler) ObserveFinalizeKind(ctx context.Context, o *servingv1.Revision) reconciler.Event { +// // TODO: add custom observation logic here. +// return nil +//} diff --git a/pkg/client/injection/reconciler/serving/v1/route/controller.go b/pkg/client/injection/reconciler/serving/v1/route/controller.go index 84395e5eb4a5..e567a6c8c2c7 100644 --- a/pkg/client/injection/reconciler/serving/v1/route/controller.go +++ b/pkg/client/injection/reconciler/serving/v1/route/controller.go @@ -25,6 +25,8 @@ import ( strings "strings" corev1 "k8s.io/api/core/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" scheme "k8s.io/client-go/kubernetes/scheme" v1 "k8s.io/client-go/kubernetes/typed/core/v1" @@ -32,6 +34,7 @@ import ( kubeclient "knative.dev/pkg/client/injection/kube/client" controller "knative.dev/pkg/controller" logging "knative.dev/pkg/logging" + reconciler "knative.dev/pkg/reconciler" versionedscheme "knative.dev/serving/pkg/client/clientset/versioned/scheme" client "knative.dev/serving/pkg/client/injection/client" route "knative.dev/serving/pkg/client/injection/informers/serving/v1/route" @@ -56,9 +59,27 @@ func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsF routeInformer := route.Get(ctx) + lister := routeInformer.Lister() + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, Client: client.Get(ctx), - Lister: routeInformer.Lister(), + Lister: lister, reconciler: r, finalizerName: defaultFinalizerName, } diff --git a/pkg/client/injection/reconciler/serving/v1/route/reconciler.go b/pkg/client/injection/reconciler/serving/v1/route/reconciler.go index 6ee7cbb40180..17da3d796201 100644 --- a/pkg/client/injection/reconciler/serving/v1/route/reconciler.go +++ b/pkg/client/injection/reconciler/serving/v1/route/reconciler.go @@ -29,6 +29,7 @@ import ( equality "k8s.io/apimachinery/pkg/api/equality" errors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" types "k8s.io/apimachinery/pkg/types" sets "k8s.io/apimachinery/pkg/util/sets" cache "k8s.io/client-go/tools/cache" @@ -64,8 +65,30 @@ type Finalizer interface { FinalizeKind(ctx context.Context, o *v1.Route) reconciler.Event } +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1.Route if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1.Route. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1.Route) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1.Route if they want to process tombstoned resources +// even when they are not the leader. Due to the nature of how finalizers are handled +// there are no guarantees that this will be called. +type ReadOnlyFinalizer interface { + // ObserveFinalizeKind implements custom logic to observe the final state of v1.Route. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1.Route) reconciler.Event +} + // reconcilerImpl implements controller.Reconciler for v1.Route resources. type reconcilerImpl struct { + // LeaderAwareFuncs is inlined to help us implement reconciler.LeaderAware + reconciler.LeaderAwareFuncs + // Client is used to write back status updates. Client versioned.Interface @@ -90,13 +113,39 @@ type reconcilerImpl struct { // Check that our Reconciler implements controller.Reconciler var _ controller.Reconciler = (*reconcilerImpl)(nil) +// Check that our generated Reconciler is always LeaderAware. +var _ reconciler.LeaderAware = (*reconcilerImpl)(nil) + func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versioned.Interface, lister servingv1.RouteLister, recorder record.EventRecorder, r Interface, options ...controller.Options) controller.Reconciler { // Check the options function input. It should be 0 or 1. if len(options) > 1 { logger.Fatalf("up to one options struct is supported, found %d", len(options)) } + // Fail fast when users inadvertently implement the other LeaderAware interface. + // For the typed reconcilers, Promote shouldn't take any arguments. + if _, ok := r.(reconciler.LeaderAware); ok { + logger.Fatalf("%T implements the incorrect LeaderAware interface. Promote() should not take an argument as genreconciler handles the enqueuing automatically.", r) + } + // TODO: Consider validating when folks implement ReadOnlyFinalizer, but not Finalizer. + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, Client: client, Lister: lister, Recorder: recorder, @@ -120,6 +169,25 @@ func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versio func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { logger := logging.FromContext(ctx) + // Convert the namespace/name string into a distinct namespace and name + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + logger.Errorf("invalid resource key: %s", key) + return nil + } + // Establish whether we are the leader for use below. + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer) + if !isLeader && !isROI && !isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return nil + } + // If configStore is set, attach the frozen configuration to the context. if r.configStore != nil { ctx = r.configStore.ToContext(ctx) @@ -128,15 +196,6 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // Add the recorder to context. ctx = controller.WithEventRecorder(ctx, r.Recorder) - // Convert the namespace/name string into a distinct namespace and name - - namespace, name, err := cache.SplitMetaNamespaceKey(key) - - if err != nil { - logger.Errorf("invalid resource key: %s", key) - return nil - } - // Get the resource with this namespace/name. getter := r.Lister.Routes(namespace) @@ -156,24 +215,32 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { var reconcileEvent reconciler.Event if resource.GetDeletionTimestamp().IsZero() { - // Append the target method to the logger. - logger = logger.With(zap.String("targetMethod", "ReconcileKind")) + if isLeader { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ReconcileKind")) + + // Set and update the finalizer on resource if r.reconciler + // implements Finalizer. + if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { + return fmt.Errorf("failed to set finalizers: %w", err) + } - // Set and update the finalizer on resource if r.reconciler - // implements Finalizer. - if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { - return fmt.Errorf("failed to set finalizers: %w", err) - } + reconciler.PreProcessReconcile(ctx, resource) - reconciler.PreProcessReconcile(ctx, resource) + // Reconcile this copy of the resource and then write back any status + // updates regardless of whether the reconciliation errored out. + reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) - // Reconcile this copy of the resource and then write back any status - // updates regardless of whether the reconciliation errored out. - reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) + reconciler.PostProcessReconcile(ctx, resource, original) - reconciler.PostProcessReconcile(ctx, resource, original) + } else if isROI { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ObserveKind")) - } else if fin, ok := r.reconciler.(Finalizer); ok { + // Observe any changes to this resource, since we are not the leader. + reconcileEvent = roi.ObserveKind(ctx, resource) + } + } else if fin, ok := r.reconciler.(Finalizer); isLeader && ok { // Append the target method to the logger. logger = logger.With(zap.String("targetMethod", "FinalizeKind")) @@ -183,6 +250,12 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil { return fmt.Errorf("failed to clear finalizers: %w", err) } + } else if !isLeader && isROF { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ObserveFinalizeKind")) + + // For finalizing reconcilers, just observe when we aren't the leader. + reconcileEvent = rof.ObserveFinalizeKind(ctx, resource) } // Synchronize the status. @@ -191,6 +264,9 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // This is important because the copy we loaded from the injectionInformer's // cache may be stale and we don't want to overwrite a prior update // to status with this stale state. + } else if !isLeader { + logger.Warn("Saw status changes when we aren't the leader!") + // TODO: Consider logging the diff at Debug? } else if err = r.updateStatus(original, resource); err != nil { logger.Warnw("Failed to update resource status", zap.Error(err)) r.Recorder.Eventf(resource, corev1.EventTypeWarning, "UpdateFailed", diff --git a/pkg/client/injection/reconciler/serving/v1/route/stub/reconciler.go b/pkg/client/injection/reconciler/serving/v1/route/stub/reconciler.go index ebcab618b659..058802836912 100644 --- a/pkg/client/injection/reconciler/serving/v1/route/stub/reconciler.go +++ b/pkg/client/injection/reconciler/serving/v1/route/stub/reconciler.go @@ -46,6 +46,15 @@ var _ route.Interface = (*Reconciler)(nil) // Optionally check that our Reconciler implements Finalizer //var _ route.Finalizer = (*Reconciler)(nil) +// Optionally check that our Reconciler implements ReadOnlyInterface +// Implement this to observe resources even when we are not the leader. +//var _ route.ReadOnlyInterface = (*Reconciler)(nil) + +// Optionally check that our Reconciler implements ReadOnlyFinalizer +// Implement this to observe tombstoned resources even when we are not +// the leader (best effort). +//var _ route.ReadOnlyFinalizer = (*Reconciler)(nil) + // ReconcileKind implements Interface.ReconcileKind. func (r *Reconciler) ReconcileKind(ctx context.Context, o *servingv1.Route) reconciler.Event { // TODO: use this if the resource implements InitializeConditions. @@ -64,3 +73,15 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, o *servingv1.Route) reco // // TODO: add custom finalization logic here. // return nil //} + +// Optionally, use ObserveKind to observe the resource when we are not the leader. +// func (r *Reconciler) ObserveKind(ctx context.Context, o *servingv1.Route) reconciler.Event { +// // TODO: add custom observation logic here. +// return nil +// } + +// Optionally, use ObserveFinalizeKind to observe resources being finalized when we are no the leader. +//func (r *Reconciler) ObserveFinalizeKind(ctx context.Context, o *servingv1.Route) reconciler.Event { +// // TODO: add custom observation logic here. +// return nil +//} diff --git a/pkg/client/injection/reconciler/serving/v1/service/controller.go b/pkg/client/injection/reconciler/serving/v1/service/controller.go index 0889837b7400..858ea780c4f6 100644 --- a/pkg/client/injection/reconciler/serving/v1/service/controller.go +++ b/pkg/client/injection/reconciler/serving/v1/service/controller.go @@ -25,6 +25,8 @@ import ( strings "strings" corev1 "k8s.io/api/core/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" scheme "k8s.io/client-go/kubernetes/scheme" v1 "k8s.io/client-go/kubernetes/typed/core/v1" @@ -32,6 +34,7 @@ import ( kubeclient "knative.dev/pkg/client/injection/kube/client" controller "knative.dev/pkg/controller" logging "knative.dev/pkg/logging" + reconciler "knative.dev/pkg/reconciler" versionedscheme "knative.dev/serving/pkg/client/clientset/versioned/scheme" client "knative.dev/serving/pkg/client/injection/client" service "knative.dev/serving/pkg/client/injection/informers/serving/v1/service" @@ -56,9 +59,27 @@ func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsF serviceInformer := service.Get(ctx) + lister := serviceInformer.Lister() + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, Client: client.Get(ctx), - Lister: serviceInformer.Lister(), + Lister: lister, reconciler: r, finalizerName: defaultFinalizerName, } diff --git a/pkg/client/injection/reconciler/serving/v1/service/reconciler.go b/pkg/client/injection/reconciler/serving/v1/service/reconciler.go index d56e8e94a5ee..9558c60591b6 100644 --- a/pkg/client/injection/reconciler/serving/v1/service/reconciler.go +++ b/pkg/client/injection/reconciler/serving/v1/service/reconciler.go @@ -29,6 +29,7 @@ import ( equality "k8s.io/apimachinery/pkg/api/equality" errors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" types "k8s.io/apimachinery/pkg/types" sets "k8s.io/apimachinery/pkg/util/sets" cache "k8s.io/client-go/tools/cache" @@ -64,8 +65,30 @@ type Finalizer interface { FinalizeKind(ctx context.Context, o *v1.Service) reconciler.Event } +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1.Service if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1.Service. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1.Service) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1.Service if they want to process tombstoned resources +// even when they are not the leader. Due to the nature of how finalizers are handled +// there are no guarantees that this will be called. +type ReadOnlyFinalizer interface { + // ObserveFinalizeKind implements custom logic to observe the final state of v1.Service. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1.Service) reconciler.Event +} + // reconcilerImpl implements controller.Reconciler for v1.Service resources. type reconcilerImpl struct { + // LeaderAwareFuncs is inlined to help us implement reconciler.LeaderAware + reconciler.LeaderAwareFuncs + // Client is used to write back status updates. Client versioned.Interface @@ -90,13 +113,39 @@ type reconcilerImpl struct { // Check that our Reconciler implements controller.Reconciler var _ controller.Reconciler = (*reconcilerImpl)(nil) +// Check that our generated Reconciler is always LeaderAware. +var _ reconciler.LeaderAware = (*reconcilerImpl)(nil) + func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versioned.Interface, lister servingv1.ServiceLister, recorder record.EventRecorder, r Interface, options ...controller.Options) controller.Reconciler { // Check the options function input. It should be 0 or 1. if len(options) > 1 { logger.Fatalf("up to one options struct is supported, found %d", len(options)) } + // Fail fast when users inadvertently implement the other LeaderAware interface. + // For the typed reconcilers, Promote shouldn't take any arguments. + if _, ok := r.(reconciler.LeaderAware); ok { + logger.Fatalf("%T implements the incorrect LeaderAware interface. Promote() should not take an argument as genreconciler handles the enqueuing automatically.", r) + } + // TODO: Consider validating when folks implement ReadOnlyFinalizer, but not Finalizer. + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, Client: client, Lister: lister, Recorder: recorder, @@ -120,6 +169,25 @@ func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versio func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { logger := logging.FromContext(ctx) + // Convert the namespace/name string into a distinct namespace and name + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + logger.Errorf("invalid resource key: %s", key) + return nil + } + // Establish whether we are the leader for use below. + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer) + if !isLeader && !isROI && !isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return nil + } + // If configStore is set, attach the frozen configuration to the context. if r.configStore != nil { ctx = r.configStore.ToContext(ctx) @@ -128,15 +196,6 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // Add the recorder to context. ctx = controller.WithEventRecorder(ctx, r.Recorder) - // Convert the namespace/name string into a distinct namespace and name - - namespace, name, err := cache.SplitMetaNamespaceKey(key) - - if err != nil { - logger.Errorf("invalid resource key: %s", key) - return nil - } - // Get the resource with this namespace/name. getter := r.Lister.Services(namespace) @@ -156,24 +215,32 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { var reconcileEvent reconciler.Event if resource.GetDeletionTimestamp().IsZero() { - // Append the target method to the logger. - logger = logger.With(zap.String("targetMethod", "ReconcileKind")) + if isLeader { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ReconcileKind")) + + // Set and update the finalizer on resource if r.reconciler + // implements Finalizer. + if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { + return fmt.Errorf("failed to set finalizers: %w", err) + } - // Set and update the finalizer on resource if r.reconciler - // implements Finalizer. - if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { - return fmt.Errorf("failed to set finalizers: %w", err) - } + reconciler.PreProcessReconcile(ctx, resource) - reconciler.PreProcessReconcile(ctx, resource) + // Reconcile this copy of the resource and then write back any status + // updates regardless of whether the reconciliation errored out. + reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) - // Reconcile this copy of the resource and then write back any status - // updates regardless of whether the reconciliation errored out. - reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) + reconciler.PostProcessReconcile(ctx, resource, original) - reconciler.PostProcessReconcile(ctx, resource, original) + } else if isROI { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ObserveKind")) - } else if fin, ok := r.reconciler.(Finalizer); ok { + // Observe any changes to this resource, since we are not the leader. + reconcileEvent = roi.ObserveKind(ctx, resource) + } + } else if fin, ok := r.reconciler.(Finalizer); isLeader && ok { // Append the target method to the logger. logger = logger.With(zap.String("targetMethod", "FinalizeKind")) @@ -183,6 +250,12 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil { return fmt.Errorf("failed to clear finalizers: %w", err) } + } else if !isLeader && isROF { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ObserveFinalizeKind")) + + // For finalizing reconcilers, just observe when we aren't the leader. + reconcileEvent = rof.ObserveFinalizeKind(ctx, resource) } // Synchronize the status. @@ -191,6 +264,9 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // This is important because the copy we loaded from the injectionInformer's // cache may be stale and we don't want to overwrite a prior update // to status with this stale state. + } else if !isLeader { + logger.Warn("Saw status changes when we aren't the leader!") + // TODO: Consider logging the diff at Debug? } else if err = r.updateStatus(original, resource); err != nil { logger.Warnw("Failed to update resource status", zap.Error(err)) r.Recorder.Eventf(resource, corev1.EventTypeWarning, "UpdateFailed", diff --git a/pkg/client/injection/reconciler/serving/v1/service/stub/reconciler.go b/pkg/client/injection/reconciler/serving/v1/service/stub/reconciler.go index de7eae3e2d5e..ad1404ded837 100644 --- a/pkg/client/injection/reconciler/serving/v1/service/stub/reconciler.go +++ b/pkg/client/injection/reconciler/serving/v1/service/stub/reconciler.go @@ -46,6 +46,15 @@ var _ service.Interface = (*Reconciler)(nil) // Optionally check that our Reconciler implements Finalizer //var _ service.Finalizer = (*Reconciler)(nil) +// Optionally check that our Reconciler implements ReadOnlyInterface +// Implement this to observe resources even when we are not the leader. +//var _ service.ReadOnlyInterface = (*Reconciler)(nil) + +// Optionally check that our Reconciler implements ReadOnlyFinalizer +// Implement this to observe tombstoned resources even when we are not +// the leader (best effort). +//var _ service.ReadOnlyFinalizer = (*Reconciler)(nil) + // ReconcileKind implements Interface.ReconcileKind. func (r *Reconciler) ReconcileKind(ctx context.Context, o *servingv1.Service) reconciler.Event { // TODO: use this if the resource implements InitializeConditions. @@ -64,3 +73,15 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, o *servingv1.Service) re // // TODO: add custom finalization logic here. // return nil //} + +// Optionally, use ObserveKind to observe the resource when we are not the leader. +// func (r *Reconciler) ObserveKind(ctx context.Context, o *servingv1.Service) reconciler.Event { +// // TODO: add custom observation logic here. +// return nil +// } + +// Optionally, use ObserveFinalizeKind to observe resources being finalized when we are no the leader. +//func (r *Reconciler) ObserveFinalizeKind(ctx context.Context, o *servingv1.Service) reconciler.Event { +// // TODO: add custom observation logic here. +// return nil +//} diff --git a/vendor/knative.dev/networking/pkg/client/injection/reconciler/networking/v1alpha1/serverlessservice/controller.go b/vendor/knative.dev/networking/pkg/client/injection/reconciler/networking/v1alpha1/serverlessservice/controller.go index 4207049ba07b..ead749a9bcd3 100644 --- a/vendor/knative.dev/networking/pkg/client/injection/reconciler/networking/v1alpha1/serverlessservice/controller.go +++ b/vendor/knative.dev/networking/pkg/client/injection/reconciler/networking/v1alpha1/serverlessservice/controller.go @@ -25,6 +25,8 @@ import ( strings "strings" corev1 "k8s.io/api/core/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" scheme "k8s.io/client-go/kubernetes/scheme" v1 "k8s.io/client-go/kubernetes/typed/core/v1" @@ -35,6 +37,7 @@ import ( kubeclient "knative.dev/pkg/client/injection/kube/client" controller "knative.dev/pkg/controller" logging "knative.dev/pkg/logging" + reconciler "knative.dev/pkg/reconciler" ) const ( @@ -56,9 +59,27 @@ func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsF serverlessserviceInformer := serverlessservice.Get(ctx) + lister := serverlessserviceInformer.Lister() + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, Client: client.Get(ctx), - Lister: serverlessserviceInformer.Lister(), + Lister: lister, reconciler: r, finalizerName: defaultFinalizerName, } diff --git a/vendor/knative.dev/networking/pkg/client/injection/reconciler/networking/v1alpha1/serverlessservice/reconciler.go b/vendor/knative.dev/networking/pkg/client/injection/reconciler/networking/v1alpha1/serverlessservice/reconciler.go index 98a9807a98cb..fdd2920a2e74 100644 --- a/vendor/knative.dev/networking/pkg/client/injection/reconciler/networking/v1alpha1/serverlessservice/reconciler.go +++ b/vendor/knative.dev/networking/pkg/client/injection/reconciler/networking/v1alpha1/serverlessservice/reconciler.go @@ -21,6 +21,7 @@ package serverlessservice import ( context "context" json "encoding/json" + fmt "fmt" reflect "reflect" zap "go.uber.org/zap" @@ -28,6 +29,7 @@ import ( equality "k8s.io/apimachinery/pkg/api/equality" errors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" types "k8s.io/apimachinery/pkg/types" sets "k8s.io/apimachinery/pkg/util/sets" cache "k8s.io/client-go/tools/cache" @@ -63,8 +65,30 @@ type Finalizer interface { FinalizeKind(ctx context.Context, o *v1alpha1.ServerlessService) reconciler.Event } +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1alpha1.ServerlessService if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1alpha1.ServerlessService. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1alpha1.ServerlessService) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1alpha1.ServerlessService if they want to process tombstoned resources +// even when they are not the leader. Due to the nature of how finalizers are handled +// there are no guarantees that this will be called. +type ReadOnlyFinalizer interface { + // ObserveFinalizeKind implements custom logic to observe the final state of v1alpha1.ServerlessService. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1alpha1.ServerlessService) reconciler.Event +} + // reconcilerImpl implements controller.Reconciler for v1alpha1.ServerlessService resources. type reconcilerImpl struct { + // LeaderAwareFuncs is inlined to help us implement reconciler.LeaderAware + reconciler.LeaderAwareFuncs + // Client is used to write back status updates. Client versioned.Interface @@ -89,13 +113,39 @@ type reconcilerImpl struct { // Check that our Reconciler implements controller.Reconciler var _ controller.Reconciler = (*reconcilerImpl)(nil) +// Check that our generated Reconciler is always LeaderAware. +var _ reconciler.LeaderAware = (*reconcilerImpl)(nil) + func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versioned.Interface, lister networkingv1alpha1.ServerlessServiceLister, recorder record.EventRecorder, r Interface, options ...controller.Options) controller.Reconciler { // Check the options function input. It should be 0 or 1. if len(options) > 1 { logger.Fatalf("up to one options struct is supported, found %d", len(options)) } + // Fail fast when users inadvertently implement the other LeaderAware interface. + // For the typed reconcilers, Promote shouldn't take any arguments. + if _, ok := r.(reconciler.LeaderAware); ok { + logger.Fatalf("%T implements the incorrect LeaderAware interface. Promote() should not take an argument as genreconciler handles the enqueuing automatically.", r) + } + // TODO: Consider validating when folks implement ReadOnlyFinalizer, but not Finalizer. + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, Client: client, Lister: lister, Recorder: recorder, @@ -119,6 +169,25 @@ func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versio func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { logger := logging.FromContext(ctx) + // Convert the namespace/name string into a distinct namespace and name + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + logger.Errorf("invalid resource key: %s", key) + return nil + } + // Establish whether we are the leader for use below. + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer) + if !isLeader && !isROI && !isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return nil + } + // If configStore is set, attach the frozen configuration to the context. if r.configStore != nil { ctx = r.configStore.ToContext(ctx) @@ -127,15 +196,6 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // Add the recorder to context. ctx = controller.WithEventRecorder(ctx, r.Recorder) - // Convert the namespace/name string into a distinct namespace and name - - namespace, name, err := cache.SplitMetaNamespaceKey(key) - - if err != nil { - logger.Errorf("invalid resource key: %s", key) - return nil - } - // Get the resource with this namespace/name. getter := r.Lister.ServerlessServices(namespace) @@ -155,24 +215,32 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { var reconcileEvent reconciler.Event if resource.GetDeletionTimestamp().IsZero() { - // Append the target method to the logger. - logger = logger.With(zap.String("targetMethod", "ReconcileKind")) + if isLeader { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ReconcileKind")) + + // Set and update the finalizer on resource if r.reconciler + // implements Finalizer. + if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { + return fmt.Errorf("failed to set finalizers: %w", err) + } - // Set and update the finalizer on resource if r.reconciler - // implements Finalizer. - if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { - logger.Warnw("Failed to set finalizers", zap.Error(err)) - } + reconciler.PreProcessReconcile(ctx, resource) - reconciler.PreProcessReconcile(ctx, resource) + // Reconcile this copy of the resource and then write back any status + // updates regardless of whether the reconciliation errored out. + reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) - // Reconcile this copy of the resource and then write back any status - // updates regardless of whether the reconciliation errored out. - reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) + reconciler.PostProcessReconcile(ctx, resource, original) - reconciler.PostProcessReconcile(ctx, resource, original) + } else if isROI { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ObserveKind")) - } else if fin, ok := r.reconciler.(Finalizer); ok { + // Observe any changes to this resource, since we are not the leader. + reconcileEvent = roi.ObserveKind(ctx, resource) + } + } else if fin, ok := r.reconciler.(Finalizer); isLeader && ok { // Append the target method to the logger. logger = logger.With(zap.String("targetMethod", "FinalizeKind")) @@ -180,8 +248,14 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // and reconciled cleanly (nil or normal event), remove the finalizer. reconcileEvent = fin.FinalizeKind(ctx, resource) if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil { - logger.Warnw("Failed to clear finalizers", zap.Error(err)) + return fmt.Errorf("failed to clear finalizers: %w", err) } + } else if !isLeader && isROF { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ObserveFinalizeKind")) + + // For finalizing reconcilers, just observe when we aren't the leader. + reconcileEvent = rof.ObserveFinalizeKind(ctx, resource) } // Synchronize the status. @@ -190,6 +264,9 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // This is important because the copy we loaded from the injectionInformer's // cache may be stale and we don't want to overwrite a prior update // to status with this stale state. + } else if !isLeader { + logger.Warn("Saw status changes when we aren't the leader!") + // TODO: Consider logging the diff at Debug? } else if err = r.updateStatus(original, resource); err != nil { logger.Warnw("Failed to update resource status", zap.Error(err)) r.Recorder.Eventf(resource, v1.EventTypeWarning, "UpdateFailed", diff --git a/vendor/knative.dev/pkg/apis/duck/register.go b/vendor/knative.dev/pkg/apis/duck/register.go index d10adc21ce7e..d84cd49d18b0 100644 --- a/vendor/knative.dev/pkg/apis/duck/register.go +++ b/vendor/knative.dev/pkg/apis/duck/register.go @@ -18,4 +18,12 @@ package duck const ( GroupName = "duck.knative.dev" + + // AddressableDuckVersionLabel is the label we use to declare + // that a type conforms to the Addressable duck type. + AddressableDuckVersionLabel = "duck.knative.dev/addressable" + + // SourceDuckVersionLabel is the label we use to declare + // that a type conforms to the Source duck type. + SourceDuckVersionLabel = "duck.knative.dev/source" ) diff --git a/vendor/knative.dev/pkg/client/injection/kube/reconciler/core/v1/namespace/controller.go b/vendor/knative.dev/pkg/client/injection/kube/reconciler/core/v1/namespace/controller.go index 4cee36b435e1..a21da03bb912 100644 --- a/vendor/knative.dev/pkg/client/injection/kube/reconciler/core/v1/namespace/controller.go +++ b/vendor/knative.dev/pkg/client/injection/kube/reconciler/core/v1/namespace/controller.go @@ -25,6 +25,8 @@ import ( strings "strings" corev1 "k8s.io/api/core/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" scheme "k8s.io/client-go/kubernetes/scheme" v1 "k8s.io/client-go/kubernetes/typed/core/v1" @@ -33,6 +35,7 @@ import ( namespace "knative.dev/pkg/client/injection/kube/informers/core/v1/namespace" controller "knative.dev/pkg/controller" logging "knative.dev/pkg/logging" + reconciler "knative.dev/pkg/reconciler" ) const ( @@ -54,9 +57,27 @@ func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsF namespaceInformer := namespace.Get(ctx) + lister := namespaceInformer.Lister() + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, Client: client.Get(ctx), - Lister: namespaceInformer.Lister(), + Lister: lister, reconciler: r, finalizerName: defaultFinalizerName, } diff --git a/vendor/knative.dev/pkg/client/injection/kube/reconciler/core/v1/namespace/reconciler.go b/vendor/knative.dev/pkg/client/injection/kube/reconciler/core/v1/namespace/reconciler.go index 00c761a1d765..e7e27c0175c9 100644 --- a/vendor/knative.dev/pkg/client/injection/kube/reconciler/core/v1/namespace/reconciler.go +++ b/vendor/knative.dev/pkg/client/injection/kube/reconciler/core/v1/namespace/reconciler.go @@ -29,6 +29,7 @@ import ( equality "k8s.io/apimachinery/pkg/api/equality" errors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" types "k8s.io/apimachinery/pkg/types" sets "k8s.io/apimachinery/pkg/util/sets" kubernetes "k8s.io/client-go/kubernetes" @@ -63,8 +64,30 @@ type Finalizer interface { FinalizeKind(ctx context.Context, o *v1.Namespace) reconciler.Event } +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1.Namespace if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1.Namespace. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1.Namespace) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1.Namespace if they want to process tombstoned resources +// even when they are not the leader. Due to the nature of how finalizers are handled +// there are no guarantees that this will be called. +type ReadOnlyFinalizer interface { + // ObserveFinalizeKind implements custom logic to observe the final state of v1.Namespace. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1.Namespace) reconciler.Event +} + // reconcilerImpl implements controller.Reconciler for v1.Namespace resources. type reconcilerImpl struct { + // LeaderAwareFuncs is inlined to help us implement reconciler.LeaderAware + reconciler.LeaderAwareFuncs + // Client is used to write back status updates. Client kubernetes.Interface @@ -89,13 +112,39 @@ type reconcilerImpl struct { // Check that our Reconciler implements controller.Reconciler var _ controller.Reconciler = (*reconcilerImpl)(nil) +// Check that our generated Reconciler is always LeaderAware. +var _ reconciler.LeaderAware = (*reconcilerImpl)(nil) + func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client kubernetes.Interface, lister corev1.NamespaceLister, recorder record.EventRecorder, r Interface, options ...controller.Options) controller.Reconciler { // Check the options function input. It should be 0 or 1. if len(options) > 1 { logger.Fatalf("up to one options struct is supported, found %d", len(options)) } + // Fail fast when users inadvertently implement the other LeaderAware interface. + // For the typed reconcilers, Promote shouldn't take any arguments. + if _, ok := r.(reconciler.LeaderAware); ok { + logger.Fatalf("%T implements the incorrect LeaderAware interface. Promote() should not take an argument as genreconciler handles the enqueuing automatically.", r) + } + // TODO: Consider validating when folks implement ReadOnlyFinalizer, but not Finalizer. + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, Client: client, Lister: lister, Recorder: recorder, @@ -119,6 +168,25 @@ func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client kubern func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { logger := logging.FromContext(ctx) + // Convert the namespace/name string into a distinct namespace and name + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + logger.Errorf("invalid resource key: %s", key) + return nil + } + // Establish whether we are the leader for use below. + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer) + if !isLeader && !isROI && !isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return nil + } + // If configStore is set, attach the frozen configuration to the context. if r.configStore != nil { ctx = r.configStore.ToContext(ctx) @@ -127,15 +195,6 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // Add the recorder to context. ctx = controller.WithEventRecorder(ctx, r.Recorder) - // Convert the namespace/name string into a distinct namespace and name - - _, name, err := cache.SplitMetaNamespaceKey(key) - - if err != nil { - logger.Errorf("invalid resource key: %s", key) - return nil - } - // Get the resource with this namespace/name. getter := r.Lister @@ -155,20 +214,28 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { var reconcileEvent reconciler.Event if resource.GetDeletionTimestamp().IsZero() { - // Append the target method to the logger. - logger = logger.With(zap.String("targetMethod", "ReconcileKind")) + if isLeader { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ReconcileKind")) + + // Set and update the finalizer on resource if r.reconciler + // implements Finalizer. + if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { + return fmt.Errorf("failed to set finalizers: %w", err) + } - // Set and update the finalizer on resource if r.reconciler - // implements Finalizer. - if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { - return fmt.Errorf("failed to set finalizers: %w", err) - } + // Reconcile this copy of the resource and then write back any status + // updates regardless of whether the reconciliation errored out. + reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) - // Reconcile this copy of the resource and then write back any status - // updates regardless of whether the reconciliation errored out. - reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) + } else if isROI { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ObserveKind")) - } else if fin, ok := r.reconciler.(Finalizer); ok { + // Observe any changes to this resource, since we are not the leader. + reconcileEvent = roi.ObserveKind(ctx, resource) + } + } else if fin, ok := r.reconciler.(Finalizer); isLeader && ok { // Append the target method to the logger. logger = logger.With(zap.String("targetMethod", "FinalizeKind")) @@ -178,6 +245,12 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil { return fmt.Errorf("failed to clear finalizers: %w", err) } + } else if !isLeader && isROF { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ObserveFinalizeKind")) + + // For finalizing reconcilers, just observe when we aren't the leader. + reconcileEvent = rof.ObserveFinalizeKind(ctx, resource) } // Synchronize the status. @@ -186,6 +259,9 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // This is important because the copy we loaded from the injectionInformer's // cache may be stale and we don't want to overwrite a prior update // to status with this stale state. + } else if !isLeader { + logger.Warn("Saw status changes when we aren't the leader!") + // TODO: Consider logging the diff at Debug? } else if err = r.updateStatus(original, resource); err != nil { logger.Warnw("Failed to update resource status", zap.Error(err)) r.Recorder.Eventf(resource, v1.EventTypeWarning, "UpdateFailed", diff --git a/vendor/knative.dev/pkg/codegen/cmd/injection-gen/generators/reconciler_controller.go b/vendor/knative.dev/pkg/codegen/cmd/injection-gen/generators/reconciler_controller.go index bf3ca04fe82b..ac1c9fac3215 100644 --- a/vendor/knative.dev/pkg/codegen/cmd/injection-gen/generators/reconciler_controller.go +++ b/vendor/knative.dev/pkg/codegen/cmd/injection-gen/generators/reconciler_controller.go @@ -142,9 +142,21 @@ func (g *reconcilerControllerGenerator) GenerateType(c *generator.Context, t *ty Package: "context", Name: "Context", }), - "fmtSprintf": c.Universe.Function(types.Name{ - Package: "fmt", - Name: "Sprintf", + "reconcilerLeaderAwareFuncs": c.Universe.Type(types.Name{ + Package: "knative.dev/pkg/reconciler", + Name: "LeaderAwareFuncs", + }), + "reconcilerBucket": c.Universe.Type(types.Name{ + Package: "knative.dev/pkg/reconciler", + Name: "Bucket", + }), + "typesNamespacedName": c.Universe.Type(types.Name{ + Package: "k8s.io/apimachinery/pkg/types", + Name: "NamespacedName", + }), + "labelsEverything": c.Universe.Function(types.Name{ + Package: "k8s.io/apimachinery/pkg/labels", + Name: "Everything", }), "stringsReplaceAll": c.Universe.Function(types.Name{ Package: "strings", @@ -154,6 +166,10 @@ func (g *reconcilerControllerGenerator) GenerateType(c *generator.Context, t *ty Package: "reflect", Name: "TypeOf", }), + "fmtSprintf": c.Universe.Function(types.Name{ + Package: "fmt", + Name: "Sprintf", + }), } sw.Do(reconcilerControllerNewImpl, m) @@ -185,9 +201,27 @@ func NewImpl(ctx {{.contextContext|raw}}, r Interface{{if .hasClass}}, classValu {{.type|lowercaseSingular}}Informer := {{.informerGet|raw}}(ctx) + lister := {{.type|lowercaseSingular}}Informer.Lister() + rec := &reconcilerImpl{ + LeaderAwareFuncs: {{.reconcilerLeaderAwareFuncs|raw}}{ + PromoteFunc: func(bkt {{.reconcilerBucket|raw}}, enq func({{.reconcilerBucket|raw}}, {{.typesNamespacedName|raw}})) error { + all, err := lister.List({{.labelsEverything|raw}}()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, {{.typesNamespacedName|raw}}{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, Client: {{.clientGet|raw}}(ctx), - Lister: {{.type|lowercaseSingular}}Informer.Lister(), + Lister: lister, reconciler: r, finalizerName: defaultFinalizerName, {{if .hasClass}}classValue: classValue,{{end}} diff --git a/vendor/knative.dev/pkg/codegen/cmd/injection-gen/generators/reconciler_reconciler.go b/vendor/knative.dev/pkg/codegen/cmd/injection-gen/generators/reconciler_reconciler.go index df4d595ab244..b3d3596a1538 100644 --- a/vendor/knative.dev/pkg/codegen/cmd/injection-gen/generators/reconciler_reconciler.go +++ b/vendor/knative.dev/pkg/codegen/cmd/injection-gen/generators/reconciler_reconciler.go @@ -152,6 +152,30 @@ func (g *reconcilerReconcilerGenerator) GenerateType(c *generator.Context, t *ty "equalitySemantic": c.Universe.Package("k8s.io/apimachinery/pkg/api/equality").Variable("Semantic"), "jsonMarshal": c.Universe.Package("encoding/json").Function("Marshal"), "typesMergePatchType": c.Universe.Package("k8s.io/apimachinery/pkg/types").Constant("MergePatchType"), + "syncRWMutex": c.Universe.Type(types.Name{ + Package: "sync", + Name: "RWMutex", + }), + "reconcilerLeaderAware": c.Universe.Type(types.Name{ + Package: "knative.dev/pkg/reconciler", + Name: "LeaderAware", + }), + "reconcilerLeaderAwareFuncs": c.Universe.Type(types.Name{ + Package: "knative.dev/pkg/reconciler", + Name: "LeaderAwareFuncs", + }), + "reconcilerBucket": c.Universe.Type(types.Name{ + Package: "knative.dev/pkg/reconciler", + Name: "Bucket", + }), + "typesNamespacedName": c.Universe.Type(types.Name{ + Package: "k8s.io/apimachinery/pkg/types", + Name: "NamespacedName", + }), + "labelsEverything": c.Universe.Function(types.Name{ + Package: "k8s.io/apimachinery/pkg/labels", + Name: "Everything", + }), } sw.Do(reconcilerInterfaceFactory, m) @@ -187,8 +211,30 @@ type Finalizer interface { FinalizeKind(ctx {{.contextContext|raw}}, o *{{.type|raw}}) {{.reconcilerEvent|raw}} } +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling {{.type|raw}} if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe {{.type|raw}}. + // This method should not write to the API. + ObserveKind(ctx {{.contextContext|raw}}, o *{{.type|raw}}) {{.reconcilerEvent|raw}} +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing {{.type|raw}} if they want to process tombstoned resources +// even when they are not the leader. Due to the nature of how finalizers are handled +// there are no guarantees that this will be called. +type ReadOnlyFinalizer interface { + // ObserveFinalizeKind implements custom logic to observe the final state of {{.type|raw}}. + // This method should not write to the API. + ObserveFinalizeKind(ctx {{.contextContext|raw}}, o *{{.type|raw}}) {{.reconcilerEvent|raw}} +} + // reconcilerImpl implements controller.Reconciler for {{.type|raw}} resources. type reconcilerImpl struct { + // LeaderAwareFuncs is inlined to help us implement {{.reconcilerLeaderAware|raw}} + {{.reconcilerLeaderAwareFuncs|raw}} + // Client is used to write back status updates. Client {{.clientsetInterface|raw}} @@ -217,6 +263,8 @@ type reconcilerImpl struct { // Check that our Reconciler implements controller.Reconciler var _ controller.Reconciler = (*reconcilerImpl)(nil) +// Check that our generated Reconciler is always LeaderAware. +var _ {{.reconcilerLeaderAware|raw}} = (*reconcilerImpl)(nil) ` @@ -227,7 +275,30 @@ func NewReconciler(ctx {{.contextContext|raw}}, logger *{{.zapSugaredLogger|raw} logger.Fatalf("up to one options struct is supported, found %d", len(options)) } + // Fail fast when users inadvertently implement the other LeaderAware interface. + // For the typed reconcilers, Promote shouldn't take any arguments. + if _, ok := r.({{.reconcilerLeaderAware|raw}}); ok { + logger.Fatalf("%T implements the incorrect LeaderAware interface. Promote() should not take an argument as genreconciler handles the enqueuing automatically.", r) + } + // TODO: Consider validating when folks implement ReadOnlyFinalizer, but not Finalizer. + rec := &reconcilerImpl{ + LeaderAwareFuncs: {{.reconcilerLeaderAwareFuncs|raw}}{ + PromoteFunc: func(bkt {{.reconcilerBucket|raw}}, enq func({{.reconcilerBucket|raw}}, {{.typesNamespacedName|raw}})) error { + all, err := lister.List({{.labelsEverything|raw}}()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, {{.typesNamespacedName|raw}}{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, Client: client, Lister: lister, Recorder: recorder, @@ -254,6 +325,25 @@ var reconcilerImplFactory = ` func (r *reconcilerImpl) Reconcile(ctx {{.contextContext|raw}}, key string) error { logger := {{.loggingFromContext|raw}}(ctx) + // Convert the namespace/name string into a distinct namespace and name + namespace, name, err := {{.cacheSplitMetaNamespaceKey|raw}}(key) + if err != nil { + logger.Errorf("invalid resource key: %s", key) + return nil + } + // Establish whether we are the leader for use below. + isLeader := r.IsLeaderFor({{.typesNamespacedName|raw}}{ + Namespace: namespace, + Name: name, + }) + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer); + if !isLeader && !isROI && !isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return nil + } + // If configStore is set, attach the frozen configuration to the context. if r.configStore != nil { ctx = r.configStore.ToContext(ctx) @@ -262,19 +352,7 @@ func (r *reconcilerImpl) Reconcile(ctx {{.contextContext|raw}}, key string) erro // Add the recorder to context. ctx = {{.controllerWithEventRecorder|raw}}(ctx, r.Recorder) - // Convert the namespace/name string into a distinct namespace and name - {{if .nonNamespaced}} - _, name, err := {{.cacheSplitMetaNamespaceKey|raw}}(key) - {{else}} - namespace, name, err := {{.cacheSplitMetaNamespaceKey|raw}}(key) - {{end}} - if err != nil { - logger.Errorf("invalid resource key: %s", key) - return nil - } - // Get the resource with this namespace/name. - {{if .nonNamespaced}} getter := r.Lister {{else}} @@ -303,27 +381,34 @@ func (r *reconcilerImpl) Reconcile(ctx {{.contextContext|raw}}, key string) erro var reconcileEvent {{.reconcilerEvent|raw}} if resource.GetDeletionTimestamp().IsZero() { - // Append the target method to the logger. - logger = logger.With(zap.String("targetMethod", "ReconcileKind")) - - // Set and update the finalizer on resource if r.reconciler - // implements Finalizer. - if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { - return {{.fmtErrorf|raw}}("failed to set finalizers: %w", err) - } + if isLeader { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ReconcileKind")) + + // Set and update the finalizer on resource if r.reconciler + // implements Finalizer. + if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { + return {{.fmtErrorf|raw}}("failed to set finalizers: %w", err) + } + {{if .isKRShaped}} + reconciler.PreProcessReconcile(ctx, resource) + {{end}} - {{if .isKRShaped}} - reconciler.PreProcessReconcile(ctx, resource) - {{end}} + // Reconcile this copy of the resource and then write back any status + // updates regardless of whether the reconciliation errored out. + reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) - // Reconcile this copy of the resource and then write back any status - // updates regardless of whether the reconciliation errored out. - reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) + {{if .isKRShaped}} + reconciler.PostProcessReconcile(ctx, resource, original) + {{end}} + } else if isROI { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ObserveKind")) - {{if .isKRShaped}} - reconciler.PostProcessReconcile(ctx, resource, original) - {{end}} - } else if fin, ok := r.reconciler.(Finalizer); ok { + // Observe any changes to this resource, since we are not the leader. + reconcileEvent = roi.ObserveKind(ctx, resource) + } + } else if fin, ok := r.reconciler.(Finalizer); isLeader && ok { // Append the target method to the logger. logger = logger.With(zap.String("targetMethod", "FinalizeKind")) @@ -333,6 +418,12 @@ func (r *reconcilerImpl) Reconcile(ctx {{.contextContext|raw}}, key string) erro if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil { return {{.fmtErrorf|raw}}("failed to clear finalizers: %w", err) } + } else if !isLeader && isROF { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ObserveFinalizeKind")) + + // For finalizing reconcilers, just observe when we aren't the leader. + reconcileEvent = rof.ObserveFinalizeKind(ctx, resource) } // Synchronize the status. @@ -341,6 +432,9 @@ func (r *reconcilerImpl) Reconcile(ctx {{.contextContext|raw}}, key string) erro // This is important because the copy we loaded from the injectionInformer's // cache may be stale and we don't want to overwrite a prior update // to status with this stale state. + } else if !isLeader { + logger.Warn("Saw status changes when we aren't the leader!") + // TODO: Consider logging the diff at Debug? } else if err = r.updateStatus(original, resource); err != nil { logger.Warnw("Failed to update resource status", zap.Error(err)) r.Recorder.Eventf(resource, {{.corev1EventTypeWarning|raw}}, "UpdateFailed", @@ -520,4 +614,5 @@ func (r *reconcilerImpl) clearFinalizer(ctx {{.contextContext|raw}}, resource *{ // Synchronize the finalizers filtered by r.finalizerName. return r.updateFinalizersFiltered(ctx, resource) } + ` diff --git a/vendor/knative.dev/pkg/codegen/cmd/injection-gen/generators/reconciler_reconciler_stub.go b/vendor/knative.dev/pkg/codegen/cmd/injection-gen/generators/reconciler_reconciler_stub.go index 211b04080ee6..62805e3d2a7a 100644 --- a/vendor/knative.dev/pkg/codegen/cmd/injection-gen/generators/reconciler_reconciler_stub.go +++ b/vendor/knative.dev/pkg/codegen/cmd/injection-gen/generators/reconciler_reconciler_stub.go @@ -77,6 +77,14 @@ func (g *reconcilerReconcilerStubGenerator) GenerateType(c *generator.Context, t Package: g.reconcilerPkg, Name: "Finalizer", }), + "reconcilerReadOnlyInterface": c.Universe.Type(types.Name{ + Package: g.reconcilerPkg, + Name: "ReadOnlyInterface", + }), + "reconcilerReadOnlyFinalizer": c.Universe.Type(types.Name{ + Package: g.reconcilerPkg, + Name: "ReadOnlyFinalizer", + }), "corev1EventTypeNormal": c.Universe.Type(types.Name{ Package: "k8s.io/api/core/v1", Name: "EventTypeNormal", @@ -112,16 +120,26 @@ var _ {{.reconcilerInterface|raw}} = (*Reconciler)(nil) // Optionally check that our Reconciler implements Finalizer //var _ {{.reconcilerFinalizer|raw}} = (*Reconciler)(nil) +// Optionally check that our Reconciler implements ReadOnlyInterface +// Implement this to observe resources even when we are not the leader. +//var _ {{.reconcilerReadOnlyInterface|raw}} = (*Reconciler)(nil) + +// Optionally check that our Reconciler implements ReadOnlyFinalizer +// Implement this to observe tombstoned resources even when we are not +// the leader (best effort). +//var _ {{.reconcilerReadOnlyFinalizer|raw}} = (*Reconciler)(nil) // ReconcileKind implements Interface.ReconcileKind. func (r *Reconciler) ReconcileKind(ctx {{.contextContext|raw}}, o *{{.type|raw}}) {{.reconcilerEvent|raw}} { - // TODO: use this if the resource implements InitializeConditions. + {{if not .isKRShaped}}// TODO: use this if the resource implements InitializeConditions. // o.Status.InitializeConditions() + {{end}} // TODO: add custom reconciliation logic here. + {{if not .isKRShaped}} // TODO: use this if the object has .status.ObservedGeneration. - // o.Status.ObservedGeneration = o.Generation + // o.Status.ObservedGeneration = o.Generation{{end}} return newReconciledNormal(o.Namespace, o.Name) } @@ -131,4 +149,16 @@ func (r *Reconciler) ReconcileKind(ctx {{.contextContext|raw}}, o *{{.type|raw}} // // TODO: add custom finalization logic here. // return nil //} + +// Optionally, use ObserveKind to observe the resource when we are not the leader. +// func (r *Reconciler) ObserveKind(ctx {{.contextContext|raw}}, o *{{.type|raw}}) {{.reconcilerEvent|raw}} { +// // TODO: add custom observation logic here. +// return nil +// } + +// Optionally, use ObserveFinalizeKind to observe resources being finalized when we are no the leader. +//func (r *Reconciler) ObserveFinalizeKind(ctx {{.contextContext|raw}}, o *{{.type|raw}}) {{.reconcilerEvent|raw}} { +// // TODO: add custom observation logic here. +// return nil +//} ` diff --git a/vendor/knative.dev/pkg/hack/update-codegen.sh b/vendor/knative.dev/pkg/hack/update-codegen.sh index 1337f811001c..8a2198335036 100644 --- a/vendor/knative.dev/pkg/hack/update-codegen.sh +++ b/vendor/knative.dev/pkg/hack/update-codegen.sh @@ -19,6 +19,12 @@ set -o nounset set -o pipefail export GO111MODULE=on +# If we run with -mod=vendor here, then generate-groups.sh looks for vendor files in the wrong place. +export GOFLAGS=-mod= + +if [ -z "${GOPATH:-}" ]; then + export GOPATH=$(go env GOPATH) +fi source $(dirname $0)/../vendor/knative.dev/test-infra/scripts/library.sh diff --git a/vendor/knative.dev/pkg/testutils/clustermanager/perf-tests/main.go b/vendor/knative.dev/pkg/testutils/clustermanager/perf-tests/main.go index 70b0e88ba289..f8084c0780d0 100644 --- a/vendor/knative.dev/pkg/testutils/clustermanager/perf-tests/main.go +++ b/vendor/knative.dev/pkg/testutils/clustermanager/perf-tests/main.go @@ -27,6 +27,7 @@ import ( var ( isRecreate bool isReconcile bool + isDelete bool gcpProjectName string repoName string benchmarkRootFolder string @@ -40,10 +41,11 @@ func main() { flag.StringVar(&benchmarkRootFolder, "benchmark-root", "", "root folder of the benchmarks") flag.BoolVar(&isRecreate, "recreate", false, "is recreate operation or not") flag.BoolVar(&isReconcile, "reconcile", false, "is reconcile operation or not") + flag.BoolVar(&isDelete, "delete", false, "is delete operation or not") flag.Parse() - if isRecreate && isReconcile { - log.Fatal("Only one operation can be specified, either recreate or reconcile") + if (isRecreate && isReconcile) || (isRecreate && isDelete) || (isReconcile && isDelete) { + log.Fatal("--recreate, --reconcile and --delete are mutually exclusive") } client, err := testPkg.NewClient(gkeEnvironment) @@ -61,7 +63,12 @@ func main() { log.Fatalf("Failed reconciling clusters for repo %q: %v", repoName, err) } log.Printf("Done with reconciling clusters for repo %q", repoName) + case isDelete: + if err := client.DeleteClusters(gcpProjectName, repoName, benchmarkRootFolder); err != nil { + log.Fatalf("Failed deleting clusters for repo %q: %v", repoName, err) + } + log.Printf("Done with deleting clusters for repo %q", repoName) default: - log.Fatal("One operation must be specified, either recreate or reconcile") + log.Fatal("One operation must be specified, either recreate, reconcile or delete") } } diff --git a/vendor/knative.dev/pkg/testutils/clustermanager/perf-tests/pkg/cluster.go b/vendor/knative.dev/pkg/testutils/clustermanager/perf-tests/pkg/cluster.go index a388c5d75889..90f0ccfc5787 100644 --- a/vendor/knative.dev/pkg/testutils/clustermanager/perf-tests/pkg/cluster.go +++ b/vendor/knative.dev/pkg/testutils/clustermanager/perf-tests/pkg/cluster.go @@ -103,6 +103,19 @@ func (gc *gkeClient) ReconcileClusters(gcpProject, repo, benchmarkRoot string) e return gc.processClusters(gcpProject, repo, benchmarkRoot, handleExistingCluster, handleNewClusterConfig) } +// DeleteClusters will delete all existing clusters. +func (gc *gkeClient) DeleteClusters(gcpProject, repo, benchmarkRoot string) error { + handleExistingCluster := func(cluster container.Cluster, configExists bool, config ClusterConfig) error { + // retain the cluster, if the cluster config is unchanged + return gc.deleteClusterWithRetries(gcpProject, cluster) + } + handleNewClusterConfig := func(clusterName string, clusterConfig ClusterConfig) error { + // do nothing + return nil + } + return gc.processClusters(gcpProject, repo, benchmarkRoot, handleExistingCluster, handleNewClusterConfig) +} + // processClusters will process existing clusters and configs for new clusters, // with the corresponding functions provided by callers. func (gc *gkeClient) processClusters( diff --git a/vendor/knative.dev/pkg/webhook/conversion.go b/vendor/knative.dev/pkg/webhook/conversion.go index 146ac259f954..73cddd7a4b88 100644 --- a/vendor/knative.dev/pkg/webhook/conversion.go +++ b/vendor/knative.dev/pkg/webhook/conversion.go @@ -54,6 +54,10 @@ func conversionHandler(rootLogger *zap.SugaredLogger, stats StatsReporter, c Con ctx := logging.WithLogger(r.Context(), logger) response := apixv1.ConversionReview{ + // Use the same type meta as the request - this is required by the K8s API + // note: v1beta1 & v1 ConversionReview shapes are identical so even though + // we're using v1 types we still support v1beta1 conversion requests + TypeMeta: review.TypeMeta, Response: c.Convert(ctx, review.Request), } diff --git a/vendor/modules.txt b/vendor/modules.txt index f9e99e0bc5f3..a1e8fa379066 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1005,7 +1005,7 @@ knative.dev/caching/pkg/client/injection/informers/caching/v1alpha1/image/fake knative.dev/caching/pkg/client/injection/informers/factory knative.dev/caching/pkg/client/injection/informers/factory/fake knative.dev/caching/pkg/client/listers/caching/v1alpha1 -# knative.dev/networking v0.0.0-20200619041525-1faac2ec5d38 +# knative.dev/networking v0.0.0-20200622163826-421cd312c651 ## explicit knative.dev/networking/pkg/apis/config knative.dev/networking/pkg/apis/networking @@ -1032,7 +1032,7 @@ knative.dev/networking/pkg/client/injection/informers/networking/v1alpha1/server knative.dev/networking/pkg/client/injection/reconciler/networking/v1alpha1/serverlessservice knative.dev/networking/pkg/client/istio/listers/networking/v1alpha3 knative.dev/networking/pkg/client/listers/networking/v1alpha1 -# knative.dev/pkg v0.0.0-20200619182625-b6a13e2894ee +# knative.dev/pkg v0.0.0-20200622193027-602857dcc5f4 ## explicit knative.dev/pkg/apiextensions/storageversion knative.dev/pkg/apiextensions/storageversion/cmd/migrate @@ -1149,7 +1149,7 @@ knative.dev/pkg/webhook/resourcesemantics/conversion knative.dev/pkg/webhook/resourcesemantics/defaulting knative.dev/pkg/webhook/resourcesemantics/validation knative.dev/pkg/websocket -# knative.dev/test-infra v0.0.0-20200619200026-0b0587234302 +# knative.dev/test-infra v0.0.0-20200622185426-9cd9379661ea ## explicit knative.dev/test-infra/scripts # sigs.k8s.io/yaml v1.2.0