diff --git a/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml b/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml index f8105b7dcd..56b9e8a957 100644 --- a/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml +++ b/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml @@ -675,6 +675,53 @@ spec: different namespace than the VirtualServer, you need to include the namespace. For example, tea-namespace/tea. type: string + routeSelector: + description: The RouteSelector allows selecting VirtualServerRoute + resources using label selectors. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic splits: description: The default splits configuration for traffic splitting. Must include at least 2 splits. diff --git a/config/crd/bases/k8s.nginx.org_virtualservers.yaml b/config/crd/bases/k8s.nginx.org_virtualservers.yaml index 677beb84aa..968909033f 100644 --- a/config/crd/bases/k8s.nginx.org_virtualservers.yaml +++ b/config/crd/bases/k8s.nginx.org_virtualservers.yaml @@ -762,6 +762,53 @@ spec: different namespace than the VirtualServer, you need to include the namespace. For example, tea-namespace/tea. type: string + routeSelector: + description: The RouteSelector allows selecting VirtualServerRoute + resources using label selectors. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic splits: description: The default splits configuration for traffic splitting. Must include at least 2 splits. diff --git a/deploy/crds.yaml b/deploy/crds.yaml index 93fc92efaa..65cad36278 100644 --- a/deploy/crds.yaml +++ b/deploy/crds.yaml @@ -1865,6 +1865,53 @@ spec: different namespace than the VirtualServer, you need to include the namespace. For example, tea-namespace/tea. type: string + routeSelector: + description: The RouteSelector allows selecting VirtualServerRoute + resources using label selectors. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic splits: description: The default splits configuration for traffic splitting. Must include at least 2 splits. @@ -3189,6 +3236,53 @@ spec: different namespace than the VirtualServer, you need to include the namespace. For example, tea-namespace/tea. type: string + routeSelector: + description: The RouteSelector allows selecting VirtualServerRoute + resources using label selectors. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic splits: description: The default splits configuration for traffic splitting. Must include at least 2 splits. diff --git a/examples/custom-resources/cross-namespace-configuration/api-key-policy.yaml b/examples/custom-resources/cross-namespace-configuration/api-key-policy.yaml new file mode 100644 index 0000000000..4b517b0a1f --- /dev/null +++ b/examples/custom-resources/cross-namespace-configuration/api-key-policy.yaml @@ -0,0 +1,13 @@ +apiVersion: k8s.nginx.org/v1 +kind: Policy +metadata: + name: api-key-policy + namespace: cafe +spec: + apiKey: + suppliedIn: + header: + - "X-header-name" + query: + - "queryName" + clientSecret: api-key-client-secret diff --git a/examples/custom-resources/cross-namespace-configuration/api-key-secret.yaml b/examples/custom-resources/cross-namespace-configuration/api-key-secret.yaml new file mode 100644 index 0000000000..7f3adb0711 --- /dev/null +++ b/examples/custom-resources/cross-namespace-configuration/api-key-secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: api-key-client-secret + namespace: cafe +type: nginx.org/apikey +data: + client1: cGFzc3dvcmQ= # password + client2: YW5vdGhlci1wYXNzd29yZA== # another-password diff --git a/examples/custom-resources/cross-namespace-configuration/cafe-virtual-server.yaml b/examples/custom-resources/cross-namespace-configuration/cafe-virtual-server.yaml index df871b319a..d764ac5b35 100644 --- a/examples/custom-resources/cross-namespace-configuration/cafe-virtual-server.yaml +++ b/examples/custom-resources/cross-namespace-configuration/cafe-virtual-server.yaml @@ -7,8 +7,28 @@ spec: host: cafe.example.com tls: secret: cafe-secret + server-snippets: | + # snippet defined in VS server block + proxy_set_header X-VS-Name "Cafe"; routes: - - path: /tea - route: tea/tea - - path: /coffee - route: coffee/coffee +# - path: /tea +# route: tea/tea +# policies: +# - name: rate-limit-policy +# - path: /coffee +# route: coffee/coffee + - path: / + routeSelector: + matchLabels: + app: cafe +# route: tea + policies: + - name: api-key-policy + location-snippets: | + # snippet defined in VS + proxy_set_header X-VS-Name "Cafe"; + errorPages: + - codes: [ 502, 503 ] + redirect: + code: 301 + url: https://nginx.org diff --git a/examples/custom-resources/cross-namespace-configuration/coffee-virtual-server-route.yaml b/examples/custom-resources/cross-namespace-configuration/coffee-virtual-server-route.yaml index 4f89de0779..7d1ac9bffb 100644 --- a/examples/custom-resources/cross-namespace-configuration/coffee-virtual-server-route.yaml +++ b/examples/custom-resources/cross-namespace-configuration/coffee-virtual-server-route.yaml @@ -3,6 +3,9 @@ kind: VirtualServerRoute metadata: name: coffee namespace: coffee + labels: + app: cafe + route: coffee spec: host: cafe.example.com upstreams: @@ -13,3 +16,13 @@ spec: - path: /coffee action: pass: coffee + policies: + - name: rate-limit-policy + location-snippets: | + # snippet defined in VSR + proxy_set_header X-VSR-Name "Coffee"; + errorPages: + - codes: [404] + return: + code: 200 + body: "Original resource not found, but success!" diff --git a/examples/custom-resources/cross-namespace-configuration/rate-limit.yaml b/examples/custom-resources/cross-namespace-configuration/rate-limit.yaml new file mode 100644 index 0000000000..7da2027130 --- /dev/null +++ b/examples/custom-resources/cross-namespace-configuration/rate-limit.yaml @@ -0,0 +1,10 @@ +apiVersion: k8s.nginx.org/v1 +kind: Policy +metadata: + name: rate-limit-policy + namespace: coffee +spec: + rateLimit: + rate: 1r/s + key: ${binary_remote_addr} + zoneSize: 10M diff --git a/examples/custom-resources/cross-namespace-configuration/tea-virtual-server-route.yaml b/examples/custom-resources/cross-namespace-configuration/tea-virtual-server-route.yaml index 25e7a08b45..b332c71fba 100644 --- a/examples/custom-resources/cross-namespace-configuration/tea-virtual-server-route.yaml +++ b/examples/custom-resources/cross-namespace-configuration/tea-virtual-server-route.yaml @@ -3,6 +3,9 @@ kind: VirtualServerRoute metadata: name: tea namespace: tea + labels: + route: tea + app: cafe spec: host: cafe.example.com upstreams: @@ -13,3 +16,6 @@ spec: - path: /tea action: pass: tea +# location-snippets: | +# # snippet defined in VSR +# proxy_set_header X-VSR-Name "Tea"; diff --git a/examples/custom-resources/vsr-route-selector/README.md b/examples/custom-resources/vsr-route-selector/README.md new file mode 100644 index 0000000000..2e3dc59aa8 --- /dev/null +++ b/examples/custom-resources/vsr-route-selector/README.md @@ -0,0 +1,128 @@ +# Basic, single-namespace VirtualServerRoute Selector + +In this example we use the [VirtualServer and +VirtualServerRoute](https://docs.nginx.com/nginx-ingress-controller/configuration/virtualserver-and-virtualserverroute-resources/) +resources to configure load balancing for the modified cafe application from the [Basic +Configuration](../basic-configuration/) example. We have put the load balancing configuration as well as the deployments +and services into one default namespace. + +- In the default namespace, we create the tea deployment, service, and the corresponding load-balancing configuration. +- In the same namespace, we create the cafe secret with the TLS certificate and key and the load-balancing configuration + for the cafe application. That configuration references the tea configuration. + +## Prerequisites + + +## Step 1 - Install NGINX Ingress COntroller + +1. Follow the [installation](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/) + instructions to deploy the Ingress Controller with custom resources enabled. + +1. Save the public IP address of the Ingress Controller into a shell variable: + + ```console + IC_IP=XXX.YYY.ZZZ.III + ``` + +1. Save the HTTPS port of the Ingress Controller into a shell variable: + + ```console + IC_HTTPS_PORT= + ``` + + +## Step 2 - Deploy the Cafe Application + +1. Create the tea deployment and service in the tea namespace: + + ```console + kubectl create -f tea.yaml + ``` + +1. Create the coffee deployment and service in the default namespace: + + ```console + kubectl create -f coffee.yaml + ``` + +## Step 3 - Configure Load Balancing and TLS Termination + +1. Create the VirtualServerRoute resource for tea: + + ```console + kubectl create -f tea-virtual-server-route.yaml + ``` + +1. Create the VirtualServerRoute resource for coffee: + + ```console + kubectl create -f coffee-virtual-server-route.yaml + ``` + +1. Create the secret with the TLS certificate and key: + + ```console + kubectl create -f cafe-secret.yaml + ``` + +1. Create the VirtualServer resource for the cafe app: + + ```console + kubectl create -f cafe-virtual-server.yaml + ``` + +## Step 4 - Test the Configuration + +1. Check that the configuration has been successfully applied by inspecting the events of the VirtualServerRoutes and + VirtualServer: + + ```console + kubectl describe virtualserverroute tea -n tea + ``` + + ```text + WIP - add an example + ``` + + ```console + kubectl describe virtualserverroute coffee + ``` + + ```text + WIP - add an example + ``` + + ```console + kubectl describe virtualserver cafe + ``` + + ```text + WIP - add example + ``` + +1. Access the application using curl. We'll use curl's `--insecure` option to turn off certificate verification of our + self-signed certificate and `--resolve` option to set the IP address and HTTPS port of the Ingress Controller to the + domain name of the cafe application: + + To get coffee: + + ```console + curl --resolve cafe.example.com:$IC_HTTPS_PORT:$IC_IP https://cafe.example.com:$IC_HTTPS_PORT/coffee --insecure + ``` + + ```text + Server address: 10.16.1.193:80 + Server name: coffee-7dbb5795f6-mltpf + ... + ``` + + If your prefer tea: + + ```console + curl --resolve cafe.example.com:$IC_HTTPS_PORT:$IC_IP https://cafe.example.com:$IC_HTTPS_PORT/tea --insecure + ``` + + ```text + Server address: 10.16.0.157:80 + Server name: tea-7d57856c44-674b8 + ... diff --git a/examples/custom-resources/vsr-route-selector/api-key-policy.yaml b/examples/custom-resources/vsr-route-selector/api-key-policy.yaml new file mode 100644 index 0000000000..4b517b0a1f --- /dev/null +++ b/examples/custom-resources/vsr-route-selector/api-key-policy.yaml @@ -0,0 +1,13 @@ +apiVersion: k8s.nginx.org/v1 +kind: Policy +metadata: + name: api-key-policy + namespace: cafe +spec: + apiKey: + suppliedIn: + header: + - "X-header-name" + query: + - "queryName" + clientSecret: api-key-client-secret diff --git a/examples/custom-resources/vsr-route-selector/api-key-secret.yaml b/examples/custom-resources/vsr-route-selector/api-key-secret.yaml new file mode 100644 index 0000000000..1c7533769c --- /dev/null +++ b/examples/custom-resources/vsr-route-selector/api-key-secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: api-key-client-secret + namespace: default +type: nginx.org/apikey +data: + client1: cGFzc3dvcmQ= # password + client2: YW5vdGhlci1wYXNzd29yZA== # another-password diff --git a/examples/custom-resources/vsr-route-selector/cafe-secret.yaml b/examples/custom-resources/vsr-route-selector/cafe-secret.yaml new file mode 100644 index 0000000000..5e6ba01bf6 --- /dev/null +++ b/examples/custom-resources/vsr-route-selector/cafe-secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: cafe-secret + namespace: default +type: kubernetes.io/tls +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURMakNDQWhZQ0NRREFPRjl0THNhWFdqQU5CZ2txaGtpRzl3MEJBUXNGQURCYU1Rc3dDUVlEVlFRR0V3SlYKVXpFTE1Ba0dBMVVFQ0F3Q1EwRXhJVEFmQmdOVkJBb01HRWx1ZEdWeWJtVjBJRmRwWkdkcGRITWdVSFI1SUV4MApaREViTUJrR0ExVUVBd3dTWTJGbVpTNWxlR0Z0Y0d4bExtTnZiU0FnTUI0WERURTRNRGt4TWpFMk1UVXpOVm9YCkRUSXpNRGt4TVRFMk1UVXpOVm93V0RFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVNFd0h3WUQKVlFRS0RCaEpiblJsY201bGRDQlhhV1JuYVhSeklGQjBlU0JNZEdReEdUQVhCZ05WQkFNTUVHTmhabVV1WlhoaApiWEJzWlM1amIyMHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDcDZLbjdzeTgxCnAwanVKL2N5ayt2Q0FtbHNmanRGTTJtdVpOSzBLdGVjcUcyZmpXUWI1NXhRMVlGQTJYT1N3SEFZdlNkd0kyaloKcnVXOHFYWENMMnJiNENaQ0Z4d3BWRUNyY3hkam0zdGVWaVJYVnNZSW1tSkhQUFN5UWdwaW9iczl4N0RsTGM2SQpCQTBaalVPeWwwUHFHOVNKZXhNVjczV0lJYTVyRFZTRjJyNGtTa2JBajREY2o3TFhlRmxWWEgySTVYd1hDcHRDCm42N0pDZzQyZitrOHdnemNSVnA4WFprWldaVmp3cTlSVUtEWG1GQjJZeU4xWEVXZFowZXdSdUtZVUpsc202OTIKc2tPcktRajB2a29QbjQxRUUvK1RhVkVwcUxUUm9VWTNyemc3RGtkemZkQml6Rk8yZHNQTkZ4MkNXMGpYa05MdgpLbzI1Q1pyT2hYQUhBZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFLSEZDY3lPalp2b0hzd1VCTWRMClJkSEliMzgzcFdGeW5acS9MdVVvdnNWQTU4QjBDZzdCRWZ5NXZXVlZycTVSSWt2NGxaODFOMjl4MjFkMUpINnIKalNuUXgrRFhDTy9USkVWNWxTQ1VwSUd6RVVZYVVQZ1J5anNNL05VZENKOHVIVmhaSitTNkZBK0NuT0Q5cm4yaQpaQmVQQ0k1ckh3RVh3bm5sOHl3aWozdnZRNXpISXV5QmdsV3IvUXl1aTlmalBwd1dVdlVtNG52NVNNRzl6Q1Y3ClBwdXd2dWF0cWpPMTIwOEJqZkUvY1pISWc4SHc5bXZXOXg5QytJUU1JTURFN2IvZzZPY0s3TEdUTHdsRnh2QTgKN1dqRWVxdW5heUlwaE1oS1JYVmYxTjM0OWVOOThFejM4Zk9USFRQYmRKakZBL1BjQytHeW1lK2lHdDVPUWRGaAp5UkU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBcWVpcCs3TXZOYWRJN2lmM01wUHJ3Z0pwYkg0N1JUTnBybVRTdENyWG5LaHRuNDFrCkcrZWNVTldCUU5semtzQndHTDBuY0NObzJhN2x2S2wxd2k5cTIrQW1RaGNjS1ZSQXEzTVhZNXQ3WGxZa1YxYkcKQ0pwaVJ6ejBza0lLWXFHN1BjZXc1UzNPaUFRTkdZMURzcGRENmh2VWlYc1RGZTkxaUNHdWF3MVVoZHErSkVwRwp3SStBM0kreTEzaFpWVng5aU9WOEZ3cWJRcCt1eVFvT05uL3BQTUlNM0VWYWZGMlpHVm1WWThLdlVWQ2cxNWhRCmRtTWpkVnhGbldkSHNFYmltRkNaYkp1dmRySkRxeWtJOUw1S0Q1K05SQlAvazJsUkthaTAwYUZHTjY4NE93NUgKYzMzUVlzeFR0bmJEelJjZGdsdEkxNURTN3lxTnVRbWF6b1Z3QndJREFRQUJBb0lCQVFDUFNkU1luUXRTUHlxbApGZlZGcFRPc29PWVJoZjhzSStpYkZ4SU91UmF1V2VoaEp4ZG01Uk9ScEF6bUNMeUw1VmhqdEptZTIyM2dMcncyCk45OUVqVUtiL1ZPbVp1RHNCYzZvQ0Y2UU5SNThkejhjbk9SVGV3Y290c0pSMXBuMWhobG5SNUhxSkpCSmFzazEKWkVuVVFmY1hackw5NGxvOUpIM0UrVXFqbzFGRnM4eHhFOHdvUEJxalpzVjdwUlVaZ0MzTGh4bndMU0V4eUZvNApjeGI5U09HNU9tQUpvelN0Rm9RMkdKT2VzOHJKNXFmZHZ5dGdnOXhiTGFRTC94MGtwUTYyQm9GTUJEZHFPZVBXCktmUDV6WjYvMDcvdnBqNDh5QTFRMzJQem9idWJzQkxkM0tjbjMyamZtMUU3cHJ0V2wrSmVPRmlPem5CUUZKYk4KNHFQVlJ6NWhBb0dCQU50V3l4aE5DU0x1NFArWGdLeWNrbGpKNkY1NjY4Zk5qNUN6Z0ZScUowOXpuMFRsc05ybwpGVExaY3hEcW5SM0hQWU00MkpFUmgySi9xREZaeW5SUW8zY2czb2VpdlVkQlZHWTgrRkkxVzBxZHViL0w5K3l1CmVkT1pUUTVYbUdHcDZyNmpleHltY0ppbS9Pc0IzWm5ZT3BPcmxEN1NQbUJ2ek5MazRNRjZneGJYQW9HQkFNWk8KMHA2SGJCbWNQMHRqRlhmY0tFNzdJbUxtMHNBRzR1SG9VeDBlUGovMnFyblRuT0JCTkU0TXZnRHVUSnp5K2NhVQprOFJxbWRIQ2JIelRlNmZ6WXEvOWl0OHNaNzdLVk4xcWtiSWN1YytSVHhBOW5OaDFUanNSbmU3NFowajFGQ0xrCmhIY3FIMHJpN1BZU0tIVEU4RnZGQ3haWWRidUI4NENtWmlodnhicFJBb0dBSWJqcWFNWVBUWXVrbENkYTVTNzkKWVNGSjFKelplMUtqYS8vdER3MXpGY2dWQ0thMzFqQXdjaXowZi9sU1JxM0hTMUdHR21lemhQVlRpcUxmZVpxYwpSMGlLYmhnYk9jVlZrSkozSzB5QXlLd1BUdW14S0haNnpJbVpTMGMwYW0rUlk5WUdxNVQ3WXJ6cHpjZnZwaU9VCmZmZTNSeUZUN2NmQ21mb09oREN0enVrQ2dZQjMwb0xDMVJMRk9ycW40M3ZDUzUxemM1em9ZNDR1QnpzcHd3WU4KVHd2UC9FeFdNZjNWSnJEakJDSCtULzZzeXNlUGJKRUltbHpNK0l3eXRGcEFOZmlJWEV0LzQ4WGY2ME54OGdXTQp1SHl4Wlp4L05LdER3MFY4dlgxUE9ucTJBNWVpS2ErOGpSQVJZS0pMWU5kZkR1d29seHZHNmJaaGtQaS80RXRUCjNZMThzUUtCZ0h0S2JrKzdsTkpWZXN3WEU1Y1VHNkVEVXNEZS8yVWE3ZlhwN0ZjanFCRW9hcDFMU3crNlRYcDAKWmdybUtFOEFSek00NytFSkhVdmlpcS9udXBFMTVnMGtKVzNzeWhwVTl6WkxPN2x0QjBLSWtPOVpSY21Vam84UQpjcExsSE1BcWJMSjhXWUdKQ2toaVd4eWFsNmhZVHlXWTRjVmtDMHh0VGwvaFVFOUllTktvCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== diff --git a/examples/custom-resources/vsr-route-selector/cafe-virtual-server.yaml b/examples/custom-resources/vsr-route-selector/cafe-virtual-server.yaml new file mode 100644 index 0000000000..69c2303f60 --- /dev/null +++ b/examples/custom-resources/vsr-route-selector/cafe-virtual-server.yaml @@ -0,0 +1,34 @@ +apiVersion: k8s.nginx.org/v1 +kind: VirtualServer +metadata: + name: cafe + namespace: default +spec: + host: cafe.example.com + tls: + secret: cafe-secret + server-snippets: | + # snippet defined in VS server block + proxy_set_header X-VS-Name "Cafe"; + routes: +# - path: /tea +# route: tea/tea +# policies: +# - name: rate-limit-policy +# - path: /coffee +# route: coffee/coffee + - path: / + routeSelector: + matchLabels: + app: cafe +# route: tea + policies: + - name: api-key-policy + location-snippets: | + # snippet defined in VS + proxy_set_header X-VS-Name "Cafe"; + errorPages: + - codes: [ 502, 503 ] + redirect: + code: 301 + url: https://nginx.org diff --git a/examples/custom-resources/vsr-route-selector/coffee-virtual-server-route.yaml b/examples/custom-resources/vsr-route-selector/coffee-virtual-server-route.yaml new file mode 100644 index 0000000000..1805aeeac3 --- /dev/null +++ b/examples/custom-resources/vsr-route-selector/coffee-virtual-server-route.yaml @@ -0,0 +1,28 @@ +apiVersion: k8s.nginx.org/v1 +kind: VirtualServerRoute +metadata: + name: coffee + namespace: default + labels: + app: cafe + route: coffee +spec: + host: cafe.example.com + upstreams: + - name: coffee + service: coffee-svc + port: 80 + subroutes: + - path: /coffee + action: + pass: coffee + policies: + - name: rate-limit-policy + location-snippets: | + # snippet defined in VSR + proxy_set_header X-VSR-Name "Coffee"; + errorPages: + - codes: [404] + return: + code: 200 + body: "Original resource not found, but success!" diff --git a/examples/custom-resources/vsr-route-selector/coffee.yaml b/examples/custom-resources/vsr-route-selector/coffee.yaml new file mode 100644 index 0000000000..83ccd6d3c9 --- /dev/null +++ b/examples/custom-resources/vsr-route-selector/coffee.yaml @@ -0,0 +1,34 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: coffee + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: coffee + template: + metadata: + labels: + app: coffee + spec: + containers: + - name: coffee + image: nginxdemos/nginx-hello:plain-text + ports: + - containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: coffee-svc + namespace: default +spec: + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: coffee diff --git a/examples/custom-resources/vsr-route-selector/tea-virtual-server-route.yaml b/examples/custom-resources/vsr-route-selector/tea-virtual-server-route.yaml new file mode 100644 index 0000000000..7d1043e868 --- /dev/null +++ b/examples/custom-resources/vsr-route-selector/tea-virtual-server-route.yaml @@ -0,0 +1,21 @@ +apiVersion: k8s.nginx.org/v1 +kind: VirtualServerRoute +metadata: + name: tea + namespace: default + labels: + route: tea + app: cafe +spec: + host: cafe.example.com + upstreams: + - name: tea + service: tea-svc + port: 80 + subroutes: + - path: /tea + action: + pass: tea +# location-snippets: | +# # snippet defined in VSR +# proxy_set_header X-VSR-Name "Tea"; diff --git a/examples/custom-resources/vsr-route-selector/tea.yaml b/examples/custom-resources/vsr-route-selector/tea.yaml new file mode 100644 index 0000000000..a4f98e1244 --- /dev/null +++ b/examples/custom-resources/vsr-route-selector/tea.yaml @@ -0,0 +1,34 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: tea + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: tea + template: + metadata: + labels: + app: tea + spec: + containers: + - name: tea + image: nginxdemos/nginx-hello:plain-text + ports: + - containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: tea-svc + namespace: default +spec: + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: tea diff --git a/examples/custom-resources/vsr-selector-basic/README.md b/examples/custom-resources/vsr-selector-basic/README.md new file mode 100644 index 0000000000..3dc2c1b198 --- /dev/null +++ b/examples/custom-resources/vsr-selector-basic/README.md @@ -0,0 +1,88 @@ +# Basic, Single-Namespace VirtualServeRoute Configuration + +In this example we use the [VirtualServer and +VirtualServerRoute](https://docs.nginx.com/nginx-ingress-controller/configuration/virtualserver-and-virtualserverroute-resources/) +resources to configure load balancing for the modified cafe application from the [Basic +Configuration](../basic-configuration/) example. We have put the load balancing configuration as well as the deployments +and services into one default namespace. + +## Prerequisites + +1. Follow the [installation](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/) + instructions to deploy the Ingress Controller with custom resources enabled. +1. Save the public IP address of the Ingress Controller into a shell variable: + + ```console + IC_IP=XXX.YYY.ZZZ.III + ``` + +1. Save the HTTPS port of the Ingress Controller into a shell variable: + + ```console + IC_HTTPS_PORT= + ``` + +## Step 1 - Deploy the Cafe Application + +Create the coffee and the tea deployments and services: + +```console +kubectl create -f cafe.yaml +``` + +## Step 2 - Configure Load Balancing and TLS Termination + +1. Create the secret with the TLS certificate and key: + + ```console + kubectl create -f cafe-secret.yaml + ``` + +2. Create the VirtualServer resource: + + ```console + kubectl create -f cafe-virtual-server.yaml + ``` + +## Step 3 - Test the Configuration + +1. Check that the configuration has been successfully applied by inspecting the events of the VirtualServer: + + ```console + kubectl describe virtualserver cafe + ``` + + ```text + . . . + Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal AddedOrUpdated 7s nginx-ingress-controller Configuration for default/cafe was added or updated + ``` + +1. Access the application using curl. We'll use curl's `--insecure` option to turn off certificate verification of our + self-signed certificate and `--resolve` option to set the IP address and HTTPS port of the Ingress Controller to the + domain name of the cafe application: + + To get coffee: + + ```console + curl --resolve cafe.example.com:$IC_HTTPS_PORT:$IC_IP https://cafe.example.com:$IC_HTTPS_PORT/coffee --insecure + ``` + + ```text + Server address: 10.16.1.182:80 + Server name: coffee-7dbb5795f6-tnbtq + ... + ``` + + If your prefer tea: + + ```console + curl --resolve cafe.example.com:$IC_HTTPS_PORT:$IC_IP https://cafe.example.com:$IC_HTTPS_PORT/tea --insecure + ``` + + ```text + Server address: 10.16.0.149:80 + Server name: tea-7d57856c44-zlftd + ... diff --git a/examples/custom-resources/vsr-selector-basic/cafe-secret.yaml b/examples/custom-resources/vsr-selector-basic/cafe-secret.yaml new file mode 100644 index 0000000000..8f9fd84855 --- /dev/null +++ b/examples/custom-resources/vsr-selector-basic/cafe-secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: cafe-secret +type: kubernetes.io/tls +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURMakNDQWhZQ0NRREFPRjl0THNhWFdqQU5CZ2txaGtpRzl3MEJBUXNGQURCYU1Rc3dDUVlEVlFRR0V3SlYKVXpFTE1Ba0dBMVVFQ0F3Q1EwRXhJVEFmQmdOVkJBb01HRWx1ZEdWeWJtVjBJRmRwWkdkcGRITWdVSFI1SUV4MApaREViTUJrR0ExVUVBd3dTWTJGbVpTNWxlR0Z0Y0d4bExtTnZiU0FnTUI0WERURTRNRGt4TWpFMk1UVXpOVm9YCkRUSXpNRGt4TVRFMk1UVXpOVm93V0RFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVNFd0h3WUQKVlFRS0RCaEpiblJsY201bGRDQlhhV1JuYVhSeklGQjBlU0JNZEdReEdUQVhCZ05WQkFNTUVHTmhabVV1WlhoaApiWEJzWlM1amIyMHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDcDZLbjdzeTgxCnAwanVKL2N5ayt2Q0FtbHNmanRGTTJtdVpOSzBLdGVjcUcyZmpXUWI1NXhRMVlGQTJYT1N3SEFZdlNkd0kyaloKcnVXOHFYWENMMnJiNENaQ0Z4d3BWRUNyY3hkam0zdGVWaVJYVnNZSW1tSkhQUFN5UWdwaW9iczl4N0RsTGM2SQpCQTBaalVPeWwwUHFHOVNKZXhNVjczV0lJYTVyRFZTRjJyNGtTa2JBajREY2o3TFhlRmxWWEgySTVYd1hDcHRDCm42N0pDZzQyZitrOHdnemNSVnA4WFprWldaVmp3cTlSVUtEWG1GQjJZeU4xWEVXZFowZXdSdUtZVUpsc202OTIKc2tPcktRajB2a29QbjQxRUUvK1RhVkVwcUxUUm9VWTNyemc3RGtkemZkQml6Rk8yZHNQTkZ4MkNXMGpYa05MdgpLbzI1Q1pyT2hYQUhBZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFLSEZDY3lPalp2b0hzd1VCTWRMClJkSEliMzgzcFdGeW5acS9MdVVvdnNWQTU4QjBDZzdCRWZ5NXZXVlZycTVSSWt2NGxaODFOMjl4MjFkMUpINnIKalNuUXgrRFhDTy9USkVWNWxTQ1VwSUd6RVVZYVVQZ1J5anNNL05VZENKOHVIVmhaSitTNkZBK0NuT0Q5cm4yaQpaQmVQQ0k1ckh3RVh3bm5sOHl3aWozdnZRNXpISXV5QmdsV3IvUXl1aTlmalBwd1dVdlVtNG52NVNNRzl6Q1Y3ClBwdXd2dWF0cWpPMTIwOEJqZkUvY1pISWc4SHc5bXZXOXg5QytJUU1JTURFN2IvZzZPY0s3TEdUTHdsRnh2QTgKN1dqRWVxdW5heUlwaE1oS1JYVmYxTjM0OWVOOThFejM4Zk9USFRQYmRKakZBL1BjQytHeW1lK2lHdDVPUWRGaAp5UkU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBcWVpcCs3TXZOYWRJN2lmM01wUHJ3Z0pwYkg0N1JUTnBybVRTdENyWG5LaHRuNDFrCkcrZWNVTldCUU5semtzQndHTDBuY0NObzJhN2x2S2wxd2k5cTIrQW1RaGNjS1ZSQXEzTVhZNXQ3WGxZa1YxYkcKQ0pwaVJ6ejBza0lLWXFHN1BjZXc1UzNPaUFRTkdZMURzcGRENmh2VWlYc1RGZTkxaUNHdWF3MVVoZHErSkVwRwp3SStBM0kreTEzaFpWVng5aU9WOEZ3cWJRcCt1eVFvT05uL3BQTUlNM0VWYWZGMlpHVm1WWThLdlVWQ2cxNWhRCmRtTWpkVnhGbldkSHNFYmltRkNaYkp1dmRySkRxeWtJOUw1S0Q1K05SQlAvazJsUkthaTAwYUZHTjY4NE93NUgKYzMzUVlzeFR0bmJEelJjZGdsdEkxNURTN3lxTnVRbWF6b1Z3QndJREFRQUJBb0lCQVFDUFNkU1luUXRTUHlxbApGZlZGcFRPc29PWVJoZjhzSStpYkZ4SU91UmF1V2VoaEp4ZG01Uk9ScEF6bUNMeUw1VmhqdEptZTIyM2dMcncyCk45OUVqVUtiL1ZPbVp1RHNCYzZvQ0Y2UU5SNThkejhjbk9SVGV3Y290c0pSMXBuMWhobG5SNUhxSkpCSmFzazEKWkVuVVFmY1hackw5NGxvOUpIM0UrVXFqbzFGRnM4eHhFOHdvUEJxalpzVjdwUlVaZ0MzTGh4bndMU0V4eUZvNApjeGI5U09HNU9tQUpvelN0Rm9RMkdKT2VzOHJKNXFmZHZ5dGdnOXhiTGFRTC94MGtwUTYyQm9GTUJEZHFPZVBXCktmUDV6WjYvMDcvdnBqNDh5QTFRMzJQem9idWJzQkxkM0tjbjMyamZtMUU3cHJ0V2wrSmVPRmlPem5CUUZKYk4KNHFQVlJ6NWhBb0dCQU50V3l4aE5DU0x1NFArWGdLeWNrbGpKNkY1NjY4Zk5qNUN6Z0ZScUowOXpuMFRsc05ybwpGVExaY3hEcW5SM0hQWU00MkpFUmgySi9xREZaeW5SUW8zY2czb2VpdlVkQlZHWTgrRkkxVzBxZHViL0w5K3l1CmVkT1pUUTVYbUdHcDZyNmpleHltY0ppbS9Pc0IzWm5ZT3BPcmxEN1NQbUJ2ek5MazRNRjZneGJYQW9HQkFNWk8KMHA2SGJCbWNQMHRqRlhmY0tFNzdJbUxtMHNBRzR1SG9VeDBlUGovMnFyblRuT0JCTkU0TXZnRHVUSnp5K2NhVQprOFJxbWRIQ2JIelRlNmZ6WXEvOWl0OHNaNzdLVk4xcWtiSWN1YytSVHhBOW5OaDFUanNSbmU3NFowajFGQ0xrCmhIY3FIMHJpN1BZU0tIVEU4RnZGQ3haWWRidUI4NENtWmlodnhicFJBb0dBSWJqcWFNWVBUWXVrbENkYTVTNzkKWVNGSjFKelplMUtqYS8vdER3MXpGY2dWQ0thMzFqQXdjaXowZi9sU1JxM0hTMUdHR21lemhQVlRpcUxmZVpxYwpSMGlLYmhnYk9jVlZrSkozSzB5QXlLd1BUdW14S0haNnpJbVpTMGMwYW0rUlk5WUdxNVQ3WXJ6cHpjZnZwaU9VCmZmZTNSeUZUN2NmQ21mb09oREN0enVrQ2dZQjMwb0xDMVJMRk9ycW40M3ZDUzUxemM1em9ZNDR1QnpzcHd3WU4KVHd2UC9FeFdNZjNWSnJEakJDSCtULzZzeXNlUGJKRUltbHpNK0l3eXRGcEFOZmlJWEV0LzQ4WGY2ME54OGdXTQp1SHl4Wlp4L05LdER3MFY4dlgxUE9ucTJBNWVpS2ErOGpSQVJZS0pMWU5kZkR1d29seHZHNmJaaGtQaS80RXRUCjNZMThzUUtCZ0h0S2JrKzdsTkpWZXN3WEU1Y1VHNkVEVXNEZS8yVWE3ZlhwN0ZjanFCRW9hcDFMU3crNlRYcDAKWmdybUtFOEFSek00NytFSkhVdmlpcS9udXBFMTVnMGtKVzNzeWhwVTl6WkxPN2x0QjBLSWtPOVpSY21Vam84UQpjcExsSE1BcWJMSjhXWUdKQ2toaVd4eWFsNmhZVHlXWTRjVmtDMHh0VGwvaFVFOUllTktvCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== diff --git a/examples/custom-resources/vsr-selector-basic/cafe-virtual-server-2.yaml b/examples/custom-resources/vsr-selector-basic/cafe-virtual-server-2.yaml new file mode 100644 index 0000000000..cd006a8d93 --- /dev/null +++ b/examples/custom-resources/vsr-selector-basic/cafe-virtual-server-2.yaml @@ -0,0 +1,22 @@ +apiVersion: k8s.nginx.org/v1 +kind: VirtualServer +metadata: + name: cafe +spec: + host: cafe.example.com + tls: + secret: cafe-secret + upstreams: + - name: tea + service: tea-svc + port: 80 + - name: coffee + service: coffee-svc + port: 80 + routes: + - path: /tea + action: + pass: tea + - path: /coffee + action: + pass: coffee diff --git a/examples/custom-resources/vsr-selector-basic/cafe-virtual-server-orig.yaml b/examples/custom-resources/vsr-selector-basic/cafe-virtual-server-orig.yaml new file mode 100644 index 0000000000..cd006a8d93 --- /dev/null +++ b/examples/custom-resources/vsr-selector-basic/cafe-virtual-server-orig.yaml @@ -0,0 +1,22 @@ +apiVersion: k8s.nginx.org/v1 +kind: VirtualServer +metadata: + name: cafe +spec: + host: cafe.example.com + tls: + secret: cafe-secret + upstreams: + - name: tea + service: tea-svc + port: 80 + - name: coffee + service: coffee-svc + port: 80 + routes: + - path: /tea + action: + pass: tea + - path: /coffee + action: + pass: coffee diff --git a/examples/custom-resources/vsr-selector-basic/cafe-virtual-server.yaml b/examples/custom-resources/vsr-selector-basic/cafe-virtual-server.yaml new file mode 100644 index 0000000000..cd006a8d93 --- /dev/null +++ b/examples/custom-resources/vsr-selector-basic/cafe-virtual-server.yaml @@ -0,0 +1,22 @@ +apiVersion: k8s.nginx.org/v1 +kind: VirtualServer +metadata: + name: cafe +spec: + host: cafe.example.com + tls: + secret: cafe-secret + upstreams: + - name: tea + service: tea-svc + port: 80 + - name: coffee + service: coffee-svc + port: 80 + routes: + - path: /tea + action: + pass: tea + - path: /coffee + action: + pass: coffee diff --git a/examples/custom-resources/vsr-selector-basic/cafe.yaml b/examples/custom-resources/vsr-selector-basic/cafe.yaml new file mode 100644 index 0000000000..f049e8bf29 --- /dev/null +++ b/examples/custom-resources/vsr-selector-basic/cafe.yaml @@ -0,0 +1,65 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: coffee +spec: + replicas: 2 + selector: + matchLabels: + app: coffee + template: + metadata: + labels: + app: coffee + spec: + containers: + - name: coffee + image: nginxdemos/nginx-hello:plain-text + ports: + - containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: coffee-svc +spec: + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: coffee +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: tea +spec: + replicas: 1 + selector: + matchLabels: + app: tea + template: + metadata: + labels: + app: tea + spec: + containers: + - name: tea + image: nginxdemos/nginx-hello:plain-text + ports: + - containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: tea-svc +spec: + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: tea diff --git a/examples/custom-resources/vsr-selector-basic/coffee-virtual-server-route.yaml b/examples/custom-resources/vsr-selector-basic/coffee-virtual-server-route.yaml new file mode 100644 index 0000000000..e5c25895e0 --- /dev/null +++ b/examples/custom-resources/vsr-selector-basic/coffee-virtual-server-route.yaml @@ -0,0 +1,15 @@ +apiVersion: k8s.nginx.org/v1 +kind: VirtualServerRoute +metadata: + name: coffee + namespace: default +spec: + host: cafe.example.com + upstreams: + - name: coffee + service: coffee-svc + port: 80 + subroutes: + - path: /coffee + action: + pass: coffee diff --git a/examples/custom-resources/vsr-selector-multinamespace/README.md b/examples/custom-resources/vsr-selector-multinamespace/README.md new file mode 100644 index 0000000000..329e694960 --- /dev/null +++ b/examples/custom-resources/vsr-selector-multinamespace/README.md @@ -0,0 +1,89 @@ +# Basic Configuration + +In this example we use the [VirtualServer and +VirtualServerRoute](https://docs.nginx.com/nginx-ingress-controller/configuration/virtualserver-and-virtualserverroute-resources/) +resources to configure load balancing for the modified cafe application from the [Basic +Configuration](../basic-configuration/) example. We have put the load balancing configuration as well as the deployments +and services into one namespace. + + +## Prerequisites + +1. Follow the [installation](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/) + instructions to deploy the Ingress Controller with custom resources enabled. +1. Save the public IP address of the Ingress Controller into a shell variable: + + ```console + IC_IP=XXX.YYY.ZZZ.III + ``` + +1. Save the HTTPS port of the Ingress Controller into a shell variable: + + ```console + IC_HTTPS_PORT= + ``` + +## Step 1 - Deploy the Cafe Application + +Create the coffee and the tea deployments and services: + +```console +kubectl create -f cafe.yaml +``` + +## Step 2 - Configure Load Balancing and TLS Termination + +1. Create the secret with the TLS certificate and key: + + ```console + kubectl create -f cafe-secret.yaml + ``` + +2. Create the VirtualServer resource: + + ```console + kubectl create -f cafe-virtual-server.yaml + ``` + +## Step 3 - Test the Configuration + +1. Check that the configuration has been successfully applied by inspecting the events of the VirtualServer: + + ```console + kubectl describe virtualserver cafe + ``` + + ```text + . . . + Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal AddedOrUpdated 7s nginx-ingress-controller Configuration for default/cafe was added or updated + ``` + +1. Access the application using curl. We'll use curl's `--insecure` option to turn off certificate verification of our + self-signed certificate and `--resolve` option to set the IP address and HTTPS port of the Ingress Controller to the + domain name of the cafe application: + + To get coffee: + + ```console + curl --resolve cafe.example.com:$IC_HTTPS_PORT:$IC_IP https://cafe.example.com:$IC_HTTPS_PORT/coffee --insecure + ``` + + ```text + Server address: 10.16.1.182:80 + Server name: coffee-7dbb5795f6-tnbtq + ... + ``` + + If your prefer tea: + + ```console + curl --resolve cafe.example.com:$IC_HTTPS_PORT:$IC_IP https://cafe.example.com:$IC_HTTPS_PORT/tea --insecure + ``` + + ```text + Server address: 10.16.0.149:80 + Server name: tea-7d57856c44-zlftd + ... diff --git a/examples/custom-resources/vsr-selector-multinamespace/cafe-secret.yaml b/examples/custom-resources/vsr-selector-multinamespace/cafe-secret.yaml new file mode 100644 index 0000000000..8f9fd84855 --- /dev/null +++ b/examples/custom-resources/vsr-selector-multinamespace/cafe-secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: cafe-secret +type: kubernetes.io/tls +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURMakNDQWhZQ0NRREFPRjl0THNhWFdqQU5CZ2txaGtpRzl3MEJBUXNGQURCYU1Rc3dDUVlEVlFRR0V3SlYKVXpFTE1Ba0dBMVVFQ0F3Q1EwRXhJVEFmQmdOVkJBb01HRWx1ZEdWeWJtVjBJRmRwWkdkcGRITWdVSFI1SUV4MApaREViTUJrR0ExVUVBd3dTWTJGbVpTNWxlR0Z0Y0d4bExtTnZiU0FnTUI0WERURTRNRGt4TWpFMk1UVXpOVm9YCkRUSXpNRGt4TVRFMk1UVXpOVm93V0RFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVNFd0h3WUQKVlFRS0RCaEpiblJsY201bGRDQlhhV1JuYVhSeklGQjBlU0JNZEdReEdUQVhCZ05WQkFNTUVHTmhabVV1WlhoaApiWEJzWlM1amIyMHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDcDZLbjdzeTgxCnAwanVKL2N5ayt2Q0FtbHNmanRGTTJtdVpOSzBLdGVjcUcyZmpXUWI1NXhRMVlGQTJYT1N3SEFZdlNkd0kyaloKcnVXOHFYWENMMnJiNENaQ0Z4d3BWRUNyY3hkam0zdGVWaVJYVnNZSW1tSkhQUFN5UWdwaW9iczl4N0RsTGM2SQpCQTBaalVPeWwwUHFHOVNKZXhNVjczV0lJYTVyRFZTRjJyNGtTa2JBajREY2o3TFhlRmxWWEgySTVYd1hDcHRDCm42N0pDZzQyZitrOHdnemNSVnA4WFprWldaVmp3cTlSVUtEWG1GQjJZeU4xWEVXZFowZXdSdUtZVUpsc202OTIKc2tPcktRajB2a29QbjQxRUUvK1RhVkVwcUxUUm9VWTNyemc3RGtkemZkQml6Rk8yZHNQTkZ4MkNXMGpYa05MdgpLbzI1Q1pyT2hYQUhBZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFLSEZDY3lPalp2b0hzd1VCTWRMClJkSEliMzgzcFdGeW5acS9MdVVvdnNWQTU4QjBDZzdCRWZ5NXZXVlZycTVSSWt2NGxaODFOMjl4MjFkMUpINnIKalNuUXgrRFhDTy9USkVWNWxTQ1VwSUd6RVVZYVVQZ1J5anNNL05VZENKOHVIVmhaSitTNkZBK0NuT0Q5cm4yaQpaQmVQQ0k1ckh3RVh3bm5sOHl3aWozdnZRNXpISXV5QmdsV3IvUXl1aTlmalBwd1dVdlVtNG52NVNNRzl6Q1Y3ClBwdXd2dWF0cWpPMTIwOEJqZkUvY1pISWc4SHc5bXZXOXg5QytJUU1JTURFN2IvZzZPY0s3TEdUTHdsRnh2QTgKN1dqRWVxdW5heUlwaE1oS1JYVmYxTjM0OWVOOThFejM4Zk9USFRQYmRKakZBL1BjQytHeW1lK2lHdDVPUWRGaAp5UkU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBcWVpcCs3TXZOYWRJN2lmM01wUHJ3Z0pwYkg0N1JUTnBybVRTdENyWG5LaHRuNDFrCkcrZWNVTldCUU5semtzQndHTDBuY0NObzJhN2x2S2wxd2k5cTIrQW1RaGNjS1ZSQXEzTVhZNXQ3WGxZa1YxYkcKQ0pwaVJ6ejBza0lLWXFHN1BjZXc1UzNPaUFRTkdZMURzcGRENmh2VWlYc1RGZTkxaUNHdWF3MVVoZHErSkVwRwp3SStBM0kreTEzaFpWVng5aU9WOEZ3cWJRcCt1eVFvT05uL3BQTUlNM0VWYWZGMlpHVm1WWThLdlVWQ2cxNWhRCmRtTWpkVnhGbldkSHNFYmltRkNaYkp1dmRySkRxeWtJOUw1S0Q1K05SQlAvazJsUkthaTAwYUZHTjY4NE93NUgKYzMzUVlzeFR0bmJEelJjZGdsdEkxNURTN3lxTnVRbWF6b1Z3QndJREFRQUJBb0lCQVFDUFNkU1luUXRTUHlxbApGZlZGcFRPc29PWVJoZjhzSStpYkZ4SU91UmF1V2VoaEp4ZG01Uk9ScEF6bUNMeUw1VmhqdEptZTIyM2dMcncyCk45OUVqVUtiL1ZPbVp1RHNCYzZvQ0Y2UU5SNThkejhjbk9SVGV3Y290c0pSMXBuMWhobG5SNUhxSkpCSmFzazEKWkVuVVFmY1hackw5NGxvOUpIM0UrVXFqbzFGRnM4eHhFOHdvUEJxalpzVjdwUlVaZ0MzTGh4bndMU0V4eUZvNApjeGI5U09HNU9tQUpvelN0Rm9RMkdKT2VzOHJKNXFmZHZ5dGdnOXhiTGFRTC94MGtwUTYyQm9GTUJEZHFPZVBXCktmUDV6WjYvMDcvdnBqNDh5QTFRMzJQem9idWJzQkxkM0tjbjMyamZtMUU3cHJ0V2wrSmVPRmlPem5CUUZKYk4KNHFQVlJ6NWhBb0dCQU50V3l4aE5DU0x1NFArWGdLeWNrbGpKNkY1NjY4Zk5qNUN6Z0ZScUowOXpuMFRsc05ybwpGVExaY3hEcW5SM0hQWU00MkpFUmgySi9xREZaeW5SUW8zY2czb2VpdlVkQlZHWTgrRkkxVzBxZHViL0w5K3l1CmVkT1pUUTVYbUdHcDZyNmpleHltY0ppbS9Pc0IzWm5ZT3BPcmxEN1NQbUJ2ek5MazRNRjZneGJYQW9HQkFNWk8KMHA2SGJCbWNQMHRqRlhmY0tFNzdJbUxtMHNBRzR1SG9VeDBlUGovMnFyblRuT0JCTkU0TXZnRHVUSnp5K2NhVQprOFJxbWRIQ2JIelRlNmZ6WXEvOWl0OHNaNzdLVk4xcWtiSWN1YytSVHhBOW5OaDFUanNSbmU3NFowajFGQ0xrCmhIY3FIMHJpN1BZU0tIVEU4RnZGQ3haWWRidUI4NENtWmlodnhicFJBb0dBSWJqcWFNWVBUWXVrbENkYTVTNzkKWVNGSjFKelplMUtqYS8vdER3MXpGY2dWQ0thMzFqQXdjaXowZi9sU1JxM0hTMUdHR21lemhQVlRpcUxmZVpxYwpSMGlLYmhnYk9jVlZrSkozSzB5QXlLd1BUdW14S0haNnpJbVpTMGMwYW0rUlk5WUdxNVQ3WXJ6cHpjZnZwaU9VCmZmZTNSeUZUN2NmQ21mb09oREN0enVrQ2dZQjMwb0xDMVJMRk9ycW40M3ZDUzUxemM1em9ZNDR1QnpzcHd3WU4KVHd2UC9FeFdNZjNWSnJEakJDSCtULzZzeXNlUGJKRUltbHpNK0l3eXRGcEFOZmlJWEV0LzQ4WGY2ME54OGdXTQp1SHl4Wlp4L05LdER3MFY4dlgxUE9ucTJBNWVpS2ErOGpSQVJZS0pMWU5kZkR1d29seHZHNmJaaGtQaS80RXRUCjNZMThzUUtCZ0h0S2JrKzdsTkpWZXN3WEU1Y1VHNkVEVXNEZS8yVWE3ZlhwN0ZjanFCRW9hcDFMU3crNlRYcDAKWmdybUtFOEFSek00NytFSkhVdmlpcS9udXBFMTVnMGtKVzNzeWhwVTl6WkxPN2x0QjBLSWtPOVpSY21Vam84UQpjcExsSE1BcWJMSjhXWUdKQ2toaVd4eWFsNmhZVHlXWTRjVmtDMHh0VGwvaFVFOUllTktvCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== diff --git a/examples/custom-resources/vsr-selector-multinamespace/cafe-virtual-server-2.yaml b/examples/custom-resources/vsr-selector-multinamespace/cafe-virtual-server-2.yaml new file mode 100644 index 0000000000..cd006a8d93 --- /dev/null +++ b/examples/custom-resources/vsr-selector-multinamespace/cafe-virtual-server-2.yaml @@ -0,0 +1,22 @@ +apiVersion: k8s.nginx.org/v1 +kind: VirtualServer +metadata: + name: cafe +spec: + host: cafe.example.com + tls: + secret: cafe-secret + upstreams: + - name: tea + service: tea-svc + port: 80 + - name: coffee + service: coffee-svc + port: 80 + routes: + - path: /tea + action: + pass: tea + - path: /coffee + action: + pass: coffee diff --git a/examples/custom-resources/vsr-selector-multinamespace/cafe-virtual-server-orig.yaml b/examples/custom-resources/vsr-selector-multinamespace/cafe-virtual-server-orig.yaml new file mode 100644 index 0000000000..cd006a8d93 --- /dev/null +++ b/examples/custom-resources/vsr-selector-multinamespace/cafe-virtual-server-orig.yaml @@ -0,0 +1,22 @@ +apiVersion: k8s.nginx.org/v1 +kind: VirtualServer +metadata: + name: cafe +spec: + host: cafe.example.com + tls: + secret: cafe-secret + upstreams: + - name: tea + service: tea-svc + port: 80 + - name: coffee + service: coffee-svc + port: 80 + routes: + - path: /tea + action: + pass: tea + - path: /coffee + action: + pass: coffee diff --git a/examples/custom-resources/vsr-selector-multinamespace/cafe-virtual-server.yaml b/examples/custom-resources/vsr-selector-multinamespace/cafe-virtual-server.yaml new file mode 100644 index 0000000000..cd006a8d93 --- /dev/null +++ b/examples/custom-resources/vsr-selector-multinamespace/cafe-virtual-server.yaml @@ -0,0 +1,22 @@ +apiVersion: k8s.nginx.org/v1 +kind: VirtualServer +metadata: + name: cafe +spec: + host: cafe.example.com + tls: + secret: cafe-secret + upstreams: + - name: tea + service: tea-svc + port: 80 + - name: coffee + service: coffee-svc + port: 80 + routes: + - path: /tea + action: + pass: tea + - path: /coffee + action: + pass: coffee diff --git a/examples/custom-resources/vsr-selector-multinamespace/cafe.yaml b/examples/custom-resources/vsr-selector-multinamespace/cafe.yaml new file mode 100644 index 0000000000..f049e8bf29 --- /dev/null +++ b/examples/custom-resources/vsr-selector-multinamespace/cafe.yaml @@ -0,0 +1,65 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: coffee +spec: + replicas: 2 + selector: + matchLabels: + app: coffee + template: + metadata: + labels: + app: coffee + spec: + containers: + - name: coffee + image: nginxdemos/nginx-hello:plain-text + ports: + - containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: coffee-svc +spec: + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: coffee +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: tea +spec: + replicas: 1 + selector: + matchLabels: + app: tea + template: + metadata: + labels: + app: tea + spec: + containers: + - name: tea + image: nginxdemos/nginx-hello:plain-text + ports: + - containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: tea-svc +spec: + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: tea diff --git a/examples/custom-resources/vsr-selector-multinamespace/coffee-virtual-server-route.yaml b/examples/custom-resources/vsr-selector-multinamespace/coffee-virtual-server-route.yaml new file mode 100644 index 0000000000..4f89de0779 --- /dev/null +++ b/examples/custom-resources/vsr-selector-multinamespace/coffee-virtual-server-route.yaml @@ -0,0 +1,15 @@ +apiVersion: k8s.nginx.org/v1 +kind: VirtualServerRoute +metadata: + name: coffee + namespace: coffee +spec: + host: cafe.example.com + upstreams: + - name: coffee + service: coffee-svc + port: 80 + subroutes: + - path: /coffee + action: + pass: coffee diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index be16b1f3fb..c0687fa6e2 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -19,6 +19,7 @@ import ( "github.com/nginx/kubernetes-ingress/internal/nginx" conf_v1 "github.com/nginx/kubernetes-ingress/pkg/apis/configuration/v1" api_v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" @@ -87,24 +88,25 @@ type PodInfo struct { // VirtualServerEx holds a VirtualServer along with the resources that are referenced in this VirtualServer. type VirtualServerEx struct { - VirtualServer *conf_v1.VirtualServer - HTTPPort int - HTTPSPort int - HTTPIPv4 string - HTTPIPv6 string - HTTPSIPv4 string - HTTPSIPv6 string - Endpoints map[string][]string - VirtualServerRoutes []*conf_v1.VirtualServerRoute - ExternalNameSvcs map[string]bool - Policies map[string]*conf_v1.Policy - PodsByIP map[string]PodInfo - SecretRefs map[string]*secrets.SecretReference - ApPolRefs map[string]*unstructured.Unstructured - LogConfRefs map[string]*unstructured.Unstructured - DosProtectedRefs map[string]*unstructured.Unstructured - DosProtectedEx map[string]*DosEx - ZoneSync bool + VirtualServer *conf_v1.VirtualServer + HTTPPort int + HTTPSPort int + HTTPIPv4 string + HTTPIPv6 string + HTTPSIPv4 string + HTTPSIPv6 string + Endpoints map[string][]string + VirtualServerRoutes []*conf_v1.VirtualServerRoute + VirtualServerSelectorRoutes map[string][]string + ExternalNameSvcs map[string]bool + Policies map[string]*conf_v1.Policy + PodsByIP map[string]PodInfo + SecretRefs map[string]*secrets.SecretReference + ApPolRefs map[string]*unstructured.Unstructured + LogConfRefs map[string]*unstructured.Unstructured + DosProtectedRefs map[string]*unstructured.Unstructured + DosProtectedEx map[string]*DosEx + ZoneSync bool } func (vsx *VirtualServerEx) String() string { @@ -572,6 +574,41 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig( vsrPoliciesFromVs[name] = r.Policies } + continue + } else if r.RouteSelector != nil { + + // get vsr name + + selector := &metav1.LabelSelector{ + MatchLabels: r.RouteSelector.MatchLabels, + } + sel, _ := metav1.LabelSelectorAsSelector(selector) + + selectorKey := sel.String() + vsrKeys := vsEx.VirtualServerSelectorRoutes[selectorKey] + + // store route location snippet for the referenced VirtualServerRoute in case they don't define their own + if r.LocationSnippets != "" { + for _, name := range vsrKeys { + vsrLocationSnippetsFromVs[name] = r.LocationSnippets + } + } + + // store route error pages and route index for the referenced VirtualServerRoute in case they don't define their own + if len(r.ErrorPages) > 0 { + for _, name := range vsrKeys { + vsrErrorPagesFromVs[name] = errorPages.pages + vsrErrorPagesRouteIndex[name] = errorPages.index + } + } + + // store route policies for the referenced VirtualServerRoute in case they don't define their own + if len(r.Policies) > 0 { + for _, name := range vsrKeys { + vsrPoliciesFromVs[name] = r.Policies + } + } + continue } diff --git a/internal/k8s/configuration.go b/internal/k8s/configuration.go index 0ca8050563..91cea15e90 100644 --- a/internal/k8s/configuration.go +++ b/internal/k8s/configuration.go @@ -16,6 +16,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" ) const ( @@ -207,23 +208,25 @@ func NewMinionConfiguration(ing *networking.Ingress) *MinionConfiguration { // VirtualServerConfiguration holds a VirtualServer along with its VirtualServerRoutes. type VirtualServerConfiguration struct { - VirtualServer *conf_v1.VirtualServer - VirtualServerRoutes []*conf_v1.VirtualServerRoute - Warnings []string - HTTPPort int - HTTPSPort int - HTTPIPv4 string - HTTPIPv6 string - HTTPSIPv4 string - HTTPSIPv6 string + VirtualServer *conf_v1.VirtualServer + VirtualServerRoutes []*conf_v1.VirtualServerRoute + VirtualServerRouteSelectors map[string][]string + Warnings []string + HTTPPort int + HTTPSPort int + HTTPIPv4 string + HTTPIPv6 string + HTTPSIPv4 string + HTTPSIPv6 string } // NewVirtualServerConfiguration creates a VirtualServerConfiguration. -func NewVirtualServerConfiguration(vs *conf_v1.VirtualServer, vsrs []*conf_v1.VirtualServerRoute, warnings []string) *VirtualServerConfiguration { +func NewVirtualServerConfiguration(vs *conf_v1.VirtualServer, vsrs []*conf_v1.VirtualServerRoute, vsrSelectors map[string][]string, warnings []string) *VirtualServerConfiguration { return &VirtualServerConfiguration{ - VirtualServer: vs, - VirtualServerRoutes: vsrs, - Warnings: warnings, + VirtualServer: vs, + VirtualServerRoutes: vsrs, + VirtualServerRouteSelectors: vsrSelectors, + Warnings: warnings, } } @@ -270,6 +273,34 @@ func (vsc *VirtualServerConfiguration) IsEqual(resource Resource) bool { } } + // Check VirtualServerRouteSelectors maps for equality + if len(vsc.VirtualServerRouteSelectors) != len(vsConfig.VirtualServerRouteSelectors) { + return false + } + + for selector, routes := range vsc.VirtualServerRouteSelectors { + otherRoutes, exists := vsConfig.VirtualServerRouteSelectors[selector] + if !exists { + return false + } + + if len(routes) != len(otherRoutes) { + return false + } + + // Create maps for O(1) lookup to compare route slices + routeSet := make(map[string]bool) + for _, route := range routes { + routeSet[route] = true + } + + for _, otherRoute := range otherRoutes { + if !routeSet[otherRoute] { + return false + } + } + } + return true } @@ -1488,13 +1519,13 @@ func (c *Configuration) buildHostsAndResources() (newHosts map[string]Resource, for _, key := range getSortedVirtualServerKeys(c.virtualServers) { vs := c.virtualServers[key] - vsrs, warnings := c.buildVirtualServerRoutes(vs) + vsrs, vsrSelectors, warnings := c.buildVirtualServerRoutes(vs) for _, vsr := range challengesVSR { if vs.Spec.Host == vsr.Spec.Host { vsrs = append(vsrs, vsr) } } - resource := NewVirtualServerConfiguration(vs, vsrs, warnings) + resource := NewVirtualServerConfiguration(vs, vsrs, vsrSelectors, warnings) c.buildListenersForVSConfiguration(resource) @@ -1648,40 +1679,76 @@ func (c *Configuration) buildMinionConfigs(masterHost string) ([]*MinionConfigur return minionConfigs, childWarnings } -func (c *Configuration) buildVirtualServerRoutes(vs *conf_v1.VirtualServer) ([]*conf_v1.VirtualServerRoute, []string) { +func (c *Configuration) buildVirtualServerRoutes(vs *conf_v1.VirtualServer) ([]*conf_v1.VirtualServerRoute, map[string][]string, []string) { var vsrs []*conf_v1.VirtualServerRoute var warnings []string + var vsrSelectors map[string][]string for _, r := range vs.Spec.Routes { - if r.Route == "" { - continue - } + if r.Route != "" { + vsrKey := r.Route - vsrKey := r.Route + // if route is defined without a namespace, use the namespace of VirtualServer. + if !strings.Contains(r.Route, "/") { + vsrKey = fmt.Sprintf("%s/%s", vs.Namespace, r.Route) + } - // if route is defined without a namespace, use the namespace of VirtualServer. - if !strings.Contains(r.Route, "/") { - vsrKey = fmt.Sprintf("%s/%s", vs.Namespace, r.Route) - } + vsr, exists := c.virtualServerRoutes[vsrKey] - vsr, exists := c.virtualServerRoutes[vsrKey] - if !exists { - warning := fmt.Sprintf("VirtualServerRoute %s doesn't exist or invalid", vsrKey) - warnings = append(warnings, warning) - continue - } + // if route is defined + if !exists { + warning := fmt.Sprintf("VirtualServerRoute %s doesn't exist or invalid", vsrKey) + warnings = append(warnings, warning) + continue + } - err := c.virtualServerValidator.ValidateVirtualServerRouteForVirtualServer(vsr, vs.Spec.Host, r.Path) - if err != nil { - warning := fmt.Sprintf("VirtualServerRoute %s is invalid: %v", vsrKey, err) - warnings = append(warnings, warning) - continue - } + err := c.virtualServerValidator.ValidateVirtualServerRouteForVirtualServer(vsr, vs.Spec.Host, r.Path) + if err != nil { + warning := fmt.Sprintf("VirtualServerRoute %s is invalid: %v", vsrKey, err) + warnings = append(warnings, warning) + continue + } - vsrs = append(vsrs, vsr) + vsrs = append(vsrs, vsr) + } else if r.RouteSelector != nil { + if vsrSelectors == nil { + vsrSelectors = make(map[string][]string) + } + + selector := &metav1.LabelSelector{ + MatchLabels: r.RouteSelector.MatchLabels, + } + sel, err := metav1.LabelSelectorAsSelector(selector) + if err != nil { + warning := fmt.Sprintf("VirtualServerRoute LabelSelector %s is invalid: %v", selector, err) + warnings = append(warnings, warning) + continue + } + + selectorStr := sel.String() + // Initialize the selector entry regardless of whether routes match + if vsrSelectors[selectorStr] == nil { + vsrSelectors[selectorStr] = make([]string, 0) + } + + for vsrKey, vsr := range c.virtualServerRoutes { + if sel.Matches(labels.Set(vsr.Labels)) { + err := c.virtualServerValidator.ValidateVirtualServerRouteForVirtualServer(vsr, vs.Spec.Host, r.Path) + if err != nil { + warning := fmt.Sprintf("VirtualServerRoute %s is invalid: %v", vsrKey, err) + warnings = append(warnings, warning) + continue + } + vsrs = append(vsrs, vsr) + + // Add to selectors map + vsrSelectors[selectorStr] = append(vsrSelectors[selectorStr], vsrKey) + } + } + } } - return vsrs, warnings + return vsrs, vsrSelectors, warnings } // GetTransportServerMetrics returns metrics about TransportServers diff --git a/internal/k8s/configuration_test.go b/internal/k8s/configuration_test.go index 5e7558ff69..1b7db62b57 100644 --- a/internal/k8s/configuration_test.go +++ b/internal/k8s/configuration_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" nl "github.com/nginx/kubernetes-ingress/internal/logger" conf_v1 "github.com/nginx/kubernetes-ingress/pkg/apis/configuration/v1" "github.com/nginx/kubernetes-ingress/pkg/apis/configuration/validation" @@ -1221,14 +1222,19 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { Path: "/second", Route: "virtualserverroute-2", }, + { + Path: "/", + RouteSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "route"}}, + }, }) expectedChanges = []ResourceChange{ { Op: AddOrUpdate, Resource: &VirtualServerConfiguration{ - VirtualServer: vs, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1}, - Warnings: []string{"VirtualServerRoute default/virtualserverroute-2 doesn't exist or invalid"}, + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1}, + VirtualServerRouteSelectors: map[string][]string{"app=route": {}}, + Warnings: []string{"VirtualServerRoute default/virtualserverroute-2 doesn't exist or invalid"}, }, }, } @@ -1250,8 +1256,9 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { { Op: AddOrUpdate, Resource: &VirtualServerConfiguration{ - VirtualServer: vs, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2}, + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2}, + VirtualServerRouteSelectors: map[string][]string{"app=route": {}}, }, }, } @@ -1265,6 +1272,49 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { t.Errorf("AddOrUpdateVirtualServerRoute() returned unexpected result (-want +got):\n%s", diff) } + // Add VirtualServerRoute-3 with RouteSelector label + + vsr3 := &conf_v1.VirtualServerRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "virtualserverroute-3", + Labels: map[string]string{"app": "route"}, + }, + Spec: conf_v1.VirtualServerRouteSpec{ + IngressClass: "nginx", + Host: "foo.example.com", + Subroutes: []conf_v1.Route{ + { + Path: "/third", + Action: &conf_v1.Action{ + Return: &conf_v1.ActionReturn{ + Body: "vsr", + }, + }, + }, + }, + }, + } + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2, vsr3}, + VirtualServerRouteSelectors: map[string][]string{"app=route": {"default/virtualserverroute-3"}}, + }, + }, + } + expectedProblems = nil + + changes, problems = configuration.AddOrUpdateVirtualServerRoute(vsr3) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateVirtualServerRoute() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateVirtualServerRoute() returned unexpected result (-want +got):\n%s", diff) + } + // Update VirtualServerRoute-1 updatedVSR1 := vsr1.DeepCopy() @@ -1274,8 +1324,9 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { { Op: AddOrUpdate, Resource: &VirtualServerConfiguration{ - VirtualServer: vs, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{updatedVSR1, vsr2}, + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{updatedVSR1, vsr2, vsr3}, + VirtualServerRouteSelectors: map[string][]string{"app=route": {"default/virtualserverroute-3"}}, }, }, } @@ -1298,9 +1349,10 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { { Op: AddOrUpdate, Resource: &VirtualServerConfiguration{ - VirtualServer: vs, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr2}, - Warnings: []string{"VirtualServerRoute default/virtualserverroute-1 doesn't exist or invalid"}, + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr2, vsr3}, + VirtualServerRouteSelectors: map[string][]string{"app=route": {"default/virtualserverroute-3"}}, + Warnings: []string{"VirtualServerRoute default/virtualserverroute-1 doesn't exist or invalid"}, }, }, } @@ -1327,8 +1379,9 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { { Op: AddOrUpdate, Resource: &VirtualServerConfiguration{ - VirtualServer: vs, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2}, + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2, vsr3}, + VirtualServerRouteSelectors: map[string][]string{"app=route": {"default/virtualserverroute-3"}}, }, }, } @@ -1351,9 +1404,10 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { { Op: AddOrUpdate, Resource: &VirtualServerConfiguration{ - VirtualServer: vs, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr2}, - Warnings: []string{"VirtualServerRoute default/virtualserverroute-1 is invalid: spec.subroutes[0]: Invalid value: \"/\": must start with '/first'"}, + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr2, vsr3}, + VirtualServerRouteSelectors: map[string][]string{"app=route": {"default/virtualserverroute-3"}}, + Warnings: []string{"VirtualServerRoute default/virtualserverroute-1 is invalid: spec.subroutes[0]: Invalid value: \"/\": must start with '/first'"}, }, }, } @@ -1379,8 +1433,9 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { { Op: AddOrUpdate, Resource: &VirtualServerConfiguration{ - VirtualServer: vs, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2}, + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2, vsr3}, + VirtualServerRouteSelectors: map[string][]string{"app=route": {"default/virtualserverroute-3"}}, }, }, } @@ -1403,9 +1458,10 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { { Op: AddOrUpdate, Resource: &VirtualServerConfiguration{ - VirtualServer: vs, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1}, - Warnings: []string{"VirtualServerRoute default/virtualserverroute-2 is invalid: spec.host: Invalid value: \"bar.example.com\": must be equal to 'foo.example.com'"}, + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr3}, + VirtualServerRouteSelectors: map[string][]string{"app=route": {"default/virtualserverroute-3"}}, + Warnings: []string{"VirtualServerRoute default/virtualserverroute-2 is invalid: spec.host: Invalid value: \"bar.example.com\": must be equal to 'foo.example.com'"}, }, }, } @@ -1434,9 +1490,10 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { { Op: AddOrUpdate, Resource: &VirtualServerConfiguration{ - VirtualServer: updatedVS, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{updatedVSR2}, - Warnings: []string{"VirtualServerRoute default/virtualserverroute-1 is invalid: spec.host: Invalid value: \"foo.example.com\": must be equal to 'bar.example.com'"}, + VirtualServer: updatedVS, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{updatedVSR2}, + VirtualServerRouteSelectors: map[string][]string{"app=route": {}}, + Warnings: []string{"VirtualServerRoute default/virtualserverroute-1 is invalid: spec.host: Invalid value: \"foo.example.com\": must be equal to 'bar.example.com'", "VirtualServerRoute default/virtualserverroute-3 is invalid: spec.host: Invalid value: \"foo.example.com\": must be equal to 'bar.example.com'"}, }, }, } @@ -1462,9 +1519,10 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { { Op: AddOrUpdate, Resource: &VirtualServerConfiguration{ - VirtualServer: vs, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1}, - Warnings: []string{"VirtualServerRoute default/virtualserverroute-2 is invalid: spec.host: Invalid value: \"bar.example.com\": must be equal to 'foo.example.com'"}, + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr3}, + VirtualServerRouteSelectors: map[string][]string{"app=route": {"default/virtualserverroute-3"}}, + Warnings: []string{"VirtualServerRoute default/virtualserverroute-2 is invalid: spec.host: Invalid value: \"bar.example.com\": must be equal to 'foo.example.com'"}, }, }, } @@ -1490,8 +1548,9 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { { Op: AddOrUpdate, Resource: &VirtualServerConfiguration{ - VirtualServer: vs, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2}, + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2, vsr3}, + VirtualServerRouteSelectors: map[string][]string{"app=route": {"default/virtualserverroute-3"}}, }, }, } @@ -1511,9 +1570,10 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { { Op: AddOrUpdate, Resource: &VirtualServerConfiguration{ - VirtualServer: vs, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr2}, - Warnings: []string{"VirtualServerRoute default/virtualserverroute-1 doesn't exist or invalid"}, + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr2, vsr3}, + VirtualServerRouteSelectors: map[string][]string{"app=route": {"default/virtualserverroute-3"}}, + Warnings: []string{"VirtualServerRoute default/virtualserverroute-1 doesn't exist or invalid"}, }, }, } @@ -1533,9 +1593,10 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { { Op: Delete, Resource: &VirtualServerConfiguration{ - VirtualServer: vs, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr2}, - Warnings: []string{"VirtualServerRoute default/virtualserverroute-1 doesn't exist or invalid"}, + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr2, vsr3}, + VirtualServerRouteSelectors: map[string][]string{"app=route": {"default/virtualserverroute-3"}}, + Warnings: []string{"VirtualServerRoute default/virtualserverroute-1 doesn't exist or invalid"}, }, }, } @@ -3788,6 +3849,30 @@ func createTestVirtualServerRoute(name string, host string, path string) *conf_v } } +func createTestVirtualServerRouteWithLabels(name string, path string, labels map[string]string) *conf_v1.VirtualServerRoute { + return &conf_v1.VirtualServerRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: name, + Labels: labels, + }, + Spec: conf_v1.VirtualServerRouteSpec{ + IngressClass: "nginx", + Host: "foo.example.com", + Subroutes: []conf_v1.Route{ + { + Path: path, + Action: &conf_v1.Action{ + Return: &conf_v1.ActionReturn{ + Body: "vsr", + }, + }, + }, + }, + }, + } +} + func createTestChallengeVirtualServerRoute(name string, host string, path string) *conf_v1.VirtualServerRoute { return &conf_v1.VirtualServerRoute{ ObjectMeta: metav1.ObjectMeta{ @@ -4496,6 +4581,7 @@ func TestIsEqualForIngressConfigurations(t *testing.T) { } } +// TODO: vsr route selector test func TestIsEqualForVirtualServers(t *testing.T) { t.Parallel() vs := createTestVirtualServerWithRoutes( @@ -4522,26 +4608,26 @@ func TestIsEqualForVirtualServers(t *testing.T) { msg string }{ { - vsConfig1: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{vsr}, []string{}), - vsConfig2: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{vsr}, []string{}), + vsConfig1: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{vsr}, nil, []string{}), + vsConfig2: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{vsr}, nil, []string{}), expected: true, msg: "equal virtual servers", }, { - vsConfig1: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{vsr}, []string{}), - vsConfig2: NewVirtualServerConfiguration(vsWithUpdatedGen, []*conf_v1.VirtualServerRoute{vsr}, []string{}), + vsConfig1: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{vsr}, nil, []string{}), + vsConfig2: NewVirtualServerConfiguration(vsWithUpdatedGen, []*conf_v1.VirtualServerRoute{vsr}, nil, []string{}), expected: false, msg: "virtual servers with different generation", }, { - vsConfig1: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{vsr}, []string{}), - vsConfig2: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{}, []string{}), + vsConfig1: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{vsr}, nil, []string{}), + vsConfig2: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{}, nil, []string{}), expected: false, msg: "virtual servers with different number of virtual server routes", }, { - vsConfig1: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{vsr}, []string{}), - vsConfig2: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{vsrWithUpdatedGen}, []string{}), + vsConfig1: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{vsr}, nil, []string{}), + vsConfig2: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{vsrWithUpdatedGen}, nil, []string{}), expected: false, msg: "virtual servers with virtual server routes with different generation", }, @@ -4558,7 +4644,7 @@ func TestIsEqualForVirtualServers(t *testing.T) { func TestIsEqualForDifferentResources(t *testing.T) { t.Parallel() ingConfig := NewRegularIngressConfiguration(createTestIngress("ingress", "foo.example.com")) - vsConfig := NewVirtualServerConfiguration(createTestVirtualServer("virtualserver", "bar.example.com"), []*conf_v1.VirtualServerRoute{}, []string{}) + vsConfig := NewVirtualServerConfiguration(createTestVirtualServer("virtualserver", "bar.example.com"), []*conf_v1.VirtualServerRoute{}, nil, []string{}) result := ingConfig.IsEqual(vsConfig) if result != false { @@ -4894,3 +4980,241 @@ func TestTransportServerListenerHostCollisions(t *testing.T) { t.Errorf("AddOrUpdateTransportServer(ts6) returned problems %v", problems) } } + +func TestMatchVSwithVSRusingSelector(t *testing.T) { + t.Parallel() + + configuration := createTestConfiguration() + + // Add VirtualServerRoute + + labels := make(map[string]string) + vsr := createTestVirtualServerRouteWithLabels("virtualserverroute", "/first", labels) + + var expectedChanges []ResourceChange + expectedProblems := []ConfigurationProblem{ + { + Object: vsr, + Reason: "NoVirtualServerFound", + Message: "VirtualServer is invalid or doesn't exist", + }, + } + + // adding VSR to the configuration; no VS exist at this stage, chance we get problems + // + // if we don't get it right now we call t.Fatal as there is no + // point to continue the test - preconditions are not setup correctly. + changes, problems := configuration.AddOrUpdateVirtualServerRoute(vsr) + if !cmp.Equal(expectedChanges, changes, cmpopts.IgnoreFields(ConfigurationProblem{}, "Message")) { + t.Fatal(cmp.Diff(expectedChanges, changes)) + } + if !cmp.Equal(expectedProblems, problems, cmpopts.IgnoreFields(ConfigurationProblem{}, "Message")) { + t.Fatal(cmp.Diff(expectedProblems, problems)) + } + + // Add VS with VRS with the RouteSelector (LabelSelector) + routes := []conf_v1.Route{ + { + Path: "/first", + Route: "virtualserverroute", + }, + { + Path: "/", + RouteSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "route"}}, + }, + } + + vs := createTestVirtualServerWithRoutes("virtualserver", "foo.example.com", routes) + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr}, + VirtualServerRouteSelectors: map[string][]string{"app=route": {}}, + }, + }, + } + expectedProblems = nil + + changes, problems = configuration.AddOrUpdateVirtualServer(vs) + if !cmp.Equal(expectedChanges, changes) { + t.Error(cmp.Diff(expectedChanges, changes)) + } + if !cmp.Equal(expectedProblems, problems) { + t.Error(cmp.Diff(expectedProblems, problems)) + } +} + +func TestAddVirtualServerWithVirtualServerRoutesVSR(t *testing.T) { + configuration := createTestConfiguration() + + // Add VirtualServerRoute-1 + + labels := make(map[string]string) + vsr1 := createTestVirtualServerRouteWithLabels("virtualserverroute-1", "/first", labels) + + var expectedChanges []ResourceChange + expectedProblems := []ConfigurationProblem{ + { + Object: vsr1, + Reason: "NoVirtualServerFound", + Message: "VirtualServer is invalid or doesn't exist", + }, + } + + changes, problems := configuration.AddOrUpdateVirtualServerRoute(vsr1) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateVirtualServerRoute() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateVirtualServerRoute() returned unexpected result (-want +got):\n%s", diff) + } + + // Add VirtualServer + + vs := createTestVirtualServerWithRoutes( + "virtualserver", + "foo.example.com", + []conf_v1.Route{ + { + Path: "/first", + Route: "virtualserverroute-1", + }, + { + Path: "/second", + Route: "virtualserverroute-2", + }, + { + Path: "/", + RouteSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "route"}}, + }, + }) + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1}, + VirtualServerRouteSelectors: map[string][]string{"app=route": {}}, + Warnings: []string{"VirtualServerRoute default/virtualserverroute-2 doesn't exist or invalid"}, + }, + }, + } + expectedProblems = nil + + changes, problems = configuration.AddOrUpdateVirtualServer(vs) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateVirtualServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateVirtualServer() returned unexpected result (-want +got):\n%s", diff) + } + + // Add VirtualServerRoute-2 + + vsr2 := createTestVirtualServerRouteWithLabels("virtualserverroute-2", "/second", nil) + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2}, + VirtualServerRouteSelectors: map[string][]string{"app=route": {}}, + }, + }, + } + expectedProblems = nil + + changes, problems = configuration.AddOrUpdateVirtualServerRoute(vsr2) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateVirtualServerRoute() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateVirtualServerRoute() returned unexpected result (-want +got):\n%s", diff) + } + + // Add VirtualServerRoute-3 with RouteSelector labels + + vsr3 := createTestVirtualServerRouteWithLabels("virtualserverroute-3", "/third", map[string]string{"app": "route"}) + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2, vsr3}, + VirtualServerRouteSelectors: map[string][]string{"app=route": {"default/virtualserverroute-3"}}, + }, + }, + } + expectedProblems = nil + + changes, problems = configuration.AddOrUpdateVirtualServerRoute(vsr3) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateVirtualServerRoute() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateVirtualServerRoute() returned unexpected result (-want +got):\n%s", diff) + } +} + +func TestIsEqualForVirtualServersVSR(t *testing.T) { + t.Parallel() + vs := createTestVirtualServerWithRoutes( + "virtualserver", + "foo.example.com", + []conf_v1.Route{ + { + Path: "/", + Route: "virtualserverroute", + }, + }) + vsr := createTestVirtualServerRouteWithLabels("virtualserverroute", "/", nil) + + vsWithUpdatedGen := vs.DeepCopy() + vsWithUpdatedGen.Generation++ + + vsrWithUpdatedGen := vsr.DeepCopy() + vsrWithUpdatedGen.Generation++ + + tests := []struct { + vsConfig1 *VirtualServerConfiguration + vsConfig2 *VirtualServerConfiguration + expected bool + msg string + }{ + { + vsConfig1: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{vsr}, nil, []string{}), + vsConfig2: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{vsr}, nil, []string{}), + expected: true, + msg: "equal virtual servers", + }, + { + vsConfig1: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{vsr}, nil, []string{}), + vsConfig2: NewVirtualServerConfiguration(vsWithUpdatedGen, []*conf_v1.VirtualServerRoute{vsr}, nil, []string{}), + expected: false, + msg: "virtual servers with different generation", + }, + { + vsConfig1: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{vsr}, nil, []string{}), + vsConfig2: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{}, nil, []string{}), + expected: false, + msg: "virtual servers with different number of virtual server routes", + }, + { + vsConfig1: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{vsr}, nil, []string{}), + vsConfig2: NewVirtualServerConfiguration(vs, []*conf_v1.VirtualServerRoute{vsrWithUpdatedGen}, nil, []string{}), + expected: false, + msg: "virtual servers with virtual server routes with different generation", + }, + } + + for _, test := range tests { + result := test.vsConfig1.IsEqual(test.vsConfig2) + if result != test.expected { + t.Errorf("IsEqual() returned %v but expected %v for the case of %s", result, test.expected, test.msg) + } + } +} diff --git a/internal/k8s/controller.go b/internal/k8s/controller.go index f2b13cbdf0..8d5895bbe2 100644 --- a/internal/k8s/controller.go +++ b/internal/k8s/controller.go @@ -864,7 +864,7 @@ func (lbc *LoadBalancerController) createExtendedResources(resources []Resource) switch impl := r.(type) { case *VirtualServerConfiguration: vs := impl.VirtualServer - vsEx := lbc.createVirtualServerEx(vs, impl.VirtualServerRoutes) + vsEx := lbc.createVirtualServerEx(vs, impl.VirtualServerRoutes, impl.VirtualServerRouteSelectors) result.VirtualServerExes = append(result.VirtualServerExes, vsEx) case *IngressConfiguration: @@ -1281,7 +1281,7 @@ func (lbc *LoadBalancerController) processChanges(changes []ResourceChange) { if c.Op == AddOrUpdate { switch impl := c.Resource.(type) { case *VirtualServerConfiguration: - vsEx := lbc.createVirtualServerEx(impl.VirtualServer, impl.VirtualServerRoutes) + vsEx := lbc.createVirtualServerEx(impl.VirtualServer, impl.VirtualServerRoutes, impl.VirtualServerRouteSelectors) warnings, addOrUpdateErr := lbc.configurator.AddOrUpdateVirtualServer(vsEx) lbc.updateVirtualServerStatusAndEvents(impl, warnings, addOrUpdateErr) @@ -2361,13 +2361,17 @@ func (lbc *LoadBalancerController) createIngressEx(ing *networking.Ingress, vali return ingEx } -func (lbc *LoadBalancerController) createVirtualServerEx(virtualServer *conf_v1.VirtualServer, virtualServerRoutes []*conf_v1.VirtualServerRoute) *configs.VirtualServerEx { +func (lbc *LoadBalancerController) createVirtualServerEx(virtualServer *conf_v1.VirtualServer, virtualServerRoutes []*conf_v1.VirtualServerRoute, selectorMap map[string][]string) *configs.VirtualServerEx { virtualServerEx := configs.VirtualServerEx{ - VirtualServer: virtualServer, - SecretRefs: make(map[string]*secrets.SecretReference), - ApPolRefs: make(map[string]*unstructured.Unstructured), - LogConfRefs: make(map[string]*unstructured.Unstructured), - DosProtectedEx: make(map[string]*configs.DosEx), + VirtualServer: virtualServer, + VirtualServerSelectorRoutes: selectorMap, + SecretRefs: make(map[string]*secrets.SecretReference), + ApPolRefs: make(map[string]*unstructured.Unstructured), + LogConfRefs: make(map[string]*unstructured.Unstructured), + DosProtectedEx: make(map[string]*configs.DosEx), + } + if lbc.configurator != nil && lbc.configurator.CfgParams != nil { + virtualServerEx.ZoneSync = lbc.configurator.CfgParams.ZoneSync.Enable } if lbc.configurator != nil && lbc.configurator.CfgParams != nil { virtualServerEx.ZoneSync = lbc.configurator.CfgParams.ZoneSync.Enable @@ -3684,7 +3688,7 @@ func (lbc *LoadBalancerController) haltIfVSRConfigInvalid(vsrNew *conf_v1.Virtua if c.Op == AddOrUpdate { switch impl := c.Resource.(type) { case *VirtualServerConfiguration: - vsEx = lbc.createVirtualServerEx(impl.VirtualServer, impl.VirtualServerRoutes) + vsEx = lbc.createVirtualServerEx(impl.VirtualServer, impl.VirtualServerRoutes, impl.VirtualServerRouteSelectors) lbc.updateVirtualServerStatusAndEvents(impl, configs.Warnings{}, nil) } } diff --git a/internal/k8s/controller_test.go b/internal/k8s/controller_test.go index 93b8eacdc2..3bbcabca2b 100644 --- a/internal/k8s/controller_test.go +++ b/internal/k8s/controller_test.go @@ -3588,7 +3588,7 @@ func TestCreateVirtualServerExWithZoneSync(t *testing.T) { for _, tc := range testCases { lbc := NewLoadBalancerController(tc.input) - vsEx := lbc.createVirtualServerEx(&tc.vs, tc.vsr) + vsEx := lbc.createVirtualServerEx(&tc.vs, tc.vsr, nil) if reflect.DeepEqual(vsEx, tc.expected) { t.Fatalf("Expected %v, but got %v", tc.expected, vsEx) } diff --git a/internal/k8s/global_configuration.go b/internal/k8s/global_configuration.go index 4e87ca1d70..3fd7c54479 100644 --- a/internal/k8s/global_configuration.go +++ b/internal/k8s/global_configuration.go @@ -124,7 +124,7 @@ func (lbc *LoadBalancerController) processChangesFromGlobalConfiguration(changes switch impl := c.Resource.(type) { case *VirtualServerConfiguration: if c.Op == AddOrUpdate { - vsEx := lbc.createVirtualServerEx(impl.VirtualServer, impl.VirtualServerRoutes) + vsEx := lbc.createVirtualServerEx(impl.VirtualServer, impl.VirtualServerRoutes, impl.VirtualServerRouteSelectors) updatedVSExes = append(updatedVSExes, vsEx) updatedResources = append(updatedResources, impl) diff --git a/internal/k8s/handlers.go b/internal/k8s/handlers.go index 3bcfcc24ae..a410b6b381 100644 --- a/internal/k8s/handlers.go +++ b/internal/k8s/handlers.go @@ -215,7 +215,7 @@ func createVirtualServerRouteHandlers(lbc *LoadBalancerController) cache.Resourc } - if !reflect.DeepEqual(oldVsr.Spec, curVsr.Spec) { + if !reflect.DeepEqual(oldVsr.Spec, curVsr.Spec) || !reflect.DeepEqual(oldVsr.Labels, curVsr.Labels) { nl.Debugf(lbc.Logger, "VirtualServerRoute %v changed, syncing", curVsr.Name) lbc.AddSyncQueue(curVsr) } diff --git a/pkg/apis/configuration/v1/types.go b/pkg/apis/configuration/v1/types.go index 90376fe69b..71d525f86e 100644 --- a/pkg/apis/configuration/v1/types.go +++ b/pkg/apis/configuration/v1/types.go @@ -275,6 +275,8 @@ type Route struct { Policies []PolicyReference `json:"policies"` // The name of a VirtualServerRoute resource that defines this route. If the VirtualServerRoute belongs to a different namespace than the VirtualServer, you need to include the namespace. For example, tea-namespace/tea. Route string `json:"route"` + // The RouteSelector allows selecting VirtualServerRoute resources using label selectors. + RouteSelector *metav1.LabelSelector `json:"routeSelector"` // The default action to perform for a request. Action *Action `json:"action"` // The default splits configuration for traffic splitting. Must include at least 2 splits. diff --git a/pkg/apis/configuration/v1/zz_generated.deepcopy.go b/pkg/apis/configuration/v1/zz_generated.deepcopy.go index e75bfc28e0..d1df3b7186 100644 --- a/pkg/apis/configuration/v1/zz_generated.deepcopy.go +++ b/pkg/apis/configuration/v1/zz_generated.deepcopy.go @@ -6,6 +6,7 @@ package v1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" intstr "k8s.io/apimachinery/pkg/util/intstr" ) @@ -1077,6 +1078,11 @@ func (in *Route) DeepCopyInto(out *Route) { *out = make([]PolicyReference, len(*in)) copy(*out, *in) } + if in.RouteSelector != nil { + in, out := &in.RouteSelector, &out.RouteSelector + *out = new(metav1.LabelSelector) + (*in).DeepCopyInto(*out) + } if in.Action != nil { in, out := &in.Action, &out.Action *out = new(Action) diff --git a/pkg/apis/configuration/validation/virtualserver.go b/pkg/apis/configuration/validation/virtualserver.go index e5edfff294..d5eb495aac 100644 --- a/pkg/apis/configuration/validation/virtualserver.go +++ b/pkg/apis/configuration/validation/virtualserver.go @@ -828,6 +828,11 @@ func (vsv *VirtualServerValidator) validateRoute(route v1.Route, fieldPath *fiel } } + if route.RouteSelector != nil { + // TODO: validate RouteSelector + fieldCount++ + } + for i, e := range route.ErrorPages { allErrs = append(allErrs, vsv.validateErrorPage(e, fieldPath.Child("errorPages").Index(i))...) } @@ -842,7 +847,7 @@ func (vsv *VirtualServerValidator) validateRoute(route v1.Route, fieldPath *fiel } if fieldCount != 1 { - msg := "must specify exactly one of `action`, `splits` or `route`" + msg := "must specify exactly one of `action`, `splits`, `route` or `routeSelector`," if isRouteFieldForbidden || len(route.Matches) > 0 { msg = "must specify exactly one of `action` or `splits`" }