From 171ea467c8eb1b928399bcfbdebaa804ecd28d60 Mon Sep 17 00:00:00 2001 From: Lin Yang Date: Mon, 30 Oct 2023 10:34:12 +0800 Subject: [PATCH] feat: add RateLimitPolicy CRD (#78) * feat: [skip ci] add RateLimitPolicy CRD Signed-off-by: Lin Yang * feat: [skip ci] add comments Signed-off-by: Lin Yang * fix: json maker Signed-off-by: Lin Yang * wip: [skip ci] RateLimitPolicy Signed-off-by: Lin Yang * chore: initialize version v1.2.0-alpha.1 Signed-off-by: Lin Yang * wip: [skip ci] Signed-off-by: Lin Yang * feat: build config.json and enrich RateLimitPolicy Signed-off-by: Lin Yang * fix: golang lint Signed-off-by: Lin Yang * feat: detect conflicts Signed-off-by: Lin Yang * feat: sort rate limits by timestamp Signed-off-by: Lin Yang * fix: golang lint Signed-off-by: Lin Yang * fix: ratelimitpolicies RBAC Signed-off-by: Lin Yang * fix: ratelimitpolicies events Signed-off-by: Lin Yang * fix: register ratelimitpolicies reconciler Signed-off-by: Lin Yang * feat: enable the ratelimit policy matches multiple routes Signed-off-by: Lin Yang * feat: enable the ratelimit policy matches multiple gateway ports Signed-off-by: Lin Yang * feat: add feature flags to enable/disable validating hostnames of GatewayAPI resources (#83) * feat: bump fgw scripts and config chains (#85) Signed-off-by: Lin Yang * wip: [skip ci] refactoring RateLimitPolicy Signed-off-by: Lin Yang * cloud connector with gateway api (#86) * cloud connector with gateway api. * cloud connector with gateway api. * feat: new FGW options to control per-request/per-connection load balancing (#87) Signed-off-by: Lin Yang * fix: package-scripts Signed-off-by: Lin Yang * wip: [skip ci] Signed-off-by: Lin Yang * feat: add more options to configure FGW (#89) * feat: add more options to configure FGW Signed-off-by: Lin Yang * fix: make chart-readme Signed-off-by: Lin Yang --------- Signed-off-by: Lin Yang * fix: code checks [skip ci] Signed-off-by: Lin Yang * docs: add testcases for RateLimitPolicy Signed-off-by: Lin Yang * fix: register policy attachment scheme Signed-off-by: Lin Yang --------- Signed-off-by: Lin Yang Co-authored-by: Cybwan --- charts/fsm/templates/fsm-rbac.yaml | 11 + .../gateway.flomesh.io_ratelimitpolicies.yaml | 652 ++++++++++++++++++ cmd/fsm-controller/fsm-controller.go | 6 + codegen/gen-crd-client.sh | 5 +- docs/tests/gateway-api/README.md | 71 ++ pkg/announcements/types.go | 11 + pkg/apis/policyattachment/v1alpha1/doc.go | 5 + .../policyattachment/v1alpha1/ratelimit.go | 149 ++++ .../policyattachment/v1alpha1/register.go | 49 ++ .../v1alpha1/zz_generated.deepcopy.go | 327 +++++++++ pkg/constants/constants.go | 33 + .../gateway/v1beta1/gateway_controller.go | 24 +- .../v1alpha1/ratelimitpolicy_controller.go | 532 ++++++++++++++ .../policyattachment/v1alpha1/types.go | 30 + pkg/gateway/cache/cache.go | 26 +- pkg/gateway/cache/config.go | 167 ++++- pkg/gateway/cache/methods.go | 49 ++ pkg/gateway/cache/ratelimits_processor.go | 39 ++ pkg/gateway/cache/types.go | 21 + pkg/gateway/cache/utils.go | 14 + pkg/gateway/client.go | 11 + pkg/gateway/routecfg/types.go | 17 +- pkg/gateway/status/route.go | 4 +- pkg/gateway/utils/utils.go | 149 +++- .../clientset/versioned/clientset.go | 117 ++++ .../clientset/versioned/doc.go | 17 + .../versioned/fake/clientset_generated.go | 82 +++ .../clientset/versioned/fake/doc.go | 17 + .../clientset/versioned/fake/register.go | 53 ++ .../clientset/versioned/scheme/doc.go | 17 + .../clientset/versioned/scheme/register.go | 53 ++ .../typed/policyattachment/v1alpha1/doc.go | 17 + .../policyattachment/v1alpha1/fake/doc.go | 17 + .../fake/fake_policyattachment_client.go | 37 + .../v1alpha1/fake/fake_ratelimitpolicy.go | 139 ++++ .../v1alpha1/generated_expansion.go | 18 + .../v1alpha1/policyattachment_client.go | 104 +++ .../v1alpha1/ratelimitpolicy.go | 192 ++++++ .../informers/externalversions/factory.go | 248 +++++++ .../informers/externalversions/generic.go | 59 ++ .../internalinterfaces/factory_interfaces.go | 37 + .../policyattachment/interface.go | 43 ++ .../policyattachment/v1alpha1/interface.go | 42 ++ .../v1alpha1/ratelimitpolicy.go | 87 +++ .../v1alpha1/expansion_generated.go | 24 + .../v1alpha1/ratelimitpolicy.go | 96 +++ pkg/k8s/informers/informers.go | 13 + pkg/k8s/informers/types.go | 9 +- pkg/manager/reconciler/reconcilers.go | 2 + pkg/manager/webhook/webhooks.go | 2 + pkg/messaging/broker.go | 2 + pkg/webhook/gateway/gateway_webhook.go | 2 +- .../gatewayclass/gatewayclass_webhook.go | 2 +- pkg/webhook/grpcroute/grpcroute_webhook.go | 2 +- pkg/webhook/httproute/httproute_webhook.go | 2 +- pkg/webhook/ratelimit/webhook.go | 370 ++++++++++ pkg/webhook/tcproute/tcproute_webhook.go | 2 +- pkg/webhook/tlsroute/tlsproute_webhook.go | 2 +- 58 files changed, 4273 insertions(+), 55 deletions(-) create mode 100644 cmd/fsm-bootstrap/crds/gateway.flomesh.io_ratelimitpolicies.yaml create mode 100644 pkg/apis/policyattachment/v1alpha1/doc.go create mode 100644 pkg/apis/policyattachment/v1alpha1/ratelimit.go create mode 100644 pkg/apis/policyattachment/v1alpha1/register.go create mode 100644 pkg/apis/policyattachment/v1alpha1/zz_generated.deepcopy.go create mode 100644 pkg/controllers/policyattachment/v1alpha1/ratelimitpolicy_controller.go create mode 100644 pkg/controllers/policyattachment/v1alpha1/types.go create mode 100644 pkg/gateway/cache/ratelimits_processor.go create mode 100644 pkg/gen/client/policyattachment/clientset/versioned/clientset.go create mode 100644 pkg/gen/client/policyattachment/clientset/versioned/doc.go create mode 100644 pkg/gen/client/policyattachment/clientset/versioned/fake/clientset_generated.go create mode 100644 pkg/gen/client/policyattachment/clientset/versioned/fake/doc.go create mode 100644 pkg/gen/client/policyattachment/clientset/versioned/fake/register.go create mode 100644 pkg/gen/client/policyattachment/clientset/versioned/scheme/doc.go create mode 100644 pkg/gen/client/policyattachment/clientset/versioned/scheme/register.go create mode 100644 pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/doc.go create mode 100644 pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/fake/doc.go create mode 100644 pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/fake/fake_policyattachment_client.go create mode 100644 pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/fake/fake_ratelimitpolicy.go create mode 100644 pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/generated_expansion.go create mode 100644 pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/policyattachment_client.go create mode 100644 pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/ratelimitpolicy.go create mode 100644 pkg/gen/client/policyattachment/informers/externalversions/factory.go create mode 100644 pkg/gen/client/policyattachment/informers/externalversions/generic.go create mode 100644 pkg/gen/client/policyattachment/informers/externalversions/internalinterfaces/factory_interfaces.go create mode 100644 pkg/gen/client/policyattachment/informers/externalversions/policyattachment/interface.go create mode 100644 pkg/gen/client/policyattachment/informers/externalversions/policyattachment/v1alpha1/interface.go create mode 100644 pkg/gen/client/policyattachment/informers/externalversions/policyattachment/v1alpha1/ratelimitpolicy.go create mode 100644 pkg/gen/client/policyattachment/listers/policyattachment/v1alpha1/expansion_generated.go create mode 100644 pkg/gen/client/policyattachment/listers/policyattachment/v1alpha1/ratelimitpolicy.go create mode 100644 pkg/webhook/ratelimit/webhook.go diff --git a/charts/fsm/templates/fsm-rbac.yaml b/charts/fsm/templates/fsm-rbac.yaml index 9c6855f1a..caaa848a5 100644 --- a/charts/fsm/templates/fsm-rbac.yaml +++ b/charts/fsm/templates/fsm-rbac.yaml @@ -130,6 +130,17 @@ rules: resources: [ "gatewayclasses/status", "gateways/status", "httproutes/status", "grpcroutes/status", "referencegrants/status", "tcproutes/status", "tlsroutes/status", "udproutes/status" ] verbs: [ "get", "patch", "update" ] + # PolicyAttachment + - apiGroups: [ "gateway.flomesh.io" ] + resources: [ "ratelimitpolicies" ] + verbs: [ "get", "list", "watch", "create", "update", "patch", "delete" ] + - apiGroups: [ "gateway.flomesh.io" ] + resources: [ "ratelimitpolicies/finalizers" ] + verbs: [ "update" ] + - apiGroups: [ "gateway.flomesh.io" ] + resources: [ "ratelimitpolicies/status"] + verbs: [ "get", "patch", "update" ] + # Used for interacting with cert-manager CertificateRequest resources. - apiGroups: ["cert-manager.io"] resources: ["certificaterequests"] diff --git a/cmd/fsm-bootstrap/crds/gateway.flomesh.io_ratelimitpolicies.yaml b/cmd/fsm-bootstrap/crds/gateway.flomesh.io_ratelimitpolicies.yaml new file mode 100644 index 000000000..60ae30fff --- /dev/null +++ b/cmd/fsm-bootstrap/crds/gateway.flomesh.io_ratelimitpolicies.yaml @@ -0,0 +1,652 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + labels: + app.kubernetes.io/name: flomesh.io + name: ratelimitpolicies.gateway.flomesh.io +spec: + group: gateway.flomesh.io + names: + kind: RateLimitPolicy + listKind: RateLimitPolicyList + plural: ratelimitpolicies + singular: ratelimitpolicy + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: RateLimitPolicy is the Schema for the RateLimitPolicys API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: RateLimitPolicySpec defines the desired state of RateLimitPolicy + properties: + bps: + description: DefaultBPS is the default rate limit for all ports + format: int64 + type: integer + grpc: + description: GRPCRateLimits is the rate limit configuration for GRPC + routes + items: + description: GRPCRateLimit defines the rate limit configuration + for a GRPC route + properties: + match: + description: "GRPCRouteMatch defines the predicate used to match + requests to a given action. Multiple match types are ANDed + together, i.e. the match will evaluate to true only if all + conditions are satisfied. \n For example, the match below + will match a gRPC request only if its service is `foo` AND + it contains the `version: v1` header: \n ``` matches: - method: + type: Exact service: \"foo\" headers: - name: \"version\" + value \"v1\" \n ```" + properties: + headers: + description: Headers specifies gRPC request header matchers. + Multiple match values are ANDed together, meaning, a request + MUST match all the specified headers to select the route. + items: + description: GRPCHeaderMatch describes how to select a + gRPC route by matching gRPC request headers. + properties: + name: + description: "Name is the name of the gRPC Header + to be matched. \n If multiple entries specify equivalent + header names, only the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. + Due to the case-insensitivity of header names, \"foo\" + and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: Type specifies how to match against the + value of the header. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of the gRPC Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: Method specifies a gRPC request service/method + matcher. If this field is not specified, all services + and methods will match. + properties: + method: + description: "Value of the method to match against. + If left empty or omitted, will match all services. + \n At least one of Service and Method MUST be a non-empty + string." + maxLength: 1024 + type: string + service: + description: "Value of the service to match against. + If left empty or omitted, will match any service. + \n At least one of Service and Method MUST be a non-empty + string." + maxLength: 1024 + type: string + type: + default: Exact + description: "Type specifies how to match against the + service and/or method. Support: Core (Exact with service + and method specified) \n Support: Implementation-specific + (Exact with method specified but no service specified) + \n Support: Implementation-specific (RegularExpression)" + enum: + - Exact + - RegularExpression + type: string + type: object + type: object + rateLimit: + description: L7RateLimit defines the rate limit configuration + for a route + properties: + backlog: + default: 10 + description: Backlog is the number of requests allowed to + wait in the queue + type: integer + burst: + description: Burst is the number of requests allowed to + be bursted, if not specified, it will be the same as Requests + type: integer + mode: + default: Local + description: Mode is the mode of the rate limit policy, + Local or Global, default is Local + enum: + - Local + - Global + type: string + requests: + description: Requests is the number of requests allowed + per statTimeWindow + type: integer + responseHeadersToAdd: + additionalProperties: + type: string + description: ResponseHeadersToAdd is the response headers + to be added when the rate limit is exceeded + type: object + responseStatusCode: + default: 429 + description: ResponseStatusCode is the response status code + to be returned when the rate limit is exceeded + type: integer + statTimeWindow: + description: StatTimeWindow is the time window in seconds + type: integer + required: + - requests + - statTimeWindow + type: object + required: + - match + type: object + type: array + hostnames: + description: Hostnames is the rate limit configuration for hostnames + items: + description: HostnameRateLimit defines the rate limit configuration + for a hostname + properties: + hostname: + description: "Hostname is the fully qualified domain name of + a network host. This matches the RFC 1123 definition of a + hostname with 2 notable exceptions: \n 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). + The wildcard label must appear by itself as the first label. + \n Hostname can be \"precise\" which is a domain name without + the terminating dot of a network host (e.g. \"foo.example.com\") + or \"wildcard\", which is a domain name prefixed with a single + wildcard label (e.g. `*.example.com`). \n Note that as per + RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with + an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + rateLimit: + description: L7RateLimit defines the rate limit configuration + for a route + properties: + backlog: + default: 10 + description: Backlog is the number of requests allowed to + wait in the queue + type: integer + burst: + description: Burst is the number of requests allowed to + be bursted, if not specified, it will be the same as Requests + type: integer + mode: + default: Local + description: Mode is the mode of the rate limit policy, + Local or Global, default is Local + enum: + - Local + - Global + type: string + requests: + description: Requests is the number of requests allowed + per statTimeWindow + type: integer + responseHeadersToAdd: + additionalProperties: + type: string + description: ResponseHeadersToAdd is the response headers + to be added when the rate limit is exceeded + type: object + responseStatusCode: + default: 429 + description: ResponseStatusCode is the response status code + to be returned when the rate limit is exceeded + type: integer + statTimeWindow: + description: StatTimeWindow is the time window in seconds + type: integer + required: + - requests + - statTimeWindow + type: object + required: + - hostname + type: object + type: array + http: + description: HTTPRateLimits is the rate limit configuration for HTTP + routes + items: + description: HTTPRateLimit defines the rate limit configuration + for a HTTP route + properties: + match: + description: "HTTPRouteMatch defines the predicate used to match + requests to a given action. Multiple match types are ANDed + together, i.e. the match will evaluate to true only if all + conditions are satisfied. \n For example, the match below + will match a HTTP request only if its path starts with `/foo` + AND it contains the `version: v1` header: \n ``` match: \n + path: value: \"/foo\" headers: - name: \"version\" value \"v1\" + \n ```" + properties: + headers: + description: Headers specifies HTTP request header matchers. + Multiple match values are ANDed together, meaning, a request + must match all the specified headers to select the route. + items: + description: HTTPHeaderMatch describes how to select a + HTTP route by matching HTTP request headers. + properties: + name: + description: "Name is the name of the HTTP Header + to be matched. Name matching MUST be case insensitive. + (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent header + names, only the first entry with an equivalent name + MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. + Due to the case-insensitivity of header names, \"foo\" + and \"Foo\" are considered equivalent. \n When a + header is repeated in an HTTP request, it is implementation-specific + behavior as to how this is represented. Generally, + proxies should follow the guidance from the RFC: + https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 + regarding processing a repeated header, with special + handling for \"Set-Cookie\"." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: "Type specifies how to match against + the value of the header. \n Support: Core (Exact) + \n Support: Implementation-specific (RegularExpression) + \n Since RegularExpression HeaderMatchType has implementation-specific + conformance, implementations can support POSIX, + PCRE or any other dialects of regular expressions. + Please read the implementation's documentation to + determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to + be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: "Method specifies HTTP method matcher. When + specified, this route will be matched only if the request + has the specified method. \n Support: Extended" + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: Path specifies a HTTP request path matcher. + If this field is not specified, a default prefix match + on the "/" path is provided. + properties: + type: + default: PathPrefix + description: "Type specifies how to match against the + path Value. \n Support: Core (Exact, PathPrefix) \n + Support: Implementation-specific (RegularExpression)" + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + queryParams: + description: "QueryParams specifies HTTP query parameter + matchers. Multiple match values are ANDed together, meaning, + a request must match all the specified query parameters + to select the route. \n Support: Extended" + items: + description: HTTPQueryParamMatch describes how to select + a HTTP route by matching HTTP query parameters. + properties: + name: + description: "Name is the name of the HTTP query param + to be matched. This must be an exact string match. + (See https://tools.ietf.org/html/rfc7230#section-2.7.3). + \n If multiple entries specify equivalent query + param names, only the first entry with an equivalent + name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST + be ignored. \n If a query param is repeated in an + HTTP request, the behavior is purposely left undefined, + since different data planes have different capabilities. + However, it is *recommended* that implementations + should match against the first value of the param + if the data plane supports it, as this behavior + is expected in other load balancing contexts outside + of the Gateway API. \n Users SHOULD NOT route traffic + based on repeated query params to guard themselves + against potential differences in the implementations." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: "Type specifies how to match against + the value of the query parameter. \n Support: Extended + (Exact) \n Support: Implementation-specific (RegularExpression) + \n Since RegularExpression QueryParamMatchType has + Implementation-specific conformance, implementations + can support POSIX, PCRE or any other dialects of + regular expressions. Please read the implementation's + documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param + to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + rateLimit: + description: L7RateLimit defines the rate limit configuration + for a route + properties: + backlog: + default: 10 + description: Backlog is the number of requests allowed to + wait in the queue + type: integer + burst: + description: Burst is the number of requests allowed to + be bursted, if not specified, it will be the same as Requests + type: integer + mode: + default: Local + description: Mode is the mode of the rate limit policy, + Local or Global, default is Local + enum: + - Local + - Global + type: string + requests: + description: Requests is the number of requests allowed + per statTimeWindow + type: integer + responseHeadersToAdd: + additionalProperties: + type: string + description: ResponseHeadersToAdd is the response headers + to be added when the rate limit is exceeded + type: object + responseStatusCode: + default: 429 + description: ResponseStatusCode is the response status code + to be returned when the rate limit is exceeded + type: integer + statTimeWindow: + description: StatTimeWindow is the time window in seconds + type: integer + required: + - requests + - statTimeWindow + type: object + required: + - match + type: object + type: array + ports: + description: Ports is the rate limit configuration for ports + items: + description: PortRateLimit defines the rate limit configuration + for a port + properties: + bps: + format: int64 + type: integer + port: + description: PortNumber defines a network port. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - port + type: object + type: array + rateLimit: + description: DefaultRateLimit is the default rate limit for all routes + and hostnames + properties: + backlog: + default: 10 + description: Backlog is the number of requests allowed to wait + in the queue + type: integer + burst: + description: Burst is the number of requests allowed to be bursted, + if not specified, it will be the same as Requests + type: integer + mode: + default: Local + description: Mode is the mode of the rate limit policy, Local + or Global, default is Local + enum: + - Local + - Global + type: string + requests: + description: Requests is the number of requests allowed per statTimeWindow + type: integer + responseHeadersToAdd: + additionalProperties: + type: string + description: ResponseHeadersToAdd is the response headers to be + added when the rate limit is exceeded + type: object + responseStatusCode: + default: 429 + description: ResponseStatusCode is the response status code to + be returned when the rate limit is exceeded + type: integer + statTimeWindow: + description: StatTimeWindow is the time window in seconds + type: integer + required: + - requests + - statTimeWindow + type: object + targetRef: + description: TargetRef is the reference to the target resource to + which the policy is applied + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. When + unspecified, the local namespace is inferred. Even when policy + targets a resource in a different namespace, it MUST only apply + to traffic originating from the same namespace as the policy. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - targetRef + type: object + status: + description: RateLimitPolicyStatus defines the observed state of RateLimitPolicy + properties: + conditions: + description: Conditions describe the current conditions of the RateLimitPolicy. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/cmd/fsm-controller/fsm-controller.go b/cmd/fsm-controller/fsm-controller.go index f4176a9f0..3c2b891fd 100644 --- a/cmd/fsm-controller/fsm-controller.go +++ b/cmd/fsm-controller/fsm-controller.go @@ -13,6 +13,8 @@ import ( "strings" "time" + policyAttachmentClientset "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/clientset/versioned" + ctrl "sigs.k8s.io/controller-runtime" fctx "github.com/flomesh-io/fsm/pkg/context" @@ -38,6 +40,7 @@ import ( mcscheme "github.com/flomesh-io/fsm/pkg/gen/client/multicluster/clientset/versioned/scheme" nsigscheme "github.com/flomesh-io/fsm/pkg/gen/client/namespacedingress/clientset/versioned/scheme" + pascheme "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/clientset/versioned/scheme" "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/runtime" @@ -162,6 +165,7 @@ func init() { _ = gwscheme.AddToScheme(scheme) _ = mcscheme.AddToScheme(scheme) _ = nsigscheme.AddToScheme(scheme) + _ = pascheme.AddToScheme(scheme) } // TODO(#4502): This function can be deleted once we get rid of cert options. @@ -239,6 +243,7 @@ func main() { smiTrafficTargetClientSet := smiAccessClient.NewForConfigOrDie(kubeConfig) gatewayAPIClient := gatewayApiClientset.NewForConfigOrDie(kubeConfig) namespacedIngressClient := nsigClientset.NewForConfigOrDie(kubeConfig) + policyAttachmentClient := policyAttachmentClientset.NewForConfigOrDie(kubeConfig) informerCollection, err := informers.NewInformerCollection(meshName, stop, informers.WithKubeClient(kubeClient), @@ -250,6 +255,7 @@ func main() { informers.WithNetworkingClient(networkingClient), informers.WithIngressClient(kubeClient, namespacedIngressClient), informers.WithGatewayAPIClient(gatewayAPIClient), + informers.WithPolicyAttachmentClient(policyAttachmentClient), ) if err != nil { events.GenericEventRecorder().FatalEvent(err, events.InitializationError, "Error creating informer collection") diff --git a/codegen/gen-crd-client.sh b/codegen/gen-crd-client.sh index dd927df98..1fa4e08dd 100755 --- a/codegen/gen-crd-client.sh +++ b/codegen/gen-crd-client.sh @@ -88,4 +88,7 @@ echo "##### Generating flomesh.io plugin client ######" generate_client "plugin" "v1alpha1" echo "##### Generating flomesh.io NamespacedIngress client ######" -generate_client "namespacedingress" "v1alpha1" \ No newline at end of file +generate_client "namespacedingress" "v1alpha1" + +echo "##### Generating gateway.flomesh.io PolicyAttachment client ######" +generate_client "policyattachment" "v1alpha1" \ No newline at end of file diff --git a/docs/tests/gateway-api/README.md b/docs/tests/gateway-api/README.md index e15384872..f02e8a9cb 100644 --- a/docs/tests/gateway-api/README.md +++ b/docs/tests/gateway-api/README.md @@ -749,4 +749,75 @@ date: Thu, 06 Jul 2023 04:44:06 GMT

Object moved to here.

* Connection #0 to host bing.com left intact +``` + +### Test RateLimitPolicy + +#### Test Port Based Rate Limit +```shell +cat < 0 { + hostnamesRateLimits = append(hostnamesRateLimits, p) + } + if len(p.Spec.HTTPRateLimits) > 0 || len(p.Spec.GRPCRateLimits) > 0 { + routeRateLimits = append(routeRateLimits, p) + } + } + } + sort.Slice(hostnamesRateLimits, func(i, j int) bool { + if hostnamesRateLimits[i].CreationTimestamp.Time.Equal(hostnamesRateLimits[j].CreationTimestamp.Time) { + return hostnamesRateLimits[i].Name < hostnamesRateLimits[j].Name + } + + return hostnamesRateLimits[i].CreationTimestamp.Time.Before(hostnamesRateLimits[j].CreationTimestamp.Time) + }) + sort.Slice(routeRateLimits, func(i, j int) bool { + if routeRateLimits[i].CreationTimestamp.Time.Equal(routeRateLimits[j].CreationTimestamp.Time) { + return routeRateLimits[i].Name < routeRateLimits[j].Name + } + + return routeRateLimits[i].CreationTimestamp.Time.Before(routeRateLimits[j].CreationTimestamp.Time) + }) + + switch route := route.(type) { + case *gwv1beta1.HTTPRoute: + info := routeInfo{ + meta: route, + parents: route.Status.Parents, + gvk: route.GroupVersionKind(), + generation: route.Generation, + hostnames: route.Spec.Hostnames, + } + if conflict := r.getConflictedHostnamesBasedRateLimitPolicy(info, rateLimitPolicy, hostnamesRateLimits); conflict != nil { + return conflict + } + if conflict := r.getConflictedRouteBasedRateLimitPolicy(route, rateLimitPolicy, routeRateLimits); conflict != nil { + return conflict + } + + case *gwv1alpha2.GRPCRoute: + info := routeInfo{ + meta: route, + parents: route.Status.Parents, + gvk: route.GroupVersionKind(), + generation: route.Generation, + hostnames: route.Spec.Hostnames, + } + if conflict := r.getConflictedHostnamesBasedRateLimitPolicy(info, rateLimitPolicy, hostnamesRateLimits); conflict != nil { + return conflict + } + if conflict := r.getConflictedRouteBasedRateLimitPolicy(route, rateLimitPolicy, routeRateLimits); conflict != nil { + return conflict + } + } + + return nil +} + +type routeInfo struct { + meta metav1.Object + parents []gwv1beta1.RouteParentStatus + gvk schema.GroupVersionKind + generation int64 + hostnames []gwv1beta1.Hostname +} + +func (r *rateLimitPolicyReconciler) getConflictedHostnamesBasedRateLimitPolicy(route routeInfo, rateLimitPolicy *gwpav1alpha1.RateLimitPolicy, hostnamesRateLimits []gwpav1alpha1.RateLimitPolicy) *types.NamespacedName { + if len(rateLimitPolicy.Spec.Hostnames) == 0 { + return nil + } + + for _, parent := range route.parents { + if metautil.IsStatusConditionTrue(parent.Conditions, string(gwv1beta1.RouteConditionAccepted)) { + key := getRouteParentKey(route.meta, parent) + + gateway := &gwv1beta1.Gateway{} + if err := r.fctx.Get(context.TODO(), key, gateway); err != nil { + continue + } + + validListeners := gwutils.GetValidListenersFromGateway(gateway) + + allowedListeners := gwutils.GetAllowedListeners(parent.ParentRef, route.gvk, route.generation, validListeners) + for _, listener := range allowedListeners { + hostnames := gwutils.GetValidHostnames(listener.Hostname, route.hostnames) + if len(hostnames) == 0 { + // no valid hostnames, should ignore it + continue + } + for _, hostname := range hostnames { + for _, hr := range hostnamesRateLimits { + r1 := gwutils.GetRateLimitIfRouteHostnameMatchesPolicy(hostname, hr) + if r1 == nil { + continue + } + + r2 := gwutils.GetRateLimitIfRouteHostnameMatchesPolicy(hostname, *rateLimitPolicy) + if r2 == nil { + continue + } + + if reflect.DeepEqual(r1, r2) { + continue + } + + return &types.NamespacedName{ + Name: hr.Name, + Namespace: hr.Namespace, + } + } + } + } + } + } + + return nil +} + +func (r *rateLimitPolicyReconciler) getConflictedRouteBasedRateLimitPolicy(route client.Object, rateLimitPolicy *gwpav1alpha1.RateLimitPolicy, routeRateLimits []gwpav1alpha1.RateLimitPolicy) *types.NamespacedName { + if len(rateLimitPolicy.Spec.HTTPRateLimits) == 0 && + len(rateLimitPolicy.Spec.GRPCRateLimits) == 0 { + return nil + } + + switch route := route.(type) { + case *gwv1beta1.HTTPRoute: + for _, rule := range route.Spec.Rules { + for _, m := range rule.Matches { + for _, rateLimit := range routeRateLimits { + if len(rateLimit.Spec.HTTPRateLimits) == 0 { + continue + } + + r1 := gwutils.GetRateLimitIfHTTPRouteMatchesPolicy(m, rateLimit) + if r1 == nil { + continue + } + + r2 := gwutils.GetRateLimitIfHTTPRouteMatchesPolicy(m, *rateLimitPolicy) + if r2 == nil { + continue + } + + if reflect.DeepEqual(r1, r2) { + continue + } + + return &types.NamespacedName{ + Name: rateLimit.Name, + Namespace: rateLimit.Namespace, + } + } + } + } + case *gwv1alpha2.GRPCRoute: + for _, rule := range route.Spec.Rules { + for _, m := range rule.Matches { + for _, rr := range routeRateLimits { + if len(rr.Spec.GRPCRateLimits) == 0 { + continue + } + + r1 := gwutils.GetRateLimitIfGRPCRouteMatchesPolicy(m, rr) + if r1 == nil { + continue + } + + r2 := gwutils.GetRateLimitIfGRPCRouteMatchesPolicy(m, *rateLimitPolicy) + if r2 == nil { + continue + } + + if reflect.DeepEqual(r1, r2) { + continue + } + + return &types.NamespacedName{ + Name: rr.Name, + Namespace: rr.Namespace, + } + } + } + } + } + + return nil +} + +func getRouteParentKey(route metav1.Object, parent gwv1beta1.RouteParentStatus) types.NamespacedName { + key := types.NamespacedName{Name: string(parent.ParentRef.Name), Namespace: route.GetNamespace()} + if parent.ParentRef.Namespace != nil { + key.Namespace = string(*parent.ParentRef.Namespace) + } + + return key +} + +func getConflictedPort(gateway *gwv1beta1.Gateway, rateLimitPolicy *gwpav1alpha1.RateLimitPolicy, allRateLimitPolicies *gwpav1alpha1.RateLimitPolicyList) *types.NamespacedName { + if len(rateLimitPolicy.Spec.Ports) == 0 { + return nil + } + + validListeners := gwutils.GetValidListenersFromGateway(gateway) + for _, p := range allRateLimitPolicies.Items { + pr := p + if gwutils.IsAcceptedRateLimitPolicy(&pr) && + gwutils.IsRefToTarget(pr.Spec.TargetRef, gwutils.ObjectKey(gateway)) && + len(pr.Spec.Ports) > 0 { + for _, listener := range validListeners { + r1 := gwutils.GetRateLimitIfPortMatchesPolicy(listener.Port, pr) + if r1 == nil { + continue + } + + r2 := gwutils.GetRateLimitIfPortMatchesPolicy(listener.Port, *rateLimitPolicy) + if r2 == nil { + continue + } + + if *r1 == *r2 { + continue + } + + return &types.NamespacedName{ + Name: pr.Name, + Namespace: pr.Namespace, + } + } + } + } + + return nil +} + +func getTargetNamespace(policy *gwpav1alpha1.RateLimitPolicy) string { + if policy.Spec.TargetRef.Namespace == nil { + return policy.Namespace + } + + return string(*policy.Spec.TargetRef.Namespace) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *rateLimitPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&gwpav1alpha1.RateLimitPolicy{}). + Complete(r) +} diff --git a/pkg/controllers/policyattachment/v1alpha1/types.go b/pkg/controllers/policyattachment/v1alpha1/types.go new file mode 100644 index 000000000..21a3e64b9 --- /dev/null +++ b/pkg/controllers/policyattachment/v1alpha1/types.go @@ -0,0 +1,30 @@ +/* + * MIT License + * + * Copyright (c) since 2021, flomesh.io Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +// Package v1beta1 contains controller logic for the Gateway API v1beta1. +package v1beta1 + +//var ( +// log = logger.New("gatewayapi/policyattachment") +//) diff --git a/pkg/gateway/cache/cache.go b/pkg/gateway/cache/cache.go index 14ccac326..174ad2986 100644 --- a/pkg/gateway/cache/cache.go +++ b/pkg/gateway/cache/cache.go @@ -13,15 +13,16 @@ import ( gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" "github.com/flomesh-io/fsm/pkg/configurator" + "github.com/flomesh-io/fsm/pkg/constants" "github.com/flomesh-io/fsm/pkg/k8s/informers" "github.com/flomesh-io/fsm/pkg/repo" ) var ( - httpRouteGVK = schema.FromAPIVersionAndKind(gwv1beta1.GroupVersion.String(), "HTTPRoute") - tlsRouteGVK = schema.FromAPIVersionAndKind(gwv1alpha2.GroupVersion.String(), "TLSRoute") - tcpRouteGVK = schema.FromAPIVersionAndKind(gwv1alpha2.GroupVersion.String(), "TCPRoute") - grpcRouteGVK = schema.FromAPIVersionAndKind(gwv1alpha2.GroupVersion.String(), "GRPCRoute") + httpRouteGVK = schema.FromAPIVersionAndKind(gwv1beta1.GroupVersion.String(), constants.HTTPRouteKind) + tlsRouteGVK = schema.FromAPIVersionAndKind(gwv1alpha2.GroupVersion.String(), constants.TLSRouteKind) + tcpRouteGVK = schema.FromAPIVersionAndKind(gwv1alpha2.GroupVersion.String(), constants.TCPRouteKind) + grpcRouteGVK = schema.FromAPIVersionAndKind(gwv1alpha2.GroupVersion.String(), constants.GRPCRouteKind) ) // GatewayCache is a cache of all the resources that are relevant to the gateway @@ -44,6 +45,7 @@ type GatewayCache struct { grpcroutes map[client.ObjectKey]struct{} tcproutes map[client.ObjectKey]struct{} tlsroutes map[client.ObjectKey]struct{} + ratelimits map[client.ObjectKey]struct{} mutex *sync.RWMutex } @@ -62,13 +64,14 @@ func NewGatewayCache(informerCollection *informers.InformerCollection, kubeClien ServiceImportsProcessorType: &ServiceImportsProcessor{}, EndpointSlicesProcessorType: &EndpointSlicesProcessor{}, //EndpointsProcessorType: &EndpointsProcessor{}, - SecretsProcessorType: &SecretProcessor{}, - GatewayClassesProcessorType: &GatewayClassesProcessor{}, - GatewaysProcessorType: &GatewaysProcessor{}, - HTTPRoutesProcessorType: &HTTPRoutesProcessor{}, - GRPCRoutesProcessorType: &GRPCRoutesProcessor{}, - TCPRoutesProcessorType: &TCPRoutesProcessor{}, - TLSRoutesProcessorType: &TLSRoutesProcessor{}, + SecretsProcessorType: &SecretProcessor{}, + GatewayClassesProcessorType: &GatewayClassesProcessor{}, + GatewaysProcessorType: &GatewaysProcessor{}, + HTTPRoutesProcessorType: &HTTPRoutesProcessor{}, + GRPCRoutesProcessorType: &GRPCRoutesProcessor{}, + TCPRoutesProcessorType: &TCPRoutesProcessor{}, + TLSRoutesProcessorType: &TLSRoutesProcessor{}, + RateLimitPoliciesProcessorType: &RateLimitPoliciesProcessor{}, }, gateways: make(map[string]client.ObjectKey), @@ -81,6 +84,7 @@ func NewGatewayCache(informerCollection *informers.InformerCollection, kubeClien grpcroutes: make(map[client.ObjectKey]struct{}), tcproutes: make(map[client.ObjectKey]struct{}), tlsroutes: make(map[client.ObjectKey]struct{}), + ratelimits: make(map[client.ObjectKey]struct{}), mutex: new(sync.RWMutex), } diff --git a/pkg/gateway/cache/config.go b/pkg/gateway/cache/config.go index 9d6c14fbc..5107c1368 100644 --- a/pkg/gateway/cache/config.go +++ b/pkg/gateway/cache/config.go @@ -2,6 +2,9 @@ package cache import ( "fmt" + "sort" + + gwpav1alpha1 "github.com/flomesh-io/fsm/pkg/apis/policyattachment/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -42,9 +45,10 @@ func (c *GatewayCache) BuildConfigs() { gw := obj.(*gwv1beta1.Gateway) validListeners := gwutils.GetValidListenersFromGateway(gw) log.Debug().Msgf("[GW-CACHE] validListeners: %v", validListeners) + acceptedRateLimits := c.rateLimits() - listenerCfg := c.listeners(gw, validListeners) - rules, referredServices := c.routeRules(gw, validListeners) + listenerCfg := c.listeners(gw, validListeners, acceptedRateLimits) + rules, referredServices := c.routeRules(gw, validListeners, acceptedRateLimits) svcConfigs := c.serviceConfigs(referredServices) configSpec := &routecfg.ConfigSpec{ @@ -135,7 +139,7 @@ func (c *GatewayCache) isDebugEnabled() bool { } } -func (c *GatewayCache) listeners(gw *gwv1beta1.Gateway, validListeners []gwtypes.Listener) []routecfg.Listener { +func (c *GatewayCache) listeners(gw *gwv1beta1.Gateway, validListeners []gwtypes.Listener, acceptedRateLimits map[RateLimitPolicyMatchType][]gwpav1alpha1.RateLimitPolicy) []routecfg.Listener { listeners := make([]routecfg.Listener, 0) for _, l := range validListeners { listener := routecfg.Listener{ @@ -148,12 +152,79 @@ func (c *GatewayCache) listeners(gw *gwv1beta1.Gateway, validListeners []gwtypes listener.TLS = tls } + l4RateLimits := acceptedRateLimits[RateLimitPolicyMatchTypePort] + if len(l4RateLimits) > 0 { + for _, rateLimit := range l4RateLimits { + if !gwutils.IsRefToTarget(rateLimit.Spec.TargetRef, gwutils.ObjectKey(gw)) { + continue + } + + if len(rateLimit.Spec.Ports) == 0 { + continue + } + + if r := gwutils.GetRateLimitIfPortMatchesPolicy(l.Port, rateLimit); r != nil && listener.BpsLimit == nil { + listener.BpsLimit = r + } + } + } + listeners = append(listeners, listener) } return listeners } +func (c *GatewayCache) rateLimits() map[RateLimitPolicyMatchType][]gwpav1alpha1.RateLimitPolicy { + rateLimits := make(map[RateLimitPolicyMatchType][]gwpav1alpha1.RateLimitPolicy) + for _, matchType := range []RateLimitPolicyMatchType{ + RateLimitPolicyMatchTypePort, + RateLimitPolicyMatchTypeHostnames, + RateLimitPolicyMatchTypeRoute, + } { + rateLimits[matchType] = make([]gwpav1alpha1.RateLimitPolicy, 0) + } + + for key := range c.ratelimits { + policy, exists, err := c.informers.GetByKey(informers.InformerKeyRateLimitPolicy, key.String()) + if !exists { + log.Error().Msgf("RateLimitPolicy %s does not exist", key) + continue + } + + if err != nil { + log.Error().Msgf("Failed to get RateLimitPolicy %s: %s", key, err) + continue + } + + rateLimitPolicy := policy.(*gwpav1alpha1.RateLimitPolicy) + if gwutils.IsAcceptedRateLimitPolicy(rateLimitPolicy) { + switch { + case len(rateLimitPolicy.Spec.Ports) > 0: + rateLimits[RateLimitPolicyMatchTypePort] = append(rateLimits[RateLimitPolicyMatchTypePort], *rateLimitPolicy) + case len(rateLimitPolicy.Spec.Hostnames) > 0: + rateLimits[RateLimitPolicyMatchTypeHostnames] = append(rateLimits[RateLimitPolicyMatchTypeHostnames], *rateLimitPolicy) + case len(rateLimitPolicy.Spec.HTTPRateLimits) > 0 || len(rateLimitPolicy.Spec.GRPCRateLimits) > 0: + rateLimits[RateLimitPolicyMatchTypeRoute] = append(rateLimits[RateLimitPolicyMatchTypeRoute], *rateLimitPolicy) + } + } + } + + // sort each type of rate limits by creation timestamp + for matchType, policies := range rateLimits { + sort.Slice(policies, func(i, j int) bool { + if policies[i].CreationTimestamp.Time.Equal(policies[j].CreationTimestamp.Time) { + return policies[i].Name < policies[j].Name + } + + return policies[i].CreationTimestamp.Time.Before(policies[j].CreationTimestamp.Time) + }) + rateLimits[matchType] = policies + } + + return rateLimits +} + func (c *GatewayCache) listenPort(l gwtypes.Listener) gwv1beta1.PortNumber { if l.Port < 1024 { return l.Port + 60000 @@ -234,7 +305,7 @@ func (c *GatewayCache) certificates(gw *gwv1beta1.Gateway, l gwtypes.Listener) [ return certs } -func (c *GatewayCache) routeRules(gw *gwv1beta1.Gateway, validListeners []gwtypes.Listener) (map[int32]routecfg.RouteRule, map[string]serviceInfo) { +func (c *GatewayCache) routeRules(gw *gwv1beta1.Gateway, validListeners []gwtypes.Listener, acceptedRateLimits map[RateLimitPolicyMatchType][]gwpav1alpha1.RateLimitPolicy) (map[int32]routecfg.RouteRule, map[string]serviceInfo) { rules := make(map[int32]routecfg.RouteRule) services := make(map[string]serviceInfo) @@ -253,7 +324,7 @@ func (c *GatewayCache) routeRules(gw *gwv1beta1.Gateway, validListeners []gwtype httpRoute := route.(*gwv1beta1.HTTPRoute) log.Debug().Msgf("Processing HTTPRoute %v", httpRoute) - processHTTPRoute(gw, validListeners, httpRoute, rules, services) + processHTTPRoute(gw, validListeners, httpRoute, acceptedRateLimits, rules, services) //processHTTPRouteBackendFilters(httpRoute, services) } @@ -271,7 +342,7 @@ func (c *GatewayCache) routeRules(gw *gwv1beta1.Gateway, validListeners []gwtype } grpcRoute := route.(*gwv1alpha2.GRPCRoute) - processGRPCRoute(gw, validListeners, grpcRoute, rules, services) + processGRPCRoute(gw, validListeners, grpcRoute, acceptedRateLimits, rules, services) //processGRPCRouteBackendFilters(grpcRoute, services) } @@ -314,7 +385,25 @@ func (c *GatewayCache) routeRules(gw *gwv1beta1.Gateway, validListeners []gwtype return rules, services } -func processHTTPRoute(gw *gwv1beta1.Gateway, validListeners []gwtypes.Listener, httpRoute *gwv1beta1.HTTPRoute, rules map[int32]routecfg.RouteRule, services map[string]serviceInfo) { +func processHTTPRoute(gw *gwv1beta1.Gateway, validListeners []gwtypes.Listener, httpRoute *gwv1beta1.HTTPRoute, acceptedRateLimits map[RateLimitPolicyMatchType][]gwpav1alpha1.RateLimitPolicy, rules map[int32]routecfg.RouteRule, services map[string]serviceInfo) { + hostnamesRateLimits := make([]gwpav1alpha1.RateLimitPolicy, 0) + if len(acceptedRateLimits[RateLimitPolicyMatchTypeHostnames]) > 0 { + for _, rateLimit := range acceptedRateLimits[RateLimitPolicyMatchTypeHostnames] { + if gwutils.IsRefToTarget(rateLimit.Spec.TargetRef, gwutils.ObjectKey(httpRoute)) { + hostnamesRateLimits = append(hostnamesRateLimits, rateLimit) + } + } + } + + routeRateLimits := make([]gwpav1alpha1.RateLimitPolicy, 0) + if len(acceptedRateLimits[RateLimitPolicyMatchTypeRoute]) > 0 { + for _, rateLimit := range acceptedRateLimits[RateLimitPolicyMatchTypeRoute] { + if gwutils.IsRefToTarget(rateLimit.Spec.TargetRef, gwutils.ObjectKey(httpRoute)) { + routeRateLimits = append(routeRateLimits, rateLimit) + } + } + } + for _, ref := range httpRoute.Spec.ParentRefs { if !gwutils.IsRefToGateway(ref, gwutils.ObjectKey(gw)) { continue @@ -337,7 +426,15 @@ func processHTTPRoute(gw *gwv1beta1.Gateway, validListeners []gwtypes.Listener, httpRule := routecfg.L7RouteRule{} for _, hostname := range hostnames { - httpRule[hostname] = generateHTTPRouteConfig(httpRoute, services) + r := generateHTTPRouteConfig(httpRoute, routeRateLimits, services) + + for _, rateLimit := range hostnamesRateLimits { + if rl := gwutils.GetRateLimitIfRouteHostnameMatchesPolicy(hostname, rateLimit); rl != nil && r.RateLimit == nil { + r.RateLimit = newRateLimitConfig(rl) + } + } + + httpRule[hostname] = r } port := int32(listener.Port) @@ -352,7 +449,25 @@ func processHTTPRoute(gw *gwv1beta1.Gateway, validListeners []gwtypes.Listener, } } -func processGRPCRoute(gw *gwv1beta1.Gateway, validListeners []gwtypes.Listener, grpcRoute *gwv1alpha2.GRPCRoute, rules map[int32]routecfg.RouteRule, services map[string]serviceInfo) { +func processGRPCRoute(gw *gwv1beta1.Gateway, validListeners []gwtypes.Listener, grpcRoute *gwv1alpha2.GRPCRoute, acceptedRateLimits map[RateLimitPolicyMatchType][]gwpav1alpha1.RateLimitPolicy, rules map[int32]routecfg.RouteRule, services map[string]serviceInfo) { + hostnamesRateLimits := make([]gwpav1alpha1.RateLimitPolicy, 0) + if len(acceptedRateLimits[RateLimitPolicyMatchTypeHostnames]) > 0 { + for _, rateLimit := range acceptedRateLimits[RateLimitPolicyMatchTypeHostnames] { + if gwutils.IsRefToTarget(rateLimit.Spec.TargetRef, gwutils.ObjectKey(grpcRoute)) { + hostnamesRateLimits = append(hostnamesRateLimits, rateLimit) + } + } + } + + routeRateLimits := make([]gwpav1alpha1.RateLimitPolicy, 0) + if len(acceptedRateLimits[RateLimitPolicyMatchTypeRoute]) > 0 { + for _, rateLimit := range acceptedRateLimits[RateLimitPolicyMatchTypeRoute] { + if gwutils.IsRefToTarget(rateLimit.Spec.TargetRef, gwutils.ObjectKey(grpcRoute)) { + routeRateLimits = append(routeRateLimits, rateLimit) + } + } + } + for _, ref := range grpcRoute.Spec.ParentRefs { if !gwutils.IsRefToGateway(ref, gwutils.ObjectKey(gw)) { continue @@ -373,7 +488,15 @@ func processGRPCRoute(gw *gwv1beta1.Gateway, validListeners []gwtypes.Listener, grpcRule := routecfg.L7RouteRule{} for _, hostname := range hostnames { - grpcRule[hostname] = generateGRPCRouteCfg(grpcRoute, services) + r := generateGRPCRouteCfg(grpcRoute, routeRateLimits, services) + + for _, rateLimit := range hostnamesRateLimits { + if rl := gwutils.GetRateLimitIfRouteHostnameMatchesPolicy(hostname, rateLimit); rl != nil && r.RateLimit == nil { + r.RateLimit = newRateLimitConfig(rl) + } + } + + grpcRule[hostname] = r } port := int32(listener.Port) @@ -599,7 +722,7 @@ func (c *GatewayCache) chains() routecfg.Chains { } } -func generateHTTPRouteConfig(httpRoute *gwv1beta1.HTTPRoute, services map[string]serviceInfo) routecfg.HTTPRouteRuleSpec { +func generateHTTPRouteConfig(httpRoute *gwv1beta1.HTTPRoute, routeRateLimits []gwpav1alpha1.RateLimitPolicy, services map[string]serviceInfo) routecfg.HTTPRouteRuleSpec { httpSpec := routecfg.HTTPRouteRuleSpec{ RouteType: routecfg.L7RouteTypeHTTP, Matches: make([]routecfg.HTTPTrafficMatch, 0), @@ -656,13 +779,23 @@ func generateHTTPRouteConfig(httpRoute *gwv1beta1.HTTPRoute, services map[string match.RequestParams = httpMatchQueryParams(m) } + for _, rateLimit := range routeRateLimits { + if len(rateLimit.Spec.HTTPRateLimits) == 0 { + continue + } + + if r := gwutils.GetRateLimitIfHTTPRouteMatchesPolicy(m, rateLimit); r != nil && match.RateLimit == nil { + match.RateLimit = newRateLimitConfig(r) + } + } + httpSpec.Matches = append(httpSpec.Matches, match) } } return httpSpec } -func generateGRPCRouteCfg(grpcRoute *gwv1alpha2.GRPCRoute, services map[string]serviceInfo) routecfg.GRPCRouteRuleSpec { +func generateGRPCRouteCfg(grpcRoute *gwv1alpha2.GRPCRoute, routeRateLimits []gwpav1alpha1.RateLimitPolicy, services map[string]serviceInfo) routecfg.GRPCRouteRuleSpec { grpcSpec := routecfg.GRPCRouteRuleSpec{ RouteType: routecfg.L7RouteTypeGRPC, Matches: make([]routecfg.GRPCTrafficMatch, 0), @@ -712,6 +845,16 @@ func generateGRPCRouteCfg(grpcRoute *gwv1alpha2.GRPCRoute, services map[string]s match.Headers = grpcMatchHeaders(m) } + for _, rateLimit := range routeRateLimits { + if len(rateLimit.Spec.GRPCRateLimits) == 0 { + continue + } + + if r := gwutils.GetRateLimitIfGRPCRouteMatchesPolicy(m, rateLimit); r != nil && match.RateLimit == nil { + match.RateLimit = newRateLimitConfig(r) + } + } + grpcSpec.Matches = append(grpcSpec.Matches, match) } } diff --git a/pkg/gateway/cache/methods.go b/pkg/gateway/cache/methods.go index 2bb57f62c..a57aaf248 100644 --- a/pkg/gateway/cache/methods.go +++ b/pkg/gateway/cache/methods.go @@ -7,6 +7,9 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + gwpav1alpha1 "github.com/flomesh-io/fsm/pkg/apis/policyattachment/v1alpha1" + "github.com/flomesh-io/fsm/pkg/constants" + mcsv1alpha1 "github.com/flomesh-io/fsm/pkg/apis/multicluster/v1alpha1" gwutils "github.com/flomesh-io/fsm/pkg/gateway/utils" "github.com/flomesh-io/fsm/pkg/k8s/informers" @@ -60,6 +63,8 @@ func (c *GatewayCache) getProcessor(obj interface{}) Processor { return c.processors[TCPRoutesProcessorType] case *gwv1alpha2.TLSRoute: return c.processors[TLSRoutesProcessorType] + case *gwpav1alpha1.RateLimitPolicy: + return c.processors[RateLimitPoliciesProcessorType] } return nil @@ -200,6 +205,50 @@ func (c *GatewayCache) isEffectiveRoute(parentRefs []gwv1beta1.ParentReference) return false } +func (c *GatewayCache) isEffectiveRateLimitPolicy(targetRef gwv1alpha2.PolicyTargetReference) bool { + if targetRef.Group != constants.GatewayAPIGroup { + return false + } + + if targetRef.Kind == constants.GatewayKind { + if len(c.gateways) == 0 { + return false + } + + for _, gw := range c.gateways { + if gwutils.IsRefToTarget(targetRef, gw) { + return true + } + } + } + + if targetRef.Kind == constants.HTTPRouteKind { + if len(c.httproutes) == 0 { + return false + } + + for route := range c.httproutes { + if gwutils.IsRefToTarget(targetRef, route) { + return true + } + } + } + + if targetRef.Kind == constants.GRPCRouteKind { + if len(c.grpcroutes) == 0 { + return false + } + + for route := range c.grpcroutes { + if gwutils.IsRefToTarget(targetRef, route) { + return true + } + } + } + + return false +} + func (c *GatewayCache) isSecretReferredByAnyGateway(secret client.ObjectKey) bool { //ctx := context.TODO() for _, key := range c.gateways { diff --git a/pkg/gateway/cache/ratelimits_processor.go b/pkg/gateway/cache/ratelimits_processor.go new file mode 100644 index 000000000..65b6f410e --- /dev/null +++ b/pkg/gateway/cache/ratelimits_processor.go @@ -0,0 +1,39 @@ +package cache + +import ( + gwpav1alpha1 "github.com/flomesh-io/fsm/pkg/apis/policyattachment/v1alpha1" + + "github.com/flomesh-io/fsm/pkg/gateway/utils" +) + +// RateLimitPoliciesProcessor is responsible for processing TLSRoute objects +type RateLimitPoliciesProcessor struct { +} + +// Insert adds a TLSRoute to the cache and returns true if the route is effective +func (p *RateLimitPoliciesProcessor) Insert(obj interface{}, cache *GatewayCache) bool { + policy, ok := obj.(*gwpav1alpha1.RateLimitPolicy) + if !ok { + log.Error().Msgf("unexpected object type %T", obj) + return false + } + + cache.ratelimits[utils.ObjectKey(policy)] = struct{}{} + + return cache.isEffectiveRateLimitPolicy(policy.Spec.TargetRef) +} + +// Delete removes a TLSRoute from the cache and returns true if the route was found +func (p *RateLimitPoliciesProcessor) Delete(obj interface{}, cache *GatewayCache) bool { + policy, ok := obj.(*gwpav1alpha1.RateLimitPolicy) + if !ok { + log.Error().Msgf("unexpected object type %T", obj) + return false + } + + key := utils.ObjectKey(policy) + _, found := cache.ratelimits[key] + delete(cache.ratelimits, key) + + return found +} diff --git a/pkg/gateway/cache/types.go b/pkg/gateway/cache/types.go index e97514bdc..8323f6e67 100644 --- a/pkg/gateway/cache/types.go +++ b/pkg/gateway/cache/types.go @@ -66,6 +66,9 @@ const ( // TLSRoutesProcessorType is the type used to represent the TLS routes processor TLSRoutesProcessorType ProcessorType = "tlsroutes" + + // RateLimitPoliciesProcessorType is the type used to represent the rate limit policies processor + RateLimitPoliciesProcessorType ProcessorType = "ratelimits" ) const ( @@ -108,6 +111,24 @@ type endpointInfo struct { port int32 } +// RateLimitPolicyMatchType is the type used to represent the rate limit policy match type +type RateLimitPolicyMatchType string + +const ( + // RateLimitPolicyMatchTypePort is the type used to represent the rate limit policy match type port + RateLimitPolicyMatchTypePort RateLimitPolicyMatchType = "port" + + // RateLimitPolicyMatchTypeHostnames is the type used to represent the rate limit policy match type hostnames + RateLimitPolicyMatchTypeHostnames RateLimitPolicyMatchType = "hostnames" + + // RateLimitPolicyMatchTypeRoute is the type used to represent the rate limit policy match type route + RateLimitPolicyMatchTypeRoute RateLimitPolicyMatchType = "route" +) + +//type policyAttachments struct { +// rateLimits map[RateLimitPolicyMatchType][]gwpav1alpha1.RateLimitPolicy +//} + var ( log = logger.New("fsm-gateway/cache") ) diff --git a/pkg/gateway/cache/utils.go b/pkg/gateway/cache/utils.go index e8be1ce0c..e4fe52346 100644 --- a/pkg/gateway/cache/utils.go +++ b/pkg/gateway/cache/utils.go @@ -3,6 +3,8 @@ package cache import ( "fmt" + gwpav1alpha1 "github.com/flomesh-io/fsm/pkg/apis/policyattachment/v1alpha1" + "golang.org/x/exp/slices" "sigs.k8s.io/controller-runtime/pkg/client" @@ -567,6 +569,18 @@ func toFSMPortNumber(port *gwv1beta1.PortNumber) *int32 { return pointer.Int32(int32(*port)) } +func newRateLimitConfig(rateLimit *gwpav1alpha1.L7RateLimit) *routecfg.RateLimit { + return &routecfg.RateLimit{ + Mode: *rateLimit.Mode, + Backlog: *rateLimit.Backlog, + Requests: rateLimit.Requests, + Burst: *rateLimit.Burst, + StatTimeWindow: rateLimit.StatTimeWindow, + ResponseStatusCode: *rateLimit.ResponseStatusCode, + ResponseHeadersToAdd: rateLimit.ResponseHeadersToAdd, + } +} + func insertAgentServiceScript(chains []string) []string { httpCodecIndex := slices.Index(chains, httpCodecScript) if httpCodecIndex != -1 { diff --git a/pkg/gateway/client.go b/pkg/gateway/client.go index 5c40a70ef..14648cc20 100644 --- a/pkg/gateway/client.go +++ b/pkg/gateway/client.go @@ -3,6 +3,8 @@ package gateway import ( "context" + gwpav1alpha1 "github.com/flomesh-io/fsm/pkg/apis/policyattachment/v1alpha1" + "github.com/google/go-cmp/cmp" "github.com/rs/zerolog" corev1 "k8s.io/api/core/v1" @@ -81,6 +83,7 @@ func newClient(informerCollection *informers.InformerCollection, kubeClient kube fsminformers.InformerKeyGatewayAPIGRPCRoute, fsminformers.InformerKeyGatewayAPITLSRoute, fsminformers.InformerKeyGatewayAPITCPRoute, + fsminformers.InformerKeyRateLimitPolicy, } { if eventTypes := getEventTypesByInformerKey(informerKey); eventTypes != nil { c.informers.AddEventHandler(informerKey, c.getEventHandlerFuncs(eventTypes)) @@ -203,6 +206,8 @@ func getEventTypesByObjectType(obj interface{}) *k8s.EventTypes { return getEventTypesByInformerKey(fsminformers.InformerKeyGatewayAPITLSRoute) case *gwv1alpha2.TCPRoute: return getEventTypesByInformerKey(fsminformers.InformerKeyGatewayAPITCPRoute) + case *gwpav1alpha1.RateLimitPolicy: + return getEventTypesByInformerKey(fsminformers.InformerKeyRateLimitPolicy) } return nil @@ -270,6 +275,12 @@ func getEventTypesByInformerKey(informerKey fsminformers.InformerKey) *k8s.Event Update: announcements.GatewayAPITCPRouteUpdated, Delete: announcements.GatewayAPITCPRouteDeleted, } + case fsminformers.InformerKeyRateLimitPolicy: + return &k8s.EventTypes{ + Add: announcements.RateLimitPolicyAdded, + Update: announcements.RateLimitPolicyUpdated, + Delete: announcements.RateLimitPolicyDeleted, + } } return nil diff --git a/pkg/gateway/routecfg/types.go b/pkg/gateway/routecfg/types.go index a216ff6cd..f650e0fb2 100644 --- a/pkg/gateway/routecfg/types.go +++ b/pkg/gateway/routecfg/types.go @@ -6,6 +6,8 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwpav1alpha1 "github.com/flomesh-io/fsm/pkg/apis/policyattachment/v1alpha1" + "k8s.io/apimachinery/pkg/types" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" @@ -129,6 +131,7 @@ var _ L7RouteRuleSpec = &HTTPRouteRuleSpec{} type GRPCRouteRuleSpec struct { RouteType L7RouteType `json:"RouteType"` Matches []GRPCTrafficMatch `json:"Matches" hash:"set"` + RateLimit *RateLimit `json:"RateLimit,omitempty"` } var _ L7RouteRuleSpec = &GRPCRouteRuleSpec{} @@ -177,6 +180,7 @@ type GRPCTrafficMatch struct { Headers map[MatchType]map[string]string `json:"Headers,omitempty"` Method *GRPCMethod `json:"Method,omitempty"` BackendService map[string]BackendServiceConfig `json:"BackendService"` + RateLimit *RateLimit `json:"RateLimit,omitempty"` Filters []Filter `json:"Filters,omitempty" hash:"set"` } @@ -195,12 +199,13 @@ type GRPCMethod struct { // RateLimit is the rate limit configuration type RateLimit struct { - Backlog int `json:"Backlog"` - Requests int `json:"Requests"` - Burst int `json:"Burst"` - StatTimeWindow int `json:"StatTimeWindow"` - ResponseStatusCode int `json:"ResponseStatusCode"` - ResponseHeadersToAdd map[string]string `json:"ResponseHeadersToAdd,omitempty" hash:"set"` + Mode gwpav1alpha1.RateLimitPolicyMode `json:"Mode"` + Backlog int `json:"Backlog"` + Requests int `json:"Requests"` + Burst int `json:"Burst"` + StatTimeWindow int `json:"StatTimeWindow"` + ResponseStatusCode int `json:"ResponseStatusCode"` + ResponseHeadersToAdd map[string]string `json:"ResponseHeadersToAdd,omitempty" hash:"set"` } // PassthroughRouteMapping is the passthrough route mapping configuration diff --git a/pkg/gateway/status/route.go b/pkg/gateway/status/route.go index 47039a85c..c4e326361 100644 --- a/pkg/gateway/status/route.go +++ b/pkg/gateway/status/route.go @@ -119,7 +119,7 @@ func (p *RouteStatusProcessor) computeRouteParentStatus( Conditions: make([]metav1.Condition, 0), } - allowedListeners := gwutils.GetAllowedListeners(parentRef, params.RouteGvk, params.RouteGeneration, validListeners, routeParentStatus) + allowedListeners := gwutils.GetAllowedListenersAndSetStatus(parentRef, params.RouteGvk, params.RouteGeneration, validListeners, routeParentStatus) //if len(allowedListeners) == 0 { // //} @@ -136,7 +136,7 @@ func (p *RouteStatusProcessor) computeRouteParentStatus( } switch params.RouteGvk.Kind { - case "HTTPRoute", "TLSRoute", "GRPCRoute": + case constants.HTTPRouteKind, constants.TLSRouteKind, constants.GRPCRouteKind: if count == 0 && metautil.FindStatusCondition(routeParentStatus.Conditions, string(gwv1beta1.RouteConditionAccepted)) == nil { metautil.SetStatusCondition(&routeParentStatus.Conditions, metav1.Condition{ Type: string(gwv1beta1.RouteConditionAccepted), diff --git a/pkg/gateway/utils/utils.go b/pkg/gateway/utils/utils.go index 91cdbcb99..aca80ac9f 100644 --- a/pkg/gateway/utils/utils.go +++ b/pkg/gateway/utils/utils.go @@ -27,9 +27,14 @@ package utils import ( "fmt" + "reflect" "strings" "time" + gwpav1alpha1 "github.com/flomesh-io/fsm/pkg/apis/policyattachment/v1alpha1" + + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + "github.com/gobwas/glob" metautil "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -39,6 +44,7 @@ import ( gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" "github.com/flomesh-io/fsm/pkg/apis/gateway" + "github.com/flomesh-io/fsm/pkg/constants" gwtypes "github.com/flomesh-io/fsm/pkg/gateway/types" ) @@ -86,13 +92,17 @@ func IsListenerAccepted(listenerStatus gwv1beta1.ListenerStatus) bool { return metautil.IsStatusConditionTrue(listenerStatus.Conditions, string(gwv1beta1.ListenerConditionAccepted)) } +func IsAcceptedRateLimitPolicy(policy *gwpav1alpha1.RateLimitPolicy) bool { + return metautil.IsStatusConditionTrue(policy.Status.Conditions, string(gwv1alpha2.PolicyConditionAccepted)) +} + // IsRefToGateway returns true if the parent reference is to the gateway func IsRefToGateway(parentRef gwv1beta1.ParentReference, gateway client.ObjectKey) bool { if parentRef.Group != nil && string(*parentRef.Group) != gwv1beta1.GroupName { return false } - if parentRef.Kind != nil && string(*parentRef.Kind) != "Gateway" { + if parentRef.Kind != nil && string(*parentRef.Kind) != constants.GatewayKind { return false } @@ -103,6 +113,15 @@ func IsRefToGateway(parentRef gwv1beta1.ParentReference, gateway client.ObjectKe return string(parentRef.Name) == gateway.Name } +// IsRefToTarget returns true if the target reference is to the target object +func IsRefToTarget(targetRef gwv1alpha2.PolicyTargetReference, object client.ObjectKey) bool { + if targetRef.Namespace != nil && string(*targetRef.Namespace) != object.Namespace { + return false + } + + return string(targetRef.Name) == object.Name +} + // ObjectKey returns the object key for the given object func ObjectKey(obj client.Object) client.ObjectKey { ns := obj.GetNamespace() @@ -144,8 +163,8 @@ func GetValidListenersFromGateway(gw *gwv1beta1.Gateway) []gwtypes.Listener { return validListeners } -// GetAllowedListeners returns the allowed listeners -func GetAllowedListeners( +// GetAllowedListenersAndSetStatus returns the allowed listeners and set status +func GetAllowedListenersAndSetStatus( parentRef gwv1beta1.ParentReference, routeGvk schema.GroupVersionKind, routeGeneration int64, @@ -198,6 +217,41 @@ func GetAllowedListeners( return allowedListeners } +// GetAllowedListeners returns the allowed listeners +func GetAllowedListeners( + parentRef gwv1beta1.ParentReference, + routeGvk schema.GroupVersionKind, + routeGeneration int64, + validListeners []gwtypes.Listener, +) []gwtypes.Listener { + var selectedListeners []gwtypes.Listener + for _, validListener := range validListeners { + if (parentRef.SectionName == nil || *parentRef.SectionName == validListener.Name) && + (parentRef.Port == nil || *parentRef.Port == validListener.Port) { + selectedListeners = append(selectedListeners, validListener) + } + } + + if len(selectedListeners) == 0 { + return nil + } + + var allowedListeners []gwtypes.Listener + for _, selectedListener := range selectedListeners { + if !selectedListener.AllowsKind(routeGvk) { + continue + } + + allowedListeners = append(allowedListeners, selectedListener) + } + + if len(allowedListeners) == 0 { + return nil + } + + return allowedListeners +} + // GetValidHostnames returns the valid hostnames func GetValidHostnames(listenerHostname *gwv1beta1.Hostname, routeHostnames []gwv1beta1.Hostname) []string { if len(routeHostnames) == 0 { @@ -243,3 +297,92 @@ func HostnameMatchesWildcardHostname(hostname, wildcardHostname string) bool { g := glob.MustCompile(wildcardHostname, '.') return g.Match(hostname) } + +// GetRateLimitIfRouteHostnameMatchesPolicy returns the rate limit config if the route hostname matches the policy +func GetRateLimitIfRouteHostnameMatchesPolicy(routeHostname string, rateLimitPolicy gwpav1alpha1.RateLimitPolicy) *gwpav1alpha1.L7RateLimit { + if len(rateLimitPolicy.Spec.Hostnames) == 0 { + return nil + } + + for i := range rateLimitPolicy.Spec.Hostnames { + hostname := string(rateLimitPolicy.Spec.Hostnames[i].Hostname) + rateLimit := rateLimitPolicy.Spec.Hostnames[i].RateLimit + if rateLimit == nil { + rateLimit = rateLimitPolicy.Spec.DefaultL7RateLimit + } + + switch { + case routeHostname == hostname: + return rateLimit + + case strings.HasPrefix(routeHostname, "*"): + if HostnameMatchesWildcardHostname(hostname, routeHostname) { + return rateLimit + } + + case strings.HasPrefix(hostname, "*"): + if HostnameMatchesWildcardHostname(routeHostname, hostname) { + return rateLimit + } + } + } + + return nil +} + +// GetRateLimitIfHTTPRouteMatchesPolicy returns the rate limit config if the HTTP route matches the policy +func GetRateLimitIfHTTPRouteMatchesPolicy(routeMatch gwv1beta1.HTTPRouteMatch, rateLimitPolicy gwpav1alpha1.RateLimitPolicy) *gwpav1alpha1.L7RateLimit { + if len(rateLimitPolicy.Spec.HTTPRateLimits) == 0 { + return nil + } + + for _, hr := range rateLimitPolicy.Spec.HTTPRateLimits { + if reflect.DeepEqual(routeMatch, hr.Match) { + if hr.RateLimit != nil { + return hr.RateLimit + } + + return rateLimitPolicy.Spec.DefaultL7RateLimit + } + } + + return nil +} + +// GetRateLimitIfGRPCRouteMatchesPolicy returns the rate limit config if the GRPC route matches the policy +func GetRateLimitIfGRPCRouteMatchesPolicy(routeMatch gwv1alpha2.GRPCRouteMatch, rateLimitPolicy gwpav1alpha1.RateLimitPolicy) *gwpav1alpha1.L7RateLimit { + if len(rateLimitPolicy.Spec.GRPCRateLimits) == 0 { + return nil + } + + for _, gr := range rateLimitPolicy.Spec.GRPCRateLimits { + if reflect.DeepEqual(routeMatch, gr.Match) { + if gr.RateLimit != nil { + return gr.RateLimit + } + + return rateLimitPolicy.Spec.DefaultL7RateLimit + } + } + + return nil +} + +// GetRateLimitIfPortMatchesPolicy returns true if the port matches the rate limit policy +func GetRateLimitIfPortMatchesPolicy(port gwv1beta1.PortNumber, rateLimitPolicy gwpav1alpha1.RateLimitPolicy) *int64 { + if len(rateLimitPolicy.Spec.Ports) == 0 { + return nil + } + + for _, policyPort := range rateLimitPolicy.Spec.Ports { + if port == policyPort.Port { + if policyPort.BPS != nil { + return policyPort.BPS + } + + return rateLimitPolicy.Spec.DefaultBPS + } + } + + return nil +} diff --git a/pkg/gen/client/policyattachment/clientset/versioned/clientset.go b/pkg/gen/client/policyattachment/clientset/versioned/clientset.go new file mode 100644 index 000000000..e185585b7 --- /dev/null +++ b/pkg/gen/client/policyattachment/clientset/versioned/clientset.go @@ -0,0 +1,117 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +package versioned + +import ( + "fmt" + "net/http" + + gatewayv1alpha1 "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1" + discovery "k8s.io/client-go/discovery" + rest "k8s.io/client-go/rest" + flowcontrol "k8s.io/client-go/util/flowcontrol" +) + +type Interface interface { + Discovery() discovery.DiscoveryInterface + GatewayV1alpha1() gatewayv1alpha1.GatewayV1alpha1Interface +} + +// Clientset contains the clients for groups. +type Clientset struct { + *discovery.DiscoveryClient + gatewayV1alpha1 *gatewayv1alpha1.GatewayV1alpha1Client +} + +// GatewayV1alpha1 retrieves the GatewayV1alpha1Client +func (c *Clientset) GatewayV1alpha1() gatewayv1alpha1.GatewayV1alpha1Interface { + return c.gatewayV1alpha1 +} + +// Discovery retrieves the DiscoveryClient +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + if c == nil { + return nil + } + return c.DiscoveryClient +} + +// NewForConfig creates a new Clientset for the given config. +// If config's RateLimiter is not set and QPS and Burst are acceptable, +// NewForConfig will generate a rate-limiter in configShallowCopy. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*Clientset, error) { + configShallowCopy := *c + + if configShallowCopy.UserAgent == "" { + configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent() + } + + // share the transport between all clients + httpClient, err := rest.HTTPClientFor(&configShallowCopy) + if err != nil { + return nil, err + } + + return NewForConfigAndClient(&configShallowCopy, httpClient) +} + +// NewForConfigAndClient creates a new Clientset for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +// If config's RateLimiter is not set and QPS and Burst are acceptable, +// NewForConfigAndClient will generate a rate-limiter in configShallowCopy. +func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) { + configShallowCopy := *c + if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { + if configShallowCopy.Burst <= 0 { + return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") + } + configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) + } + + var cs Clientset + var err error + cs.gatewayV1alpha1, err = gatewayv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } + + cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } + return &cs, nil +} + +// NewForConfigOrDie creates a new Clientset for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *Clientset { + cs, err := NewForConfig(c) + if err != nil { + panic(err) + } + return cs +} + +// New creates a new Clientset for the given RESTClient. +func New(c rest.Interface) *Clientset { + var cs Clientset + cs.gatewayV1alpha1 = gatewayv1alpha1.New(c) + + cs.DiscoveryClient = discovery.NewDiscoveryClient(c) + return &cs +} diff --git a/pkg/gen/client/policyattachment/clientset/versioned/doc.go b/pkg/gen/client/policyattachment/clientset/versioned/doc.go new file mode 100644 index 000000000..0a3c04391 --- /dev/null +++ b/pkg/gen/client/policyattachment/clientset/versioned/doc.go @@ -0,0 +1,17 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated clientset. +package versioned diff --git a/pkg/gen/client/policyattachment/clientset/versioned/fake/clientset_generated.go b/pkg/gen/client/policyattachment/clientset/versioned/fake/clientset_generated.go new file mode 100644 index 000000000..101a7bf87 --- /dev/null +++ b/pkg/gen/client/policyattachment/clientset/versioned/fake/clientset_generated.go @@ -0,0 +1,82 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + clientset "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/clientset/versioned" + gatewayv1alpha1 "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1" + fakegatewayv1alpha1 "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/fake" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/discovery" + fakediscovery "k8s.io/client-go/discovery/fake" + "k8s.io/client-go/testing" +) + +// NewSimpleClientset returns a clientset that will respond with the provided objects. +// It's backed by a very simple object tracker that processes creates, updates and deletions as-is, +// without applying any validations and/or defaults. It shouldn't be considered a replacement +// for a real clientset and is mostly useful in simple unit tests. +func NewSimpleClientset(objects ...runtime.Object) *Clientset { + o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + cs := &Clientset{tracker: o} + cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} + cs.AddReactor("*", "*", testing.ObjectReaction(o)) + cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + gvr := action.GetResource() + ns := action.GetNamespace() + watch, err := o.Watch(gvr, ns) + if err != nil { + return false, nil, err + } + return true, watch, nil + }) + + return cs +} + +// Clientset implements clientset.Interface. Meant to be embedded into a +// struct to get a default implementation. This makes faking out just the method +// you want to test easier. +type Clientset struct { + testing.Fake + discovery *fakediscovery.FakeDiscovery + tracker testing.ObjectTracker +} + +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + return c.discovery +} + +func (c *Clientset) Tracker() testing.ObjectTracker { + return c.tracker +} + +var ( + _ clientset.Interface = &Clientset{} + _ testing.FakeClient = &Clientset{} +) + +// GatewayV1alpha1 retrieves the GatewayV1alpha1Client +func (c *Clientset) GatewayV1alpha1() gatewayv1alpha1.GatewayV1alpha1Interface { + return &fakegatewayv1alpha1.FakeGatewayV1alpha1{Fake: &c.Fake} +} diff --git a/pkg/gen/client/policyattachment/clientset/versioned/fake/doc.go b/pkg/gen/client/policyattachment/clientset/versioned/fake/doc.go new file mode 100644 index 000000000..20f84e8d0 --- /dev/null +++ b/pkg/gen/client/policyattachment/clientset/versioned/fake/doc.go @@ -0,0 +1,17 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated fake clientset. +package fake diff --git a/pkg/gen/client/policyattachment/clientset/versioned/fake/register.go b/pkg/gen/client/policyattachment/clientset/versioned/fake/register.go new file mode 100644 index 000000000..5020457e6 --- /dev/null +++ b/pkg/gen/client/policyattachment/clientset/versioned/fake/register.go @@ -0,0 +1,53 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + gatewayv1alpha1 "github.com/flomesh-io/fsm/pkg/apis/policyattachment/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +var scheme = runtime.NewScheme() +var codecs = serializer.NewCodecFactory(scheme) + +var localSchemeBuilder = runtime.SchemeBuilder{ + gatewayv1alpha1.AddToScheme, +} + +// AddToScheme adds all types of this clientset into the given scheme. This allows composition +// of clientsets, like in: +// +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kubernetes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) +// +// kclientset, _ := kubernetes.NewForConfig(c) +// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// +// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types +// correctly. +var AddToScheme = localSchemeBuilder.AddToScheme + +func init() { + v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) + utilruntime.Must(AddToScheme(scheme)) +} diff --git a/pkg/gen/client/policyattachment/clientset/versioned/scheme/doc.go b/pkg/gen/client/policyattachment/clientset/versioned/scheme/doc.go new file mode 100644 index 000000000..08657961b --- /dev/null +++ b/pkg/gen/client/policyattachment/clientset/versioned/scheme/doc.go @@ -0,0 +1,17 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +// This package contains the scheme of the automatically generated clientset. +package scheme diff --git a/pkg/gen/client/policyattachment/clientset/versioned/scheme/register.go b/pkg/gen/client/policyattachment/clientset/versioned/scheme/register.go new file mode 100644 index 000000000..1f3b2f7a2 --- /dev/null +++ b/pkg/gen/client/policyattachment/clientset/versioned/scheme/register.go @@ -0,0 +1,53 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +package scheme + +import ( + gatewayv1alpha1 "github.com/flomesh-io/fsm/pkg/apis/policyattachment/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +var Scheme = runtime.NewScheme() +var Codecs = serializer.NewCodecFactory(Scheme) +var ParameterCodec = runtime.NewParameterCodec(Scheme) +var localSchemeBuilder = runtime.SchemeBuilder{ + gatewayv1alpha1.AddToScheme, +} + +// AddToScheme adds all types of this clientset into the given scheme. This allows composition +// of clientsets, like in: +// +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kubernetes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) +// +// kclientset, _ := kubernetes.NewForConfig(c) +// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// +// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types +// correctly. +var AddToScheme = localSchemeBuilder.AddToScheme + +func init() { + v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) + utilruntime.Must(AddToScheme(Scheme)) +} diff --git a/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/doc.go b/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/doc.go new file mode 100644 index 000000000..caa8499d2 --- /dev/null +++ b/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/doc.go @@ -0,0 +1,17 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1alpha1 diff --git a/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/fake/doc.go b/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/fake/doc.go new file mode 100644 index 000000000..b8b5e2568 --- /dev/null +++ b/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/fake/doc.go @@ -0,0 +1,17 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/fake/fake_policyattachment_client.go b/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/fake/fake_policyattachment_client.go new file mode 100644 index 000000000..2c63bfb13 --- /dev/null +++ b/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/fake/fake_policyattachment_client.go @@ -0,0 +1,37 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1" + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" +) + +type FakeGatewayV1alpha1 struct { + *testing.Fake +} + +func (c *FakeGatewayV1alpha1) RateLimitPolicies(namespace string) v1alpha1.RateLimitPolicyInterface { + return &FakeRateLimitPolicies{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeGatewayV1alpha1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/fake/fake_ratelimitpolicy.go b/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/fake/fake_ratelimitpolicy.go new file mode 100644 index 000000000..5f797ed07 --- /dev/null +++ b/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/fake/fake_ratelimitpolicy.go @@ -0,0 +1,139 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/flomesh-io/fsm/pkg/apis/policyattachment/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeRateLimitPolicies implements RateLimitPolicyInterface +type FakeRateLimitPolicies struct { + Fake *FakeGatewayV1alpha1 + ns string +} + +var ratelimitpoliciesResource = schema.GroupVersionResource{Group: "gateway.flomesh.io", Version: "v1alpha1", Resource: "ratelimitpolicies"} + +var ratelimitpoliciesKind = schema.GroupVersionKind{Group: "gateway.flomesh.io", Version: "v1alpha1", Kind: "RateLimitPolicy"} + +// Get takes name of the rateLimitPolicy, and returns the corresponding rateLimitPolicy object, and an error if there is any. +func (c *FakeRateLimitPolicies) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.RateLimitPolicy, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(ratelimitpoliciesResource, c.ns, name), &v1alpha1.RateLimitPolicy{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.RateLimitPolicy), err +} + +// List takes label and field selectors, and returns the list of RateLimitPolicies that match those selectors. +func (c *FakeRateLimitPolicies) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.RateLimitPolicyList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(ratelimitpoliciesResource, ratelimitpoliciesKind, c.ns, opts), &v1alpha1.RateLimitPolicyList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.RateLimitPolicyList{ListMeta: obj.(*v1alpha1.RateLimitPolicyList).ListMeta} + for _, item := range obj.(*v1alpha1.RateLimitPolicyList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested rateLimitPolicies. +func (c *FakeRateLimitPolicies) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(ratelimitpoliciesResource, c.ns, opts)) + +} + +// Create takes the representation of a rateLimitPolicy and creates it. Returns the server's representation of the rateLimitPolicy, and an error, if there is any. +func (c *FakeRateLimitPolicies) Create(ctx context.Context, rateLimitPolicy *v1alpha1.RateLimitPolicy, opts v1.CreateOptions) (result *v1alpha1.RateLimitPolicy, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(ratelimitpoliciesResource, c.ns, rateLimitPolicy), &v1alpha1.RateLimitPolicy{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.RateLimitPolicy), err +} + +// Update takes the representation of a rateLimitPolicy and updates it. Returns the server's representation of the rateLimitPolicy, and an error, if there is any. +func (c *FakeRateLimitPolicies) Update(ctx context.Context, rateLimitPolicy *v1alpha1.RateLimitPolicy, opts v1.UpdateOptions) (result *v1alpha1.RateLimitPolicy, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(ratelimitpoliciesResource, c.ns, rateLimitPolicy), &v1alpha1.RateLimitPolicy{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.RateLimitPolicy), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeRateLimitPolicies) UpdateStatus(ctx context.Context, rateLimitPolicy *v1alpha1.RateLimitPolicy, opts v1.UpdateOptions) (*v1alpha1.RateLimitPolicy, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(ratelimitpoliciesResource, "status", c.ns, rateLimitPolicy), &v1alpha1.RateLimitPolicy{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.RateLimitPolicy), err +} + +// Delete takes name of the rateLimitPolicy and deletes it. Returns an error if one occurs. +func (c *FakeRateLimitPolicies) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(ratelimitpoliciesResource, c.ns, name, opts), &v1alpha1.RateLimitPolicy{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeRateLimitPolicies) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(ratelimitpoliciesResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.RateLimitPolicyList{}) + return err +} + +// Patch applies the patch and returns the patched rateLimitPolicy. +func (c *FakeRateLimitPolicies) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.RateLimitPolicy, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(ratelimitpoliciesResource, c.ns, name, pt, data, subresources...), &v1alpha1.RateLimitPolicy{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.RateLimitPolicy), err +} diff --git a/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/generated_expansion.go b/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/generated_expansion.go new file mode 100644 index 000000000..ba542d617 --- /dev/null +++ b/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/generated_expansion.go @@ -0,0 +1,18 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +type RateLimitPolicyExpansion interface{} diff --git a/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/policyattachment_client.go b/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/policyattachment_client.go new file mode 100644 index 000000000..a9d90f626 --- /dev/null +++ b/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/policyattachment_client.go @@ -0,0 +1,104 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "net/http" + + v1alpha1 "github.com/flomesh-io/fsm/pkg/apis/policyattachment/v1alpha1" + "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/clientset/versioned/scheme" + rest "k8s.io/client-go/rest" +) + +type GatewayV1alpha1Interface interface { + RESTClient() rest.Interface + RateLimitPoliciesGetter +} + +// GatewayV1alpha1Client is used to interact with features provided by the gateway.flomesh.io group. +type GatewayV1alpha1Client struct { + restClient rest.Interface +} + +func (c *GatewayV1alpha1Client) RateLimitPolicies(namespace string) RateLimitPolicyInterface { + return newRateLimitPolicies(c, namespace) +} + +// NewForConfig creates a new GatewayV1alpha1Client for the given config. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*GatewayV1alpha1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + httpClient, err := rest.HTTPClientFor(&config) + if err != nil { + return nil, err + } + return NewForConfigAndClient(&config, httpClient) +} + +// NewForConfigAndClient creates a new GatewayV1alpha1Client for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +func NewForConfigAndClient(c *rest.Config, h *http.Client) (*GatewayV1alpha1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientForConfigAndClient(&config, h) + if err != nil { + return nil, err + } + return &GatewayV1alpha1Client{client}, nil +} + +// NewForConfigOrDie creates a new GatewayV1alpha1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *GatewayV1alpha1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new GatewayV1alpha1Client for the given RESTClient. +func New(c rest.Interface) *GatewayV1alpha1Client { + return &GatewayV1alpha1Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1alpha1.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + return nil +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *GatewayV1alpha1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/ratelimitpolicy.go b/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/ratelimitpolicy.go new file mode 100644 index 000000000..d74e9837a --- /dev/null +++ b/pkg/gen/client/policyattachment/clientset/versioned/typed/policyattachment/v1alpha1/ratelimitpolicy.go @@ -0,0 +1,192 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/flomesh-io/fsm/pkg/apis/policyattachment/v1alpha1" + scheme "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// RateLimitPoliciesGetter has a method to return a RateLimitPolicyInterface. +// A group's client should implement this interface. +type RateLimitPoliciesGetter interface { + RateLimitPolicies(namespace string) RateLimitPolicyInterface +} + +// RateLimitPolicyInterface has methods to work with RateLimitPolicy resources. +type RateLimitPolicyInterface interface { + Create(ctx context.Context, rateLimitPolicy *v1alpha1.RateLimitPolicy, opts v1.CreateOptions) (*v1alpha1.RateLimitPolicy, error) + Update(ctx context.Context, rateLimitPolicy *v1alpha1.RateLimitPolicy, opts v1.UpdateOptions) (*v1alpha1.RateLimitPolicy, error) + UpdateStatus(ctx context.Context, rateLimitPolicy *v1alpha1.RateLimitPolicy, opts v1.UpdateOptions) (*v1alpha1.RateLimitPolicy, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.RateLimitPolicy, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.RateLimitPolicyList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.RateLimitPolicy, err error) + RateLimitPolicyExpansion +} + +// rateLimitPolicies implements RateLimitPolicyInterface +type rateLimitPolicies struct { + client rest.Interface + ns string +} + +// newRateLimitPolicies returns a RateLimitPolicies +func newRateLimitPolicies(c *GatewayV1alpha1Client, namespace string) *rateLimitPolicies { + return &rateLimitPolicies{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the rateLimitPolicy, and returns the corresponding rateLimitPolicy object, and an error if there is any. +func (c *rateLimitPolicies) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.RateLimitPolicy, err error) { + result = &v1alpha1.RateLimitPolicy{} + err = c.client.Get(). + Namespace(c.ns). + Resource("ratelimitpolicies"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of RateLimitPolicies that match those selectors. +func (c *rateLimitPolicies) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.RateLimitPolicyList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.RateLimitPolicyList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("ratelimitpolicies"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested rateLimitPolicies. +func (c *rateLimitPolicies) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("ratelimitpolicies"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a rateLimitPolicy and creates it. Returns the server's representation of the rateLimitPolicy, and an error, if there is any. +func (c *rateLimitPolicies) Create(ctx context.Context, rateLimitPolicy *v1alpha1.RateLimitPolicy, opts v1.CreateOptions) (result *v1alpha1.RateLimitPolicy, err error) { + result = &v1alpha1.RateLimitPolicy{} + err = c.client.Post(). + Namespace(c.ns). + Resource("ratelimitpolicies"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(rateLimitPolicy). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a rateLimitPolicy and updates it. Returns the server's representation of the rateLimitPolicy, and an error, if there is any. +func (c *rateLimitPolicies) Update(ctx context.Context, rateLimitPolicy *v1alpha1.RateLimitPolicy, opts v1.UpdateOptions) (result *v1alpha1.RateLimitPolicy, err error) { + result = &v1alpha1.RateLimitPolicy{} + err = c.client.Put(). + Namespace(c.ns). + Resource("ratelimitpolicies"). + Name(rateLimitPolicy.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(rateLimitPolicy). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *rateLimitPolicies) UpdateStatus(ctx context.Context, rateLimitPolicy *v1alpha1.RateLimitPolicy, opts v1.UpdateOptions) (result *v1alpha1.RateLimitPolicy, err error) { + result = &v1alpha1.RateLimitPolicy{} + err = c.client.Put(). + Namespace(c.ns). + Resource("ratelimitpolicies"). + Name(rateLimitPolicy.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(rateLimitPolicy). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the rateLimitPolicy and deletes it. Returns an error if one occurs. +func (c *rateLimitPolicies) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("ratelimitpolicies"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *rateLimitPolicies) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("ratelimitpolicies"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched rateLimitPolicy. +func (c *rateLimitPolicies) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.RateLimitPolicy, err error) { + result = &v1alpha1.RateLimitPolicy{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("ratelimitpolicies"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/gen/client/policyattachment/informers/externalversions/factory.go b/pkg/gen/client/policyattachment/informers/externalversions/factory.go new file mode 100644 index 000000000..f16ab91cf --- /dev/null +++ b/pkg/gen/client/policyattachment/informers/externalversions/factory.go @@ -0,0 +1,248 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by informer-gen. DO NOT EDIT. + +package externalversions + +import ( + reflect "reflect" + sync "sync" + time "time" + + versioned "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/clientset/versioned" + internalinterfaces "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/informers/externalversions/internalinterfaces" + policyattachment "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/informers/externalversions/policyattachment" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + cache "k8s.io/client-go/tools/cache" +) + +// SharedInformerOption defines the functional option type for SharedInformerFactory. +type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory + +type sharedInformerFactory struct { + client versioned.Interface + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc + lock sync.Mutex + defaultResync time.Duration + customResync map[reflect.Type]time.Duration + + informers map[reflect.Type]cache.SharedIndexInformer + // startedInformers is used for tracking which informers have been started. + // This allows Start() to be called multiple times safely. + startedInformers map[reflect.Type]bool + // wg tracks how many goroutines were started. + wg sync.WaitGroup + // shuttingDown is true when Shutdown has been called. It may still be running + // because it needs to wait for goroutines. + shuttingDown bool +} + +// WithCustomResyncConfig sets a custom resync period for the specified informer types. +func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + for k, v := range resyncConfig { + factory.customResync[reflect.TypeOf(k)] = v + } + return factory + } +} + +// WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory. +func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.tweakListOptions = tweakListOptions + return factory + } +} + +// WithNamespace limits the SharedInformerFactory to the specified namespace. +func WithNamespace(namespace string) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.namespace = namespace + return factory + } +} + +// NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. +func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory { + return NewSharedInformerFactoryWithOptions(client, defaultResync) +} + +// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory. +// Listers obtained via this SharedInformerFactory will be subject to the same filters +// as specified here. +// Deprecated: Please use NewSharedInformerFactoryWithOptions instead +func NewFilteredSharedInformerFactory(client versioned.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory { + return NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions)) +} + +// NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options. +func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory { + factory := &sharedInformerFactory{ + client: client, + namespace: v1.NamespaceAll, + defaultResync: defaultResync, + informers: make(map[reflect.Type]cache.SharedIndexInformer), + startedInformers: make(map[reflect.Type]bool), + customResync: make(map[reflect.Type]time.Duration), + } + + // Apply all options + for _, opt := range options { + factory = opt(factory) + } + + return factory +} + +func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { + f.lock.Lock() + defer f.lock.Unlock() + + if f.shuttingDown { + return + } + + for informerType, informer := range f.informers { + if !f.startedInformers[informerType] { + f.wg.Add(1) + // We need a new variable in each loop iteration, + // otherwise the goroutine would use the loop variable + // and that keeps changing. + informer := informer + go func() { + defer f.wg.Done() + informer.Run(stopCh) + }() + f.startedInformers[informerType] = true + } + } +} + +func (f *sharedInformerFactory) Shutdown() { + f.lock.Lock() + f.shuttingDown = true + f.lock.Unlock() + + // Will return immediately if there is nothing to wait for. + f.wg.Wait() +} + +func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { + informers := func() map[reflect.Type]cache.SharedIndexInformer { + f.lock.Lock() + defer f.lock.Unlock() + + informers := map[reflect.Type]cache.SharedIndexInformer{} + for informerType, informer := range f.informers { + if f.startedInformers[informerType] { + informers[informerType] = informer + } + } + return informers + }() + + res := map[reflect.Type]bool{} + for informType, informer := range informers { + res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced) + } + return res +} + +// InternalInformerFor returns the SharedIndexInformer for obj using an internal +// client. +func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer { + f.lock.Lock() + defer f.lock.Unlock() + + informerType := reflect.TypeOf(obj) + informer, exists := f.informers[informerType] + if exists { + return informer + } + + resyncPeriod, exists := f.customResync[informerType] + if !exists { + resyncPeriod = f.defaultResync + } + + informer = newFunc(f.client, resyncPeriod) + f.informers[informerType] = informer + + return informer +} + +// SharedInformerFactory provides shared informers for resources in all known +// API group versions. +// +// It is typically used like this: +// +// ctx, cancel := context.Background() +// defer cancel() +// factory := NewSharedInformerFactory(client, resyncPeriod) +// defer factory.WaitForStop() // Returns immediately if nothing was started. +// genericInformer := factory.ForResource(resource) +// typedInformer := factory.SomeAPIGroup().V1().SomeType() +// factory.Start(ctx.Done()) // Start processing these informers. +// synced := factory.WaitForCacheSync(ctx.Done()) +// for v, ok := range synced { +// if !ok { +// fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v) +// return +// } +// } +// +// // Creating informers can also be created after Start, but then +// // Start must be called again: +// anotherGenericInformer := factory.ForResource(resource) +// factory.Start(ctx.Done()) +type SharedInformerFactory interface { + internalinterfaces.SharedInformerFactory + + // Start initializes all requested informers. They are handled in goroutines + // which run until the stop channel gets closed. + Start(stopCh <-chan struct{}) + + // Shutdown marks a factory as shutting down. At that point no new + // informers can be started anymore and Start will return without + // doing anything. + // + // In addition, Shutdown blocks until all goroutines have terminated. For that + // to happen, the close channel(s) that they were started with must be closed, + // either before Shutdown gets called or while it is waiting. + // + // Shutdown may be called multiple times, even concurrently. All such calls will + // block until all goroutines have terminated. + Shutdown() + + // WaitForCacheSync blocks until all started informers' caches were synced + // or the stop channel gets closed. + WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool + + // ForResource gives generic access to a shared informer of the matching type. + ForResource(resource schema.GroupVersionResource) (GenericInformer, error) + + // InternalInformerFor returns the SharedIndexInformer for obj using an internal + // client. + InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer + + Gateway() policyattachment.Interface +} + +func (f *sharedInformerFactory) Gateway() policyattachment.Interface { + return policyattachment.New(f, f.namespace, f.tweakListOptions) +} diff --git a/pkg/gen/client/policyattachment/informers/externalversions/generic.go b/pkg/gen/client/policyattachment/informers/externalversions/generic.go new file mode 100644 index 000000000..3a734aa76 --- /dev/null +++ b/pkg/gen/client/policyattachment/informers/externalversions/generic.go @@ -0,0 +1,59 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by informer-gen. DO NOT EDIT. + +package externalversions + +import ( + "fmt" + + v1alpha1 "github.com/flomesh-io/fsm/pkg/apis/policyattachment/v1alpha1" + schema "k8s.io/apimachinery/pkg/runtime/schema" + cache "k8s.io/client-go/tools/cache" +) + +// GenericInformer is type of SharedIndexInformer which will locate and delegate to other +// sharedInformers based on type +type GenericInformer interface { + Informer() cache.SharedIndexInformer + Lister() cache.GenericLister +} + +type genericInformer struct { + informer cache.SharedIndexInformer + resource schema.GroupResource +} + +// Informer returns the SharedIndexInformer. +func (f *genericInformer) Informer() cache.SharedIndexInformer { + return f.informer +} + +// Lister returns the GenericLister. +func (f *genericInformer) Lister() cache.GenericLister { + return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource) +} + +// ForResource gives generic access to a shared informer of the matching type +// TODO extend this to unknown resources with a client pool +func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { + switch resource { + // Group=gateway.flomesh.io, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithResource("ratelimitpolicies"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Gateway().V1alpha1().RateLimitPolicies().Informer()}, nil + + } + + return nil, fmt.Errorf("no informer found for %v", resource) +} diff --git a/pkg/gen/client/policyattachment/informers/externalversions/internalinterfaces/factory_interfaces.go b/pkg/gen/client/policyattachment/informers/externalversions/internalinterfaces/factory_interfaces.go new file mode 100644 index 000000000..c4d8e0913 --- /dev/null +++ b/pkg/gen/client/policyattachment/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -0,0 +1,37 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by informer-gen. DO NOT EDIT. + +package internalinterfaces + +import ( + time "time" + + versioned "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/clientset/versioned" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + cache "k8s.io/client-go/tools/cache" +) + +// NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. +type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer + +// SharedInformerFactory a small interface to allow for adding an informer without an import cycle +type SharedInformerFactory interface { + Start(stopCh <-chan struct{}) + InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer +} + +// TweakListOptionsFunc is a function that transforms a v1.ListOptions. +type TweakListOptionsFunc func(*v1.ListOptions) diff --git a/pkg/gen/client/policyattachment/informers/externalversions/policyattachment/interface.go b/pkg/gen/client/policyattachment/informers/externalversions/policyattachment/interface.go new file mode 100644 index 000000000..9474dde09 --- /dev/null +++ b/pkg/gen/client/policyattachment/informers/externalversions/policyattachment/interface.go @@ -0,0 +1,43 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by informer-gen. DO NOT EDIT. + +package policyattachment + +import ( + internalinterfaces "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/informers/externalversions/policyattachment/v1alpha1" +) + +// Interface provides access to each of this group's versions. +type Interface interface { + // V1alpha1 provides access to shared informers for resources in V1alpha1. + V1alpha1() v1alpha1.Interface +} + +type group struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// V1alpha1 returns a new v1alpha1.Interface. +func (g *group) V1alpha1() v1alpha1.Interface { + return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) +} diff --git a/pkg/gen/client/policyattachment/informers/externalversions/policyattachment/v1alpha1/interface.go b/pkg/gen/client/policyattachment/informers/externalversions/policyattachment/v1alpha1/interface.go new file mode 100644 index 000000000..23de5f5de --- /dev/null +++ b/pkg/gen/client/policyattachment/informers/externalversions/policyattachment/v1alpha1/interface.go @@ -0,0 +1,42 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + internalinterfaces "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // RateLimitPolicies returns a RateLimitPolicyInformer. + RateLimitPolicies() RateLimitPolicyInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// RateLimitPolicies returns a RateLimitPolicyInformer. +func (v *version) RateLimitPolicies() RateLimitPolicyInformer { + return &rateLimitPolicyInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/gen/client/policyattachment/informers/externalversions/policyattachment/v1alpha1/ratelimitpolicy.go b/pkg/gen/client/policyattachment/informers/externalversions/policyattachment/v1alpha1/ratelimitpolicy.go new file mode 100644 index 000000000..50b24d9c9 --- /dev/null +++ b/pkg/gen/client/policyattachment/informers/externalversions/policyattachment/v1alpha1/ratelimitpolicy.go @@ -0,0 +1,87 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + policyattachmentv1alpha1 "github.com/flomesh-io/fsm/pkg/apis/policyattachment/v1alpha1" + versioned "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/clientset/versioned" + internalinterfaces "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/listers/policyattachment/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// RateLimitPolicyInformer provides access to a shared informer and lister for +// RateLimitPolicies. +type RateLimitPolicyInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.RateLimitPolicyLister +} + +type rateLimitPolicyInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewRateLimitPolicyInformer constructs a new informer for RateLimitPolicy type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewRateLimitPolicyInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredRateLimitPolicyInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredRateLimitPolicyInformer constructs a new informer for RateLimitPolicy type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredRateLimitPolicyInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.GatewayV1alpha1().RateLimitPolicies(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.GatewayV1alpha1().RateLimitPolicies(namespace).Watch(context.TODO(), options) + }, + }, + &policyattachmentv1alpha1.RateLimitPolicy{}, + resyncPeriod, + indexers, + ) +} + +func (f *rateLimitPolicyInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredRateLimitPolicyInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *rateLimitPolicyInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&policyattachmentv1alpha1.RateLimitPolicy{}, f.defaultInformer) +} + +func (f *rateLimitPolicyInformer) Lister() v1alpha1.RateLimitPolicyLister { + return v1alpha1.NewRateLimitPolicyLister(f.Informer().GetIndexer()) +} diff --git a/pkg/gen/client/policyattachment/listers/policyattachment/v1alpha1/expansion_generated.go b/pkg/gen/client/policyattachment/listers/policyattachment/v1alpha1/expansion_generated.go new file mode 100644 index 000000000..cdf8aa895 --- /dev/null +++ b/pkg/gen/client/policyattachment/listers/policyattachment/v1alpha1/expansion_generated.go @@ -0,0 +1,24 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +// RateLimitPolicyListerExpansion allows custom methods to be added to +// RateLimitPolicyLister. +type RateLimitPolicyListerExpansion interface{} + +// RateLimitPolicyNamespaceListerExpansion allows custom methods to be added to +// RateLimitPolicyNamespaceLister. +type RateLimitPolicyNamespaceListerExpansion interface{} diff --git a/pkg/gen/client/policyattachment/listers/policyattachment/v1alpha1/ratelimitpolicy.go b/pkg/gen/client/policyattachment/listers/policyattachment/v1alpha1/ratelimitpolicy.go new file mode 100644 index 000000000..0cbe0e274 --- /dev/null +++ b/pkg/gen/client/policyattachment/listers/policyattachment/v1alpha1/ratelimitpolicy.go @@ -0,0 +1,96 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/flomesh-io/fsm/pkg/apis/policyattachment/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// RateLimitPolicyLister helps list RateLimitPolicies. +// All objects returned here must be treated as read-only. +type RateLimitPolicyLister interface { + // List lists all RateLimitPolicies in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.RateLimitPolicy, err error) + // RateLimitPolicies returns an object that can list and get RateLimitPolicies. + RateLimitPolicies(namespace string) RateLimitPolicyNamespaceLister + RateLimitPolicyListerExpansion +} + +// rateLimitPolicyLister implements the RateLimitPolicyLister interface. +type rateLimitPolicyLister struct { + indexer cache.Indexer +} + +// NewRateLimitPolicyLister returns a new RateLimitPolicyLister. +func NewRateLimitPolicyLister(indexer cache.Indexer) RateLimitPolicyLister { + return &rateLimitPolicyLister{indexer: indexer} +} + +// List lists all RateLimitPolicies in the indexer. +func (s *rateLimitPolicyLister) List(selector labels.Selector) (ret []*v1alpha1.RateLimitPolicy, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.RateLimitPolicy)) + }) + return ret, err +} + +// RateLimitPolicies returns an object that can list and get RateLimitPolicies. +func (s *rateLimitPolicyLister) RateLimitPolicies(namespace string) RateLimitPolicyNamespaceLister { + return rateLimitPolicyNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// RateLimitPolicyNamespaceLister helps list and get RateLimitPolicies. +// All objects returned here must be treated as read-only. +type RateLimitPolicyNamespaceLister interface { + // List lists all RateLimitPolicies in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.RateLimitPolicy, err error) + // Get retrieves the RateLimitPolicy from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.RateLimitPolicy, error) + RateLimitPolicyNamespaceListerExpansion +} + +// rateLimitPolicyNamespaceLister implements the RateLimitPolicyNamespaceLister +// interface. +type rateLimitPolicyNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all RateLimitPolicies in the indexer for a given namespace. +func (s rateLimitPolicyNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.RateLimitPolicy, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.RateLimitPolicy)) + }) + return ret, err +} + +// Get retrieves the RateLimitPolicy from the indexer for a given namespace and name. +func (s rateLimitPolicyNamespaceLister) Get(name string) (*v1alpha1.RateLimitPolicy, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("ratelimitpolicy"), name) + } + return obj.(*v1alpha1.RateLimitPolicy), nil +} diff --git a/pkg/k8s/informers/informers.go b/pkg/k8s/informers/informers.go index d153affa3..76d397bdb 100644 --- a/pkg/k8s/informers/informers.go +++ b/pkg/k8s/informers/informers.go @@ -39,6 +39,9 @@ import ( pluginInformers "github.com/flomesh-io/fsm/pkg/gen/client/plugin/informers/externalversions" policyClientset "github.com/flomesh-io/fsm/pkg/gen/client/policy/clientset/versioned" policyInformers "github.com/flomesh-io/fsm/pkg/gen/client/policy/informers/externalversions" + + policyAttachmentClientset "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/clientset/versioned" + policyAttachmentInformers "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/informers/externalversions" ) // InformerCollectionOption is a function that modifies an informer collection @@ -219,6 +222,16 @@ func WithIngressClient(kubeClient kubernetes.Interface, nsigClient nsigClientset } } +// WithPolicyAttachmentClient sets the PolicyAttachment client for the InformerCollection +func WithPolicyAttachmentClient(policyAttachmentClient policyAttachmentClientset.Interface) InformerCollectionOption { + return func(ic *InformerCollection) { + informerFactory := policyAttachmentInformers.NewSharedInformerFactory(policyAttachmentClient, DefaultKubeEventResyncInterval) + + ic.informers[InformerKeyRateLimitPolicy] = informerFactory.Gateway().V1alpha1().RateLimitPolicies().Informer() + ic.listers.RateLimitPolicy = informerFactory.Gateway().V1alpha1().RateLimitPolicies().Lister() + } +} + // WithGatewayAPIClient sets the gateway api client for the InformerCollection func WithGatewayAPIClient(gatewayAPIClient gatewayApiClientset.Interface) InformerCollectionOption { return func(ic *InformerCollection) { diff --git a/pkg/k8s/informers/types.go b/pkg/k8s/informers/types.go index 85ae394c0..1bd9df88c 100644 --- a/pkg/k8s/informers/types.go +++ b/pkg/k8s/informers/types.go @@ -4,6 +4,8 @@ import ( "errors" "time" + gwpav1alpha1 "github.com/flomesh-io/fsm/pkg/gen/client/policyattachment/listers/policyattachment/v1alpha1" + v1 "k8s.io/client-go/listers/core/v1" discoveryv1 "k8s.io/client-go/listers/discovery/v1" networkingv1 "k8s.io/client-go/listers/networking/v1" @@ -95,10 +97,12 @@ const ( InformerKeyGatewayAPIHTTPRoute InformerKey = "HTTPRoute-gwapi" // InformerKeyGatewayAPIGRPCRoute is the InformerKey for a GRPCRoute informer InformerKeyGatewayAPIGRPCRoute InformerKey = "GRPCRoute-gwapi" - // InformerKeyGatewayAPITLSRoute is the InformerKey for a IngressClass informer + // InformerKeyGatewayAPITLSRoute is the InformerKey for a TLSRoute informer InformerKeyGatewayAPITLSRoute InformerKey = "TLSRoute-gwapi" - // InformerKeyGatewayAPITCPRoute is the InformerKey for a IngressClass informer + // InformerKeyGatewayAPITCPRoute is the InformerKey for a TCPRoute informer InformerKeyGatewayAPITCPRoute InformerKey = "TCPRoute-gwapi" + // InformerKeyRateLimitPolicy is the InformerKey for a RateLimitPolicy informer + InformerKeyRateLimitPolicy InformerKey = "RateLimitPolicy" ) const ( @@ -138,4 +142,5 @@ type Lister struct { K8sIngressClass networkingv1.IngressClassLister K8sIngress networkingv1.IngressLister NamespacedIngress nsigv1alpha1.NamespacedIngressLister + RateLimitPolicy gwpav1alpha1.RateLimitPolicyLister } diff --git a/pkg/manager/reconciler/reconcilers.go b/pkg/manager/reconciler/reconcilers.go index 4079c4589..5df2d76da 100644 --- a/pkg/manager/reconciler/reconcilers.go +++ b/pkg/manager/reconciler/reconcilers.go @@ -33,6 +33,7 @@ import ( gatewayv1beta1 "github.com/flomesh-io/fsm/pkg/controllers/gateway/v1beta1" mcsv1alpha1 "github.com/flomesh-io/fsm/pkg/controllers/mcs/v1alpha1" nsigv1alpha1 "github.com/flomesh-io/fsm/pkg/controllers/namespacedingress/v1alpha1" + pav1alpha1 "github.com/flomesh-io/fsm/pkg/controllers/policyattachment/v1alpha1" svclb "github.com/flomesh-io/fsm/pkg/controllers/servicelb" "github.com/flomesh-io/fsm/pkg/version" ) @@ -62,6 +63,7 @@ func RegisterReconcilers(ctx *fctx.ControllerContext) error { reconcilers["GatewayAPI(GRPCRoute)"] = gatewayv1alpha2.NewGRPCRouteReconciler(ctx) reconcilers["GatewayAPI(TCPRoute)"] = gatewayv1alpha2.NewTCPRouteReconciler(ctx) reconcilers["GatewayAPI(TLSRoute)"] = gatewayv1alpha2.NewTLSRouteReconciler(ctx) + reconcilers["PolicyAttachment(RateLimit)"] = pav1alpha1.NewRateLimitPolicyReconciler(ctx) } if mc.IsNamespacedIngressEnabled() { diff --git a/pkg/manager/webhook/webhooks.go b/pkg/manager/webhook/webhooks.go index d2307ad09..a6ebd39dd 100644 --- a/pkg/manager/webhook/webhooks.go +++ b/pkg/manager/webhook/webhooks.go @@ -51,6 +51,7 @@ import ( "github.com/flomesh-io/fsm/pkg/webhook/httproute" "github.com/flomesh-io/fsm/pkg/webhook/ingress" "github.com/flomesh-io/fsm/pkg/webhook/namespacedingress" + "github.com/flomesh-io/fsm/pkg/webhook/ratelimit" "github.com/flomesh-io/fsm/pkg/webhook/serviceexport" "github.com/flomesh-io/fsm/pkg/webhook/serviceimport" "github.com/flomesh-io/fsm/pkg/webhook/tcproute" @@ -250,6 +251,7 @@ func getRegisters(regCfg *webhook.RegisterConfig, mc configurator.Configurator) result = append(result, grpcroute.NewRegister(regCfg)) result = append(result, tcproute.NewRegister(regCfg)) result = append(result, tlsroute.NewRegister(regCfg)) + result = append(result, ratelimit.NewRegister(regCfg)) } if mc.IsFLBEnabled() { diff --git a/pkg/messaging/broker.go b/pkg/messaging/broker.go index cf8b9156f..c2d021915 100644 --- a/pkg/messaging/broker.go +++ b/pkg/messaging/broker.go @@ -795,6 +795,8 @@ func getGatewayUpdateEvent(msg events.PubSubMessage) *gatewayUpdateEvent { announcements.GatewayAPITLSRouteAdded, announcements.GatewayAPITLSRouteDeleted, announcements.GatewayAPITLSRouteUpdated, // TCPRoute event announcements.GatewayAPITCPRouteAdded, announcements.GatewayAPITCPRouteDeleted, announcements.GatewayAPITCPRouteUpdated, + // RateLimitPolicy event + announcements.RateLimitPolicyAdded, announcements.RateLimitPolicyDeleted, announcements.RateLimitPolicyUpdated, // // MultiCluster events diff --git a/pkg/webhook/gateway/gateway_webhook.go b/pkg/webhook/gateway/gateway_webhook.go index 956766858..260d96aba 100644 --- a/pkg/webhook/gateway/gateway_webhook.go +++ b/pkg/webhook/gateway/gateway_webhook.go @@ -68,7 +68,7 @@ func NewRegister(cfg *webhook.RegisterConfig) webhook.Register { func (r *register) GetWebhooks() ([]admissionregv1.MutatingWebhook, []admissionregv1.ValidatingWebhook) { rule := flomeshadmission.NewRule( []admissionregv1.OperationType{admissionregv1.Create, admissionregv1.Update}, - []string{"gateway.networking.k8s.io"}, + []string{constants.GatewayAPIGroup}, []string{"v1beta1"}, []string{"gateways"}, ) diff --git a/pkg/webhook/gatewayclass/gatewayclass_webhook.go b/pkg/webhook/gatewayclass/gatewayclass_webhook.go index dfb83548d..a98d12b60 100644 --- a/pkg/webhook/gatewayclass/gatewayclass_webhook.go +++ b/pkg/webhook/gatewayclass/gatewayclass_webhook.go @@ -56,7 +56,7 @@ func NewRegister(cfg *webhook.RegisterConfig) webhook.Register { func (r *register) GetWebhooks() ([]admissionregv1.MutatingWebhook, []admissionregv1.ValidatingWebhook) { rule := flomeshadmission.NewRule( []admissionregv1.OperationType{admissionregv1.Create, admissionregv1.Update}, - []string{"gateway.networking.k8s.io"}, + []string{constants.GatewayAPIGroup}, []string{"v1beta1"}, []string{"gatewayclasses"}, ) diff --git a/pkg/webhook/grpcroute/grpcroute_webhook.go b/pkg/webhook/grpcroute/grpcroute_webhook.go index 7fecc78b5..223d8885d 100644 --- a/pkg/webhook/grpcroute/grpcroute_webhook.go +++ b/pkg/webhook/grpcroute/grpcroute_webhook.go @@ -55,7 +55,7 @@ func NewRegister(cfg *webhook.RegisterConfig) webhook.Register { func (r *register) GetWebhooks() ([]admissionregv1.MutatingWebhook, []admissionregv1.ValidatingWebhook) { rule := flomeshadmission.NewRule( []admissionregv1.OperationType{admissionregv1.Create, admissionregv1.Update}, - []string{"gateway.networking.k8s.io"}, + []string{constants.GatewayAPIGroup}, []string{"v1alpha2"}, []string{"grpcroutes"}, ) diff --git a/pkg/webhook/httproute/httproute_webhook.go b/pkg/webhook/httproute/httproute_webhook.go index 0e2c87488..aad69889b 100644 --- a/pkg/webhook/httproute/httproute_webhook.go +++ b/pkg/webhook/httproute/httproute_webhook.go @@ -55,7 +55,7 @@ func NewRegister(cfg *webhook.RegisterConfig) webhook.Register { func (r *register) GetWebhooks() ([]admissionregv1.MutatingWebhook, []admissionregv1.ValidatingWebhook) { rule := flomeshadmission.NewRule( []admissionregv1.OperationType{admissionregv1.Create, admissionregv1.Update}, - []string{"gateway.networking.k8s.io"}, + []string{constants.GatewayAPIGroup}, []string{"v1beta1"}, []string{"httproutes"}, ) diff --git a/pkg/webhook/ratelimit/webhook.go b/pkg/webhook/ratelimit/webhook.go new file mode 100644 index 000000000..b9f60834b --- /dev/null +++ b/pkg/webhook/ratelimit/webhook.go @@ -0,0 +1,370 @@ +package ratelimit + +import ( + "fmt" + "net/http" + + "github.com/flomesh-io/fsm/pkg/utils" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/pointer" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + + admissionregv1 "k8s.io/api/admissionregistration/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes" + + flomeshadmission "github.com/flomesh-io/fsm/pkg/admission" + gwpav1alpha1 "github.com/flomesh-io/fsm/pkg/apis/policyattachment/v1alpha1" + "github.com/flomesh-io/fsm/pkg/configurator" + "github.com/flomesh-io/fsm/pkg/constants" + "github.com/flomesh-io/fsm/pkg/logger" + "github.com/flomesh-io/fsm/pkg/webhook" +) + +var ( + log = logger.New("webhook/ratelimit") +) + +type register struct { + *webhook.RegisterConfig +} + +// NewRegister creates a new RateLimitPolicy webhook register +func NewRegister(cfg *webhook.RegisterConfig) webhook.Register { + return ®ister{ + RegisterConfig: cfg, + } +} + +// GetWebhooks returns the webhooks to be registered for RateLimitPolicy +func (r *register) GetWebhooks() ([]admissionregv1.MutatingWebhook, []admissionregv1.ValidatingWebhook) { + rule := flomeshadmission.NewRule( + []admissionregv1.OperationType{admissionregv1.Create, admissionregv1.Update}, + []string{"gateway.flomesh.io"}, + []string{"v1alpha1"}, + []string{"ratelimitpolicies"}, + ) + + return []admissionregv1.MutatingWebhook{flomeshadmission.NewMutatingWebhook( + "mratelimitpolicy.kb.flomesh.io", + r.WebhookSvcNs, + r.WebhookSvcName, + constants.RateLimitPolicyMutatingWebhookPath, + r.CaBundle, + nil, + nil, + admissionregv1.Ignore, + []admissionregv1.RuleWithOperations{rule}, + )}, []admissionregv1.ValidatingWebhook{flomeshadmission.NewValidatingWebhook( + "vratelimitpolicy.kb.flomesh.io", + r.WebhookSvcNs, + r.WebhookSvcName, + constants.RateLimitPolicyValidatingWebhookPath, + r.CaBundle, + nil, + nil, + admissionregv1.Ignore, + []admissionregv1.RuleWithOperations{rule}, + )} +} + +// GetHandlers returns the handlers to be registered for RateLimitPolicy +func (r *register) GetHandlers() map[string]http.Handler { + return map[string]http.Handler{ + constants.RateLimitPolicyMutatingWebhookPath: webhook.DefaultingWebhookFor(newDefaulter(r.KubeClient, r.Config)), + constants.RateLimitPolicyValidatingWebhookPath: webhook.ValidatingWebhookFor(newValidator(r.KubeClient)), + } +} + +type defaulter struct { + kubeClient kubernetes.Interface + cfg configurator.Configurator +} + +func newDefaulter(kubeClient kubernetes.Interface, cfg configurator.Configurator) *defaulter { + return &defaulter{ + kubeClient: kubeClient, + cfg: cfg, + } +} + +// RuntimeObject returns the runtime object for the webhook +func (w *defaulter) RuntimeObject() runtime.Object { + return &gwpav1alpha1.RateLimitPolicy{} +} + +// SetDefaults sets the default values for the RateLimitPolicy +func (w *defaulter) SetDefaults(obj interface{}) { + policy, ok := obj.(*gwpav1alpha1.RateLimitPolicy) + if !ok { + return + } + + log.Debug().Msgf("Default Webhook, name=%s", policy.Name) + log.Debug().Msgf("Before setting default values, spec=%v", policy.Spec) + + if policy.Spec.TargetRef.Group == constants.GatewayAPIGroup { + if policy.Spec.TargetRef.Kind == constants.HTTPRouteKind || + policy.Spec.TargetRef.Kind == constants.GRPCRouteKind { + if len(policy.Spec.Hostnames) > 0 || len(policy.Spec.HTTPRateLimits) > 0 || len(policy.Spec.GRPCRateLimits) > 0 { + setDefaults(policy) + } + } + } + + log.Debug().Msgf("After setting default values, spec=%v", policy.Spec) +} + +func setDefaults(policy *gwpav1alpha1.RateLimitPolicy) { + if policy.Spec.DefaultL7RateLimit != nil { + policy.Spec.DefaultL7RateLimit = l7RateLimitDefaults(policy.Spec.DefaultL7RateLimit) + } + + if len(policy.Spec.Hostnames) > 0 { + for i, hostname := range policy.Spec.Hostnames { + if hostname.RateLimit == nil && policy.Spec.DefaultL7RateLimit != nil { + policy.Spec.Hostnames[i].RateLimit = policy.Spec.DefaultL7RateLimit + } + + if hostname.RateLimit != nil { + policy.Spec.Hostnames[i].RateLimit = l7RateLimitDefaults(hostname.RateLimit) + } + } + } + + if len(policy.Spec.HTTPRateLimits) > 0 { + for i, hr := range policy.Spec.HTTPRateLimits { + if hr.RateLimit == nil && policy.Spec.DefaultL7RateLimit != nil { + policy.Spec.HTTPRateLimits[i].RateLimit = policy.Spec.DefaultL7RateLimit + } + + if hr.RateLimit != nil { + policy.Spec.HTTPRateLimits[i].RateLimit = l7RateLimitDefaults(hr.RateLimit) + } + } + } + + if len(policy.Spec.GRPCRateLimits) > 0 { + for i, gr := range policy.Spec.GRPCRateLimits { + if gr.RateLimit == nil && policy.Spec.DefaultL7RateLimit != nil { + policy.Spec.GRPCRateLimits[i].RateLimit = policy.Spec.DefaultL7RateLimit + } + + if gr.RateLimit != nil { + policy.Spec.GRPCRateLimits[i].RateLimit = l7RateLimitDefaults(gr.RateLimit) + } + } + } +} + +func l7RateLimitDefaults(rateLimit *gwpav1alpha1.L7RateLimit) *gwpav1alpha1.L7RateLimit { + result := rateLimit.DeepCopy() + + if result.Mode == nil { + result.Mode = rateLimitPolicyModePointer(gwpav1alpha1.RateLimitPolicyModeLocal) + } + + if result.Backlog == nil { + result.Backlog = pointer.Int(10) + } + + if result.Burst == nil { + result.Burst = &result.Requests + } + + return result +} + +func rateLimitPolicyModePointer(mode gwpav1alpha1.RateLimitPolicyMode) *gwpav1alpha1.RateLimitPolicyMode { + return &mode +} + +type validator struct { + kubeClient kubernetes.Interface +} + +// RuntimeObject returns the runtime object for the webhook +func (w *validator) RuntimeObject() runtime.Object { + return &gwpav1alpha1.RateLimitPolicy{} +} + +// ValidateCreate validates the creation of the RateLimitPolicy +func (w *validator) ValidateCreate(obj interface{}) error { + return doValidation(obj) +} + +// ValidateUpdate validates the update of the RateLimitPolicy +func (w *validator) ValidateUpdate(_, obj interface{}) error { + return doValidation(obj) +} + +// ValidateDelete validates the deletion of the RateLimitPolicy +func (w *validator) ValidateDelete(_ interface{}) error { + return nil +} + +func newValidator(kubeClient kubernetes.Interface) *validator { + return &validator{ + kubeClient: kubeClient, + } +} + +func doValidation(obj interface{}) error { + policy, ok := obj.(*gwpav1alpha1.RateLimitPolicy) + if !ok { + return nil + } + + errorList := validateTargetRef(policy.Spec.TargetRef) + errorList = append(errorList, validateConfig(policy)...) + + if len(errorList) > 0 { + return utils.ErrorListToError(errorList) + } + + return nil +} + +func validateTargetRef(ref gwv1alpha2.PolicyTargetReference) field.ErrorList { + var errs field.ErrorList + + if ref.Group != constants.GatewayAPIGroup { + path := field.NewPath("spec").Child("targetRef").Child("group") + errs = append(errs, field.Invalid(path, ref.Group, "group must be set to gateway.networking.k8s.io")) + } + + switch ref.Kind { + case constants.GatewayKind, constants.HTTPRouteKind, constants.GRPCRouteKind: + // do nothing + default: + path := field.NewPath("spec").Child("targetRef").Child("kind") + errs = append(errs, field.Invalid(path, ref.Kind, "kind must be set to Gateway, HTTPRoute or GRPCRoute")) + } + + return errs +} + +func validateConfig(policy *gwpav1alpha1.RateLimitPolicy) field.ErrorList { + errs := validateL4RateLimits(policy) + errs = append(errs, validateL7RateLimits(policy)...) + + return errs +} + +func validateL4RateLimits(policy *gwpav1alpha1.RateLimitPolicy) field.ErrorList { + var errs field.ErrorList + + if policy.Spec.TargetRef.Group == constants.GatewayAPIGroup && + policy.Spec.TargetRef.Kind == constants.GatewayKind { + if len(policy.Spec.Ports) == 0 { + path := field.NewPath("spec").Child("ports") + errs = append(errs, field.Invalid(path, policy.Spec.Ports, "cannot be empty for Gateway target")) + } + + if len(policy.Spec.Hostnames) > 0 { + path := field.NewPath("spec").Child("hostnames") + errs = append(errs, field.Invalid(path, policy.Spec.Hostnames, "must be empty for Gateway target")) + } + + if len(policy.Spec.HTTPRateLimits) > 0 { + path := field.NewPath("spec").Child("http") + errs = append(errs, field.Invalid(path, policy.Spec.HTTPRateLimits, "must be empty for Gateway target")) + } + + if len(policy.Spec.GRPCRateLimits) > 0 { + path := field.NewPath("spec").Child("grpc") + errs = append(errs, field.Invalid(path, policy.Spec.GRPCRateLimits, "must be empty for Gateway target")) + } + + if policy.Spec.DefaultL7RateLimit != nil { + path := field.NewPath("spec").Child("rateLimit") + errs = append(errs, field.Invalid(path, policy.Spec.DefaultL7RateLimit, "must not be set for Gateway target")) + } + + if policy.Spec.DefaultBPS == nil { + path := field.NewPath("spec").Child("ports") + for i, port := range policy.Spec.Ports { + if port.BPS == nil { + errs = append(errs, field.Required(path.Index(i).Child("bps"), fmt.Sprintf("bps must be set for port %d, as there's no default BPS", port.Port))) + } + } + } + } + return errs +} + +func validateL7RateLimits(policy *gwpav1alpha1.RateLimitPolicy) field.ErrorList { + var errs field.ErrorList + + if policy.Spec.TargetRef.Group == constants.GatewayAPIGroup && + (policy.Spec.TargetRef.Kind == constants.HTTPRouteKind || policy.Spec.TargetRef.Kind == constants.GRPCRouteKind) { + if len(policy.Spec.Ports) > 0 { + path := field.NewPath("spec").Child("ports") + errs = append(errs, field.Invalid(path, policy.Spec.Ports, "must be empty for HTTPRoute/GRPCRoute target")) + } + + if policy.Spec.DefaultBPS != nil { + path := field.NewPath("spec").Child("bps") + errs = append(errs, field.Invalid(path, policy.Spec.DefaultBPS, "must not be set for HTTPRoute/GRPCRoute target")) + } + + if len(policy.Spec.Hostnames) == 0 && len(policy.Spec.HTTPRateLimits) == 0 && len(policy.Spec.GRPCRateLimits) == 0 { + path := field.NewPath("spec") + errs = append(errs, field.Invalid(path, policy.Spec, "any one of hostnames, http or grpc must be set for HTTPRoute/GRPCRoute target")) + } + + if len(policy.Spec.Hostnames) > 0 { + errs = append(errs, validateHostnames(policy)...) + } + + if policy.Spec.DefaultL7RateLimit == nil { + if len(policy.Spec.Hostnames) > 0 { + path := field.NewPath("spec").Child("hostnames") + for i, h := range policy.Spec.Hostnames { + if h.RateLimit == nil { + errs = append(errs, field.Required(path.Index(i).Child("rateLimit"), fmt.Sprintf("rateLimit must be set for hostname %q, as there's no default rate limit", h.Hostname))) + } + } + } + + if len(policy.Spec.HTTPRateLimits) > 0 { + path := field.NewPath("spec").Child("http") + for i, h := range policy.Spec.HTTPRateLimits { + if h.RateLimit == nil { + errs = append(errs, field.Required(path.Index(i).Child("rateLimit"), "rateLimit must be set, as there's no default rate limit")) + } + } + } + + if len(policy.Spec.Hostnames) > 0 { + path := field.NewPath("spec").Child("grpc") + for i, g := range policy.Spec.GRPCRateLimits { + if g.RateLimit == nil { + errs = append(errs, field.Required(path.Index(i).Child("rateLimit"), "rateLimit must be set, as there's no default rate limit")) + } + } + } + } + } + + return errs +} + +func validateHostnames(policy *gwpav1alpha1.RateLimitPolicy) field.ErrorList { + var errs field.ErrorList + + for i, r := range policy.Spec.Hostnames { + h := string(r.Hostname) + if err := webhook.IsValidHostname(h); err != nil { + path := field.NewPath("spec"). + Child("hostnames"). + Index(i). + Child("hostname") + + errs = append(errs, field.Invalid(path, h, fmt.Sprintf("%s", err))) + } + } + + return errs +} diff --git a/pkg/webhook/tcproute/tcproute_webhook.go b/pkg/webhook/tcproute/tcproute_webhook.go index acbc819a4..fadf8cf94 100644 --- a/pkg/webhook/tcproute/tcproute_webhook.go +++ b/pkg/webhook/tcproute/tcproute_webhook.go @@ -55,7 +55,7 @@ func NewRegister(cfg *webhook.RegisterConfig) webhook.Register { func (r *register) GetWebhooks() ([]admissionregv1.MutatingWebhook, []admissionregv1.ValidatingWebhook) { rule := flomeshadmission.NewRule( []admissionregv1.OperationType{admissionregv1.Create, admissionregv1.Update}, - []string{"gateway.networking.k8s.io"}, + []string{constants.GatewayAPIGroup}, []string{"v1alpha2"}, []string{"tcproutes"}, ) diff --git a/pkg/webhook/tlsroute/tlsproute_webhook.go b/pkg/webhook/tlsroute/tlsproute_webhook.go index aef5763d4..ad2f60b9e 100644 --- a/pkg/webhook/tlsroute/tlsproute_webhook.go +++ b/pkg/webhook/tlsroute/tlsproute_webhook.go @@ -55,7 +55,7 @@ func NewRegister(cfg *webhook.RegisterConfig) webhook.Register { func (r *register) GetWebhooks() ([]admissionregv1.MutatingWebhook, []admissionregv1.ValidatingWebhook) { rule := flomeshadmission.NewRule( []admissionregv1.OperationType{admissionregv1.Create, admissionregv1.Update}, - []string{"gateway.networking.k8s.io"}, + []string{constants.GatewayAPIGroup}, []string{"v1alpha2"}, []string{"tlsroutes"}, )