diff --git a/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml b/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml index c6dffbdf39..ea28078e18 100644 --- a/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml +++ b/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml @@ -438,6 +438,55 @@ spec: type: array route: type: string + routeSelector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + 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: items: description: Split defines a split. diff --git a/config/crd/bases/k8s.nginx.org_virtualservers.yaml b/config/crd/bases/k8s.nginx.org_virtualservers.yaml index 49adfc3d41..0e57e88948 100644 --- a/config/crd/bases/k8s.nginx.org_virtualservers.yaml +++ b/config/crd/bases/k8s.nginx.org_virtualservers.yaml @@ -500,6 +500,55 @@ spec: type: array route: type: string + routeSelector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + 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: items: description: Split defines a split. diff --git a/deploy/crds.yaml b/deploy/crds.yaml index 6e7844db4b..986622e935 100644 --- a/deploy/crds.yaml +++ b/deploy/crds.yaml @@ -1338,6 +1338,55 @@ spec: type: array route: type: string + routeSelector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + 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: items: description: Split defines a split. @@ -2129,6 +2178,55 @@ spec: type: array route: type: string + routeSelector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + 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: items: description: Split defines a split. 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 9518765e7b..5a6091da77 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -12,6 +12,8 @@ import ( "strconv" "strings" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/nginxinc/kubernetes-ingress/internal/configs/version2" "github.com/nginxinc/kubernetes-ingress/internal/k8s/secrets" nl "github.com/nginxinc/kubernetes-ingress/internal/logger" @@ -85,23 +87,24 @@ 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 + 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 } func (vsx *VirtualServerEx) String() string { @@ -398,6 +401,7 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig( apResources *appProtectResourcesForVS, dosResources map[string]*appProtectDosResource, ) (version2.VirtualServerConfig, Warnings) { + // l := nl.LoggerFromContext(vsc.cfgParams.Context) vsc.clearWarnings() useCustomListeners := false @@ -568,6 +572,50 @@ 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] + //nl.Infof(l, "VirtualServerRoutes: %v", vsEx.VirtualServerRoutes) + // + //nl.Infof(l, "VirtualServerSelectorRoutes: %v", vsEx.VirtualServerSelectorRoutes) + // + //nl.Infof(l, "vsrKeys: %v", vsrKeys) + // + //nl.Infof(l, "RouteSelector: %v", selector) + + // 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 { + // nl.Infof(l, "Route Policies: %v", r.Policies) + for _, name := range vsrKeys { + // nl.Infof(l, "Adding policy to VSR $v: %v", name, r.Policies) + vsrPoliciesFromVs[name] = r.Policies + } + } + continue } @@ -681,6 +729,7 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig( } errorPageLocations = append(errorPageLocations, generateErrorPageLocations(errorPages.index, errorPages.pages)...) vsrNamespaceName := fmt.Sprintf("%v/%v", vsr.Namespace, vsr.Name) + // glog.Infof("vsrNamespaceName: %v", vsrNamespaceName) // use the VirtualServer error pages if the route does not define any if r.ErrorPages == nil { if vsErrorPages, ok := vsrErrorPagesFromVs[vsrNamespaceName]; ok { diff --git a/internal/k8s/configuration.go b/internal/k8s/configuration.go index 139bf3f7a4..ef0122efcc 100644 --- a/internal/k8s/configuration.go +++ b/internal/k8s/configuration.go @@ -2,11 +2,15 @@ package k8s import ( "fmt" + "log/slog" "reflect" "sort" "strings" "sync" + nl "github.com/nginxinc/kubernetes-ingress/internal/logger" + "k8s.io/apimachinery/pkg/labels" + "github.com/nginxinc/kubernetes-ingress/internal/configs" conf_v1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1" "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/validation" @@ -205,23 +209,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, routeSelectors map[string][]string, warnings []string) *VirtualServerConfiguration { return &VirtualServerConfiguration{ - VirtualServer: vs, - VirtualServerRoutes: vsrs, - Warnings: warnings, + VirtualServer: vs, + VirtualServerRoutes: vsrs, + VirtualServerRouteSelectors: routeSelectors, + Warnings: warnings, } } @@ -268,7 +274,8 @@ func (vsc *VirtualServerConfiguration) IsEqual(resource Resource) bool { } } - return true + // TODO: check all values of the map + return len(vsc.VirtualServerRouteSelectors) != len(vsConfig.VirtualServerRouteSelectors) } // TransportServerConfiguration holds a TransportServer resource. @@ -342,6 +349,10 @@ type TransportServerMetrics struct { // The IC needs to ensure that at any point in time the NGINX config on the filesystem reflects the state // of the objects in the Configuration. type Configuration struct { + // Context context.Context + + logger *slog.Logger + hosts map[string]Resource listenerHosts map[listenerHostKey]*TransportServerConfiguration listenerMap map[string]conf_v1.Listener @@ -384,6 +395,8 @@ type Configuration struct { // NewConfiguration creates a new Configuration. func NewConfiguration( + // ctx context.Context, + logger *slog.Logger, hasCorrectIngressClass func(interface{}) bool, isPlus bool, appProtectEnabled bool, @@ -397,7 +410,11 @@ func NewConfiguration( isCertManagerEnabled bool, isIPV6Disabled bool, ) *Configuration { + // l := nl.LoggerFromContext(ctx) + return &Configuration{ + // Context: ctx, + logger: logger, hosts: make(map[string]Resource), listenerHosts: make(map[listenerHostKey]*TransportServerConfiguration), ingresses: make(map[string]*networking.Ingress), @@ -424,6 +441,7 @@ func NewConfiguration( snippetsEnabled: snippetsEnabled, isCertManagerEnabled: isCertManagerEnabled, isIPV6Disabled: isIPV6Disabled, + // logger: l, } } @@ -570,6 +588,7 @@ func (c *Configuration) AddOrUpdateVirtualServerRoute(vsr *conf_v1.VirtualServer if !c.hasCorrectIngressClass(vsr) { delete(c.virtualServerRoutes, key) } else { + nl.Debugf(c.logger, "labels: %v", vsr.ObjectMeta.Labels) validationError = c.virtualServerValidator.ValidateVirtualServerRoute(vsr) if validationError != nil { delete(c.virtualServerRoutes, key) @@ -1416,6 +1435,7 @@ func squashResourceChanges(changes []ResourceChange) []ResourceChange { } func (c *Configuration) buildHostsAndResources() (newHosts map[string]Resource, newResources map[string]Resource) { + // l := nl.LoggerFromContext(c.Context) newHosts = make(map[string]Resource) newResources = make(map[string]Resource) var challengesVSR []*conf_v1.VirtualServerRoute @@ -1471,13 +1491,16 @@ func (c *Configuration) buildHostsAndResources() (newHosts map[string]Resource, for _, key := range getSortedVirtualServerKeys(c.virtualServers) { vs := c.virtualServers[key] - vsrs, warnings := c.buildVirtualServerRoutes(vs) + vsrs, routeSelectorMap, 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, routeSelectorMap, warnings) + + // todo - uncomment (Jakub) + // nl.Infof(c.logger, "resource: %v", resource) c.buildListenersForVSConfiguration(resource) @@ -1631,40 +1654,78 @@ 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) { + // l := nl.LoggerFromContext(c.Context) var vsrs []*conf_v1.VirtualServerRoute + var routeSelectorMap map[string][]string var warnings []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) + } else if r.RouteSelector != nil { + selector := &metav1.LabelSelector{ + MatchLabels: r.RouteSelector.MatchLabels, + } + sel, err := metav1.LabelSelectorAsSelector(selector) + + selectorKey := sel.String() - vsrs = append(vsrs, vsr) + // routeSelectorMap[selectorKey] := []*conf_v1.VirtualServerRoute{} + + if err != nil { + warning := fmt.Sprintf("VirtualServerRoute LabelSelector %s is invalid: %v", selector, err) + warnings = append(warnings, warning) + continue + } + for vsrKey, vsr := range c.virtualServerRoutes { + if sel.Matches(labels.Set(vsr.ObjectMeta.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 + } + + // todo (Jakub) + // nl.Infof(c.logger, "VirtualServerRoute %s found for label selector %v", vsrKey, selector) + + if routeSelectorMap == nil { + routeSelectorMap = make(map[string][]string) + } + routeSelectorMap[selectorKey] = append(routeSelectorMap[selectorKey], vsrKey) + + vsrs = append(vsrs, vsr) + } + } + + } } - return vsrs, warnings + return vsrs, routeSelectorMap, warnings } // GetTransportServerMetrics returns metrics about TransportServers diff --git a/internal/k8s/configuration_test.go b/internal/k8s/configuration_test.go index b8d0289f09..a7a7ebc05f 100644 --- a/internal/k8s/configuration_test.go +++ b/internal/k8s/configuration_test.go @@ -27,6 +27,7 @@ func createTestConfiguration() *Configuration { snippetsEnabled := true isIPV6Disabled := false return NewConfiguration( + lbc.Logger, lbc.HasCorrectIngressClass, isPlus, appProtectEnabled, @@ -1182,12 +1183,14 @@ func TestDeleteNonExistingVirtualServer(t *testing.T) { } } +// TODO: vsr route selector test func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { configuration := createTestConfiguration() // Add VirtualServerRoute-1 - vsr1 := createTestVirtualServerRoute("virtualserverroute-1", "foo.example.com", "/first") + labels := make(map[string]string) + vsr1 := createTestVirtualServerRoute("virtualserverroute-1", "foo.example.com", "/first", labels) var expectedChanges []ResourceChange expectedProblems := []ConfigurationProblem{ { @@ -1219,6 +1222,10 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { Path: "/second", Route: "virtualserverroute-2", }, + { + Path: "/", + RouteSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "route"}}, + }, }) expectedChanges = []ResourceChange{ { @@ -1240,10 +1247,10 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { t.Errorf("AddOrUpdateVirtualServer() returned unexpected result (-want +got):\n%s", diff) } - vsr2 := createTestVirtualServerRoute("virtualserverroute-2", "foo.example.com", "/second") - // Add VirtualServerRoute-2 + vsr2 := createTestVirtualServerRoute("virtualserverroute-2", "foo.example.com", "/second", nil) + expectedChanges = []ResourceChange{ { Op: AddOrUpdate, @@ -1287,6 +1294,29 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { t.Errorf("AddOrUpdateVirtualServerRoute() returned unexpected result (-want +got):\n%s", diff) } + // Add VirtualServerRoute-3 and VirtualServerRoute-4 with selectors + + vsr3 := createTestVirtualServerRoute("virtualserverroute-3", "foo.example.com", "/third", map[string]string{"app": "route"}) + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{updatedVSR1, 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) + } + // Make VirtualServerRoute-1 invalid invalidVSR1 := updatedVSR1.DeepCopy() @@ -1297,7 +1327,7 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { Op: AddOrUpdate, Resource: &VirtualServerConfiguration{ VirtualServer: vs, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr2}, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr2, vsr3}, Warnings: []string{"VirtualServerRoute default/virtualserverroute-1 doesn't exist or invalid"}, }, }, @@ -1326,7 +1356,7 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { Op: AddOrUpdate, Resource: &VirtualServerConfiguration{ VirtualServer: vs, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2}, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2, vsr3}, }, }, } @@ -1350,7 +1380,7 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { Op: AddOrUpdate, Resource: &VirtualServerConfiguration{ VirtualServer: vs, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr2}, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr2, vsr3}, Warnings: []string{"VirtualServerRoute default/virtualserverroute-1 is invalid: spec.subroutes[0]: Invalid value: \"/\": must start with '/first'"}, }, }, @@ -1378,7 +1408,7 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { Op: AddOrUpdate, Resource: &VirtualServerConfiguration{ VirtualServer: vs, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2}, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2, vsr3}, }, }, } @@ -1402,7 +1432,7 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { Op: AddOrUpdate, Resource: &VirtualServerConfiguration{ VirtualServer: vs, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1}, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr3}, Warnings: []string{"VirtualServerRoute default/virtualserverroute-2 is invalid: spec.host: Invalid value: \"bar.example.com\": must be equal to 'foo.example.com'"}, }, }, @@ -1461,7 +1491,7 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { Op: AddOrUpdate, Resource: &VirtualServerConfiguration{ VirtualServer: vs, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1}, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr3}, Warnings: []string{"VirtualServerRoute default/virtualserverroute-2 is invalid: spec.host: Invalid value: \"bar.example.com\": must be equal to 'foo.example.com'"}, }, }, @@ -1489,7 +1519,7 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { Op: AddOrUpdate, Resource: &VirtualServerConfiguration{ VirtualServer: vs, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2}, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2, vsr3}, }, }, } @@ -1510,7 +1540,7 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { Op: AddOrUpdate, Resource: &VirtualServerConfiguration{ VirtualServer: vs, - VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr2}, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr2, vsr3}, Warnings: []string{"VirtualServerRoute default/virtualserverroute-1 doesn't exist or invalid"}, }, }, @@ -1570,7 +1600,7 @@ func TestAddVirtualServerWithVirtualServerRoutes(t *testing.T) { func TestAddInvalidVirtualServerRoute(t *testing.T) { configuration := createTestConfiguration() - vsr := createTestVirtualServerRoute("virtualserverroute", "", "/") + vsr := createTestVirtualServerRoute("virtualserverroute", "", "/", nil) var expectedChanges []ResourceChange expectedProblems := []ConfigurationProblem{ @@ -1594,7 +1624,8 @@ func TestAddInvalidVirtualServerRoute(t *testing.T) { func TestAddVirtualServerWithIncorrectClass(t *testing.T) { configuration := createTestConfiguration() - vsr := createTestVirtualServerRoute("virtualserver", "foo.example.com", "/") + labels := make(map[string]string) + vsr := createTestVirtualServerRoute("virtualserver", "foo.example.com", "/", labels) vsr.Spec.IngressClass = "someproxy" var expectedChanges []ResourceChange @@ -1791,627 +1822,90 @@ func TestHostCollisions(t *testing.T) { t.Errorf("DeleteIngress() returned unexpected result (-want +got):\n%s", diff) } - // Delete master Ingress - - expectedChanges = []ResourceChange{ - { - Op: Delete, - Resource: &IngressConfiguration{ - Ingress: masterIng, - IsMaster: true, - ValidHosts: map[string]bool{"foo.example.com": true}, - ChildWarnings: map[string][]string{}, - }, - }, - { - Op: AddOrUpdate, - Resource: &IngressConfiguration{ - Ingress: regularIng, - ValidHosts: map[string]bool{"foo.example.com": true, "bar.example.com": true}, - ChildWarnings: map[string][]string{}, - }, - }, - } - expectedProblems = nil - - changes, problems = configuration.DeleteIngress("default/master-ingress") - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("DeleteIngress() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("DeleteIngress() returned unexpected result (-want +got):\n%s", diff) - } - - // Delete regular Ingress - - expectedChanges = []ResourceChange{ - { - Op: Delete, - Resource: &IngressConfiguration{ - Ingress: regularIng, - ValidHosts: map[string]bool{"foo.example.com": true, "bar.example.com": true}, - ChildWarnings: map[string][]string{}, - }, - }, - { - Op: AddOrUpdate, - Resource: &VirtualServerConfiguration{ - VirtualServer: vs, - }, - }, - } - expectedProblems = nil - - changes, problems = configuration.DeleteIngress("default/regular-ingress") - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("DeleteIngress() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("DeleteIngress() returned unexpected result (-want +got):\n%s", diff) - } - - // Delete VirtualServer - - expectedChanges = []ResourceChange{ - { - Op: Delete, - Resource: &VirtualServerConfiguration{ - VirtualServer: vs, - }, - }, - { - Op: AddOrUpdate, - Resource: &TransportServerConfiguration{ - ListenerPort: 0, - TransportServer: ts, - }, - }, - } - expectedProblems = nil - - changes, problems = configuration.DeleteVirtualServer("default/virtualserver") - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("DeleteIngress() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("DeleteIngress() returned unexpected result (-want +got):\n%s", diff) - } -} - -func TestAddTransportServer(t *testing.T) { - configuration := createTestConfiguration() - - listeners := []conf_v1.Listener{ - { - Name: "tcp-7777", - Port: 7777, - Protocol: "TCP", - }, - } - - addOrUpdateGlobalConfiguration(t, configuration, listeners, noChanges, noProblems) - - ts := createTestTransportServer("transportserver", "tcp-7777", "TCP") - - // no problems are expected for all cases - var expectedProblems []ConfigurationProblem - var expectedChanges []ResourceChange - - // Add TransportServer - - expectedChanges = []ResourceChange{ - { - Op: AddOrUpdate, - Resource: &TransportServerConfiguration{ - ListenerPort: 7777, - TransportServer: ts, - }, - }, - } - - changes, problems := configuration.AddOrUpdateTransportServer(ts) - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - - // Update TransportServer - - updatedTS := ts.DeepCopy() - updatedTS.Generation++ - - expectedChanges = []ResourceChange{ - { - Op: AddOrUpdate, - Resource: &TransportServerConfiguration{ - ListenerPort: 7777, - TransportServer: updatedTS, - }, - }, - } - - changes, problems = configuration.AddOrUpdateTransportServer(updatedTS) - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - - // Make TransportServer invalid - - invalidTS := updatedTS.DeepCopy() - invalidTS.Generation++ - invalidTS.Spec.Upstreams = nil - - expectedChanges = []ResourceChange{ - { - Op: Delete, - Resource: &TransportServerConfiguration{ - ListenerPort: 7777, - TransportServer: updatedTS, - }, - Error: `spec.action.pass: Not found: "myapp"`, - }, - } - - changes, problems = configuration.AddOrUpdateTransportServer(invalidTS) - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - - // Restore TransportServer - - expectedChanges = []ResourceChange{ - { - Op: AddOrUpdate, - Resource: &TransportServerConfiguration{ - ListenerPort: 7777, - TransportServer: updatedTS, - }, - }, - } - - changes, problems = configuration.AddOrUpdateTransportServer(updatedTS) - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - - // Delete TransportServer - - expectedChanges = []ResourceChange{ - { - Op: Delete, - Resource: &TransportServerConfiguration{ - ListenerPort: 7777, - TransportServer: updatedTS, - }, - }, - } - - changes, problems = configuration.DeleteTransportServer("default/transportserver") - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("DeleteTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("DeleteTransportServer() returned unexpected result (-want +got):\n%s", diff) - } -} - -func TestAddTransportServerWithHost(t *testing.T) { - configuration := createTestConfiguration() - - listeners := []conf_v1.Listener{ - { - Name: "tcp-7777", - Port: 7777, - Protocol: "TCP", - }, - } - - addOrUpdateGlobalConfiguration(t, configuration, listeners, noChanges, noProblems) - - secretName := "echo-secret" - - ts := createTestTransportServerWithHost("transportserver", "echo.example.com", "tcp-7777", secretName) - - // no problems are expected for all cases - var expectedProblems []ConfigurationProblem - var expectedChanges []ResourceChange - - // Add TransportServer - - expectedChanges = []ResourceChange{ - { - Op: AddOrUpdate, - Resource: &TransportServerConfiguration{ - ListenerPort: 7777, - TransportServer: ts, - }, - }, - } - - changes, problems := configuration.AddOrUpdateTransportServer(ts) - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - - // Update TransportServer - - updatedTS := ts.DeepCopy() - updatedTS.Generation++ - - expectedChanges = []ResourceChange{ - { - Op: AddOrUpdate, - Resource: &TransportServerConfiguration{ - ListenerPort: 7777, - TransportServer: updatedTS, - }, - }, - } - - changes, problems = configuration.AddOrUpdateTransportServer(updatedTS) - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - - // Make TransportServer invalid - - invalidTS := updatedTS.DeepCopy() - invalidTS.Generation++ - invalidTS.Spec.Upstreams = nil - - expectedChanges = []ResourceChange{ - { - Op: Delete, - Resource: &TransportServerConfiguration{ - ListenerPort: 7777, - TransportServer: updatedTS, - }, - Error: `spec.action.pass: Not found: "myapp"`, - }, - } - - changes, problems = configuration.AddOrUpdateTransportServer(invalidTS) - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - - // Restore TransportServer - - expectedChanges = []ResourceChange{ - { - Op: AddOrUpdate, - Resource: &TransportServerConfiguration{ - ListenerPort: 7777, - TransportServer: updatedTS, - }, - }, - } - - changes, problems = configuration.AddOrUpdateTransportServer(updatedTS) - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - - // Delete TransportServer - - expectedChanges = []ResourceChange{ - { - Op: Delete, - Resource: &TransportServerConfiguration{ - ListenerPort: 7777, - TransportServer: updatedTS, - }, - }, - } - - changes, problems = configuration.DeleteTransportServer("default/transportserver") - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("DeleteTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("DeleteTransportServer() returned unexpected result (-want +got):\n%s", diff) - } -} - -func TestAddTransportServerForTLSPassthrough(t *testing.T) { - configuration := createTestConfiguration() - - ts := createTestTLSPassthroughTransportServer("transportserver", "foo.example.com") - - // no problems are expected for all cases - var expectedProblems []ConfigurationProblem - - // Add TransportServer - - expectedChanges := []ResourceChange{ - { - Op: AddOrUpdate, - Resource: &TransportServerConfiguration{ - ListenerPort: 0, - TransportServer: ts, - }, - }, - } - - changes, problems := configuration.AddOrUpdateTransportServer(ts) - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - - // DeleteTransportServer - - expectedChanges = []ResourceChange{ - { - Op: Delete, - Resource: &TransportServerConfiguration{ - ListenerPort: 0, - TransportServer: ts, - }, - }, - } - - changes, problems = configuration.DeleteTransportServer("default/transportserver") - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("DeleteTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("DeleteTransportServer() returned unexpected result (-want +got):\n%s", diff) - } -} - -func TestListenerFlip(t *testing.T) { - configuration := createTestConfiguration() - - listeners := []conf_v1.Listener{ - { - Name: "tcp-7777", - Port: 7777, - Protocol: "TCP", - }, - { - Name: "tcp-8888", - Port: 8888, - Protocol: "TCP", - }, - } - addOrUpdateGlobalConfiguration(t, configuration, listeners, noChanges, noProblems) - - ts := createTestTransportServer("transportserver", "tcp-7777", "TCP") - - // no problems are expected for all cases - var expectedProblems []ConfigurationProblem - var expectedChanges []ResourceChange - - // Add TransportServer - - expectedChanges = []ResourceChange{ - { - Op: AddOrUpdate, - Resource: &TransportServerConfiguration{ - ListenerPort: 7777, - TransportServer: ts, - }, - }, - } - - changes, problems := configuration.AddOrUpdateTransportServer(ts) - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - - // Update TransportServer listener - - updatedListenerTS := ts.DeepCopy() - updatedListenerTS.Generation++ - updatedListenerTS.Spec.Listener.Name = "tcp-8888" - - expectedChanges = []ResourceChange{ - { - Op: AddOrUpdate, - Resource: &TransportServerConfiguration{ - ListenerPort: 8888, - TransportServer: updatedListenerTS, - }, - }, - } - - changes, problems = configuration.AddOrUpdateTransportServer(updatedListenerTS) - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - - // Update TransportSever listener to TLS Passthrough - - updatedWithPassthroughTS := updatedListenerTS.DeepCopy() - updatedWithPassthroughTS.Generation++ - updatedWithPassthroughTS.Spec.Listener.Name = "tls-passthrough" - updatedWithPassthroughTS.Spec.Listener.Protocol = "TLS_PASSTHROUGH" - updatedWithPassthroughTS.Spec.Host = "example.com" - - expectedChanges = []ResourceChange{ - { - Op: Delete, - Resource: &TransportServerConfiguration{ - ListenerPort: 8888, - TransportServer: updatedListenerTS, - }, - }, - { - Op: AddOrUpdate, - Resource: &TransportServerConfiguration{ - ListenerPort: 0, - TransportServer: updatedWithPassthroughTS, - }, - }, - } - - changes, problems = configuration.AddOrUpdateTransportServer(updatedWithPassthroughTS) - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } -} - -func TestAddInvalidTransportServer(t *testing.T) { - configuration := createTestConfiguration() - - ts := createTestTransportServer("transportserver", "", "TCP") - - expectedProblems := []ConfigurationProblem{ - { - Object: ts, - IsError: true, - Reason: "Rejected", - Message: "TransportServer default/transportserver was rejected with error: spec.listener.name: Required value", - }, - } - var expectedChanges []ResourceChange - - changes, problems := configuration.AddOrUpdateTransportServer(ts) - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } -} - -func TestAddTransportServerWithIncorrectClass(t *testing.T) { - configuration := createTestConfiguration() - - // Add TransportServer with incorrect class - - ts := createTestTLSPassthroughTransportServer("transportserver", "foo.example.com") - ts.Spec.IngressClass = "someproxy" - - var expectedProblems []ConfigurationProblem - var expectedChanges []ResourceChange - - changes, problems := configuration.AddOrUpdateTransportServer(ts) - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - - // Make the class correct - - updatedTS := ts.DeepCopy() - updatedTS.Generation++ - updatedTS.Spec.IngressClass = "nginx" + // Delete master Ingress expectedChanges = []ResourceChange{ + { + Op: Delete, + Resource: &IngressConfiguration{ + Ingress: masterIng, + IsMaster: true, + ValidHosts: map[string]bool{"foo.example.com": true}, + ChildWarnings: map[string][]string{}, + }, + }, { Op: AddOrUpdate, - Resource: &TransportServerConfiguration{ - TransportServer: updatedTS, + Resource: &IngressConfiguration{ + Ingress: regularIng, + ValidHosts: map[string]bool{"foo.example.com": true, "bar.example.com": true}, + ChildWarnings: map[string][]string{}, }, }, } expectedProblems = nil - changes, problems = configuration.AddOrUpdateTransportServer(updatedTS) + changes, problems = configuration.DeleteIngress("default/master-ingress") if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + t.Errorf("DeleteIngress() returned unexpected result (-want +got):\n%s", diff) } if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + t.Errorf("DeleteIngress() returned unexpected result (-want +got):\n%s", diff) } - // Make the class incorrect + // Delete regular Ingress expectedChanges = []ResourceChange{ { Op: Delete, - Resource: &TransportServerConfiguration{ - TransportServer: updatedTS, + Resource: &IngressConfiguration{ + Ingress: regularIng, + ValidHosts: map[string]bool{"foo.example.com": true, "bar.example.com": true}, + ChildWarnings: map[string][]string{}, + }, + }, + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, }, }, } expectedProblems = nil - changes, problems = configuration.AddOrUpdateTransportServer(ts) + changes, problems = configuration.DeleteIngress("default/regular-ingress") if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + t.Errorf("DeleteIngress() returned unexpected result (-want +got):\n%s", diff) } if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + t.Errorf("DeleteIngress() returned unexpected result (-want +got):\n%s", diff) } -} - -func TestAddTransportServerWithNonExistingListener(t *testing.T) { - configuration := createTestConfiguration() - - addOrUpdateGlobalConfiguration(t, configuration, []conf_v1.Listener{}, noChanges, noProblems) - ts := createTestTransportServer("transportserver", "tcp-7777", "TCP") + // Delete VirtualServer - expectedProblems := []ConfigurationProblem{ + expectedChanges = []ResourceChange{ { - Object: ts, - IsError: false, - Reason: "Rejected", - Message: `Listener tcp-7777 doesn't exist`, + Op: Delete, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, + }, + }, + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 0, + TransportServer: ts, + }, }, } - var expectedChanges []ResourceChange - - changes, problems := configuration.AddOrUpdateTransportServer(ts) - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) - } -} - -func TestDeleteNonExistingTransportServer(t *testing.T) { - configuration := createTestConfiguration() - - var expectedChanges []ResourceChange - var expectedProblems []ConfigurationProblem + expectedProblems = nil - changes, problems := configuration.DeleteTransportServer("default/transportserver") + changes, problems = configuration.DeleteVirtualServer("default/virtualserver") if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("DeleteTransportServer() returned unexpected result (-want +got):\n%s", diff) + t.Errorf("DeleteIngress() returned unexpected result (-want +got):\n%s", diff) } if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("DeleteTransportServer() returned unexpected result (-want +got):\n%s", diff) + t.Errorf("DeleteIngress() returned unexpected result (-want +got):\n%s", diff) } } @@ -3563,7 +3057,9 @@ func TestChallengeIngressNoVSR(t *testing.T) { vs := createTestVirtualServer("virtualserver", "bar.example.com") ing := createTestChallengeIngress("challenge", "foo.example.com", "/.well-known/acme-challenge/test", "cm-acme-http-solver-test") - configuration.AddOrUpdateVirtualServer(vs) + + changes, problems := configuration.AddOrUpdateVirtualServer(vs) + expectedChanges := []ResourceChange{ { Op: AddOrUpdate, @@ -3577,12 +3073,13 @@ func TestChallengeIngressNoVSR(t *testing.T) { }, } - changes, problems := configuration.AddOrUpdateIngress(ing) + changes, problems = configuration.AddOrUpdateIngress(ing) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateIngress() returned unexpected result (-want +got):\n%s", diff) + t.Errorf("AddOrUpdateIngress() returned unexpected changes (-want +got):\n%s", diff) } if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("AddOrUpdateIngress() returned unexpected result (-want +got):\n%s", diff) + t.Errorf("AddOrUpdateIngress() returned unexpected problems (-want +got):\n%s", diff) } } @@ -3763,11 +3260,12 @@ func createTestVirtualServerWithRoutes(name string, host string, routes []conf_v return vs } -func createTestVirtualServerRoute(name string, host string, path string) *conf_v1.VirtualServerRoute { +func createTestVirtualServerRoute(name string, host 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", @@ -3813,48 +3311,6 @@ func createTestChallengeVirtualServerRoute(name string, host string, path string } } -func createTestTransportServer(name string, listenerName string, listenerProtocol string) *conf_v1.TransportServer { - return &conf_v1.TransportServer{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: "default", - CreationTimestamp: metav1.Now(), - Generation: 1, - }, - Spec: conf_v1.TransportServerSpec{ - Listener: conf_v1.TransportServerListener{ - Name: listenerName, - Protocol: listenerProtocol, - }, - Upstreams: []conf_v1.TransportServerUpstream{ - { - Name: "myapp", - Service: "myapp-svc", - Port: 1234, - }, - }, - Action: &conf_v1.TransportServerAction{ - Pass: "myapp", - }, - }, - } -} - -func createTestTransportServerWithHost(name string, host string, listenerName string, secretName string) *conf_v1.TransportServer { - ts := createTestTransportServer(name, listenerName, "TCP") - ts.Spec.Host = host - ts.Spec.TLS = &conf_v1.TransportServerTLS{Secret: secretName} - - return ts -} - -func createTestTLSPassthroughTransportServer(name string, host string) *conf_v1.TransportServer { - ts := createTestTransportServer(name, conf_v1.TLSPassthroughListenerName, conf_v1.TLSPassthroughListenerProtocol) - ts.Spec.Host = host - - return ts -} - func createTestGlobalConfiguration(listeners []conf_v1.Listener) *conf_v1.GlobalConfiguration { return &conf_v1.GlobalConfiguration{ ObjectMeta: metav1.ObjectMeta{ @@ -4123,7 +3579,7 @@ func TestFindResourcesForResourceReference(t *testing.T) { Route: "virtualserverroute", }, }) - vsr := createTestVirtualServerRoute("virtualserverroute", "asd.example.com", "/") + vsr := createTestVirtualServerRoute("virtualserverroute", "asd.example.com", "/", nil) tsPassthrough := createTestTLSPassthroughTransportServer("transportserver-passthrough", "ts.example.com") listeners := []conf_v1.Listener{ { @@ -4300,105 +3756,6 @@ func TestGetResources(t *testing.T) { } } -func TestGetTransportServerMetrics(t *testing.T) { - t.Parallel() - tsPass := createTestTLSPassthroughTransportServer("transportserver", "abc.example.com") - tsTCP := createTestTransportServer("transportserver-tcp", "tcp-7777", "TCP") - tsUDP := createTestTransportServer("transportserver-udp", "udp-7777", "UDP") - - tests := []struct { - tses []*conf_v1.TransportServer - expected *TransportServerMetrics - msg string - }{ - { - tses: nil, - expected: &TransportServerMetrics{ - TotalTLSPassthrough: 0, - TotalTCP: 0, - TotalUDP: 0, - }, - msg: "no TransportServers", - }, - { - tses: []*conf_v1.TransportServer{ - tsPass, - }, - expected: &TransportServerMetrics{ - TotalTLSPassthrough: 1, - TotalTCP: 0, - TotalUDP: 0, - }, - msg: "one TLSPassthrough TransportServer", - }, - { - tses: []*conf_v1.TransportServer{ - tsTCP, - }, - expected: &TransportServerMetrics{ - TotalTLSPassthrough: 0, - TotalTCP: 1, - TotalUDP: 0, - }, - msg: "one TCP TransportServer", - }, - { - tses: []*conf_v1.TransportServer{ - tsUDP, - }, - expected: &TransportServerMetrics{ - TotalTLSPassthrough: 0, - TotalTCP: 0, - TotalUDP: 1, - }, - msg: "one UDP TransportServer", - }, - { - tses: []*conf_v1.TransportServer{ - tsPass, tsTCP, tsUDP, - }, - expected: &TransportServerMetrics{ - TotalTLSPassthrough: 1, - TotalTCP: 1, - TotalUDP: 1, - }, - msg: "TLSPassthrough, TCP and UDP TransportServers", - }, - } - - listeners := []conf_v1.Listener{ - { - Name: "tcp-7777", - Port: 7777, - Protocol: "TCP", - }, - { - Name: "udp-7777", - Port: 7777, - Protocol: "UDP", - }, - } - gc := createTestGlobalConfiguration(listeners) - - for _, test := range tests { - configuration := createTestConfiguration() - - _, _, err := configuration.AddOrUpdateGlobalConfiguration(gc) - if err != nil { - t.Fatalf("AddOrUpdateGlobalConfiguration() returned unexpected error %v", err) - } - - for _, ts := range test.tses { - configuration.AddOrUpdateTransportServer(ts) - } - - result := configuration.GetTransportServerMetrics() - if diff := cmp.Diff(test.expected, result); diff != "" { - t.Errorf("GetTransportServerMetrics() returned unexpected result for the case of %s (-want +got):\n%s", test.msg, diff) - } - } -} - func TestIsEqualForIngressConfigurations(t *testing.T) { t.Parallel() regularIng := createTestIngress("regular-ingress", "foo.example.com") @@ -4494,6 +3851,7 @@ func TestIsEqualForIngressConfigurations(t *testing.T) { } } +// TODO: vsr route selector test func TestIsEqualForVirtualServers(t *testing.T) { t.Parallel() vs := createTestVirtualServerWithRoutes( @@ -4505,7 +3863,7 @@ func TestIsEqualForVirtualServers(t *testing.T) { Route: "virtualserverroute", }, }) - vsr := createTestVirtualServerRoute("virtualserverroute", "foo.example.com", "/") + vsr := createTestVirtualServerRoute("virtualserverroute", "foo.example.com", "/", nil) vsWithUpdatedGen := vs.DeepCopy() vsWithUpdatedGen.Generation++ @@ -4520,26 +3878,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", }, @@ -4556,7 +3914,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 { @@ -4754,141 +4112,3 @@ var ( }, } ) - -func TestTransportServerListenerHostCollisions(t *testing.T) { - configuration := createTestConfiguration() - - listeners := []conf_v1.Listener{ - { - Name: "tcp-7777", - Port: 7777, - Protocol: "TCP", - }, - { - Name: "tcp-8888", - Port: 8888, - Protocol: "TCP", - }, - } - - addOrUpdateGlobalConfiguration(t, configuration, listeners, noChanges, noProblems) - - // Create TransportServers with the same listener and host - ts1 := createTestTransportServerWithHost("ts1", "example.com", "tcp-7777", "secret1") - ts2 := createTestTransportServerWithHost("ts2", "example.com", "tcp-7777", "secret2") // same listener and host - ts3 := createTestTransportServerWithHost("ts3", "example.org", "tcp-7777", "secret3") // different host - ts4 := createTestTransportServer("ts4", "tcp-7777", "TCP") // No host same listener - ts5 := createTestTransportServer("ts5", "tcp-7777", "TCP") // same as ts4 to induce error with empty host twice - ts6 := createTestTransportServerWithHost("ts6", "example.com", "tcp-8888", "secret4") // different listener - - // Add ts1 to the configuration - expectedChanges := []ResourceChange{ - { - Op: AddOrUpdate, - Resource: &TransportServerConfiguration{ - ListenerPort: 7777, - TransportServer: ts1, - }, - }, - } - changes, problems := configuration.AddOrUpdateTransportServer(ts1) - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer(ts1) returned unexpected result (-want +got):\n%s", diff) - } - if len(problems) != 0 { - t.Errorf("AddOrUpdateTransportServer(ts1) returned problems %v", problems) - } - - // Try to add ts2, should be rejected due to conflict - changes, problems = configuration.AddOrUpdateTransportServer(ts2) - expectedChanges = nil // No changes expected - expectedProblems := []ConfigurationProblem{ - { - Object: ts2, - IsError: false, - Reason: "Rejected", - Message: "Listener tcp-7777 with host example.com is taken by another resource", - }, - } - - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer(ts2) returned unexpected changes (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("AddOrUpdateTransportServer(ts2) returned unexpected problems (-want +got):\n%s", diff) - } - - // Add ts3 with a different host, should be accepted - expectedChanges = []ResourceChange{ - { - Op: AddOrUpdate, - Resource: &TransportServerConfiguration{ - ListenerPort: 7777, - TransportServer: ts3, - }, - }, - } - changes, problems = configuration.AddOrUpdateTransportServer(ts3) - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer(ts3) returned unexpected result (-want +got):\n%s", diff) - } - if len(problems) != 0 { - t.Errorf("AddOrUpdateTransportServer(ts3) returned problems %v", problems) - } - - // Add ts4 with no host, should be accepted - expectedChanges = []ResourceChange{ - { - Op: AddOrUpdate, - Resource: &TransportServerConfiguration{ - ListenerPort: 7777, - TransportServer: ts4, - }, - }, - } - changes, problems = configuration.AddOrUpdateTransportServer(ts4) - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer(ts4) returned unexpected result (-want +got):\n%s", diff) - } - if len(problems) != 0 { - t.Errorf("AddOrUpdateTransportServer(ts4) returned problems %v", problems) - } - - // Try to add ts5 with no host, should be rejected due to conflict - changes, problems = configuration.AddOrUpdateTransportServer(ts5) - expectedChanges = nil - expectedProblems = []ConfigurationProblem{ - { - Object: ts5, - IsError: false, - Reason: "Rejected", - Message: "Listener tcp-7777 with host empty host is taken by another resource", - }, - } - - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer(ts5) returned unexpected changes (-want +got):\n%s", diff) - } - if diff := cmp.Diff(expectedProblems, problems); diff != "" { - t.Errorf("AddOrUpdateTransportServer(ts5) returned unexpected problems (-want +got):\n%s", diff) - } - - // Try to add ts6 with different listener, but same domain as initial ts, should be fine as different listener - changes, problems = configuration.AddOrUpdateTransportServer(ts6) - expectedChanges = []ResourceChange{ - { - Op: AddOrUpdate, - Resource: &TransportServerConfiguration{ - ListenerPort: 8888, - TransportServer: ts6, - }, - }, - } - if diff := cmp.Diff(expectedChanges, changes); diff != "" { - t.Errorf("AddOrUpdateTransportServer(ts6) returned unexpected changes (-want +got):\n%s", diff) - } - - if len(problems) != 0 { - t.Errorf("AddOrUpdateTransportServer(ts6) returned problems %v", problems) - } -} diff --git a/internal/k8s/configuration_ts_test.go b/internal/k8s/configuration_ts_test.go new file mode 100644 index 0000000000..f168c87543 --- /dev/null +++ b/internal/k8s/configuration_ts_test.go @@ -0,0 +1,825 @@ +package k8s + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + conf_v1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestAddTransportServer(t *testing.T) { + configuration := createTestConfiguration() + + listeners := []conf_v1.Listener{ + { + Name: "tcp-7777", + Port: 7777, + Protocol: "TCP", + }, + } + + addOrUpdateGlobalConfiguration(t, configuration, listeners, noChanges, noProblems) + + ts := createTestTransportServer("transportserver", "tcp-7777", "TCP") + + // no problems are expected for all cases + var expectedProblems []ConfigurationProblem + var expectedChanges []ResourceChange + + // Add TransportServer + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: ts, + }, + }, + } + + changes, problems := configuration.AddOrUpdateTransportServer(ts) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + + // Update TransportServer + + updatedTS := ts.DeepCopy() + updatedTS.Generation++ + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: updatedTS, + }, + }, + } + + changes, problems = configuration.AddOrUpdateTransportServer(updatedTS) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + + // Make TransportServer invalid + + invalidTS := updatedTS.DeepCopy() + invalidTS.Generation++ + invalidTS.Spec.Upstreams = nil + + expectedChanges = []ResourceChange{ + { + Op: Delete, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: updatedTS, + }, + Error: `spec.action.pass: Not found: "myapp"`, + }, + } + + changes, problems = configuration.AddOrUpdateTransportServer(invalidTS) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + + // Restore TransportServer + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: updatedTS, + }, + }, + } + + changes, problems = configuration.AddOrUpdateTransportServer(updatedTS) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + + // Delete TransportServer + + expectedChanges = []ResourceChange{ + { + Op: Delete, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: updatedTS, + }, + }, + } + + changes, problems = configuration.DeleteTransportServer("default/transportserver") + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("DeleteTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("DeleteTransportServer() returned unexpected result (-want +got):\n%s", diff) + } +} + +func TestAddTransportServerWithHost(t *testing.T) { + configuration := createTestConfiguration() + + listeners := []conf_v1.Listener{ + { + Name: "tcp-7777", + Port: 7777, + Protocol: "TCP", + }, + } + + addOrUpdateGlobalConfiguration(t, configuration, listeners, noChanges, noProblems) + + secretName := "echo-secret" + + ts := createTestTransportServerWithHost("transportserver", "echo.example.com", "tcp-7777", secretName) + + // no problems are expected for all cases + var expectedProblems []ConfigurationProblem + var expectedChanges []ResourceChange + + // Add TransportServer + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: ts, + }, + }, + } + + changes, problems := configuration.AddOrUpdateTransportServer(ts) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + + // Update TransportServer + + updatedTS := ts.DeepCopy() + updatedTS.Generation++ + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: updatedTS, + }, + }, + } + + changes, problems = configuration.AddOrUpdateTransportServer(updatedTS) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + + // Make TransportServer invalid + + invalidTS := updatedTS.DeepCopy() + invalidTS.Generation++ + invalidTS.Spec.Upstreams = nil + + expectedChanges = []ResourceChange{ + { + Op: Delete, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: updatedTS, + }, + Error: `spec.action.pass: Not found: "myapp"`, + }, + } + + changes, problems = configuration.AddOrUpdateTransportServer(invalidTS) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + + // Restore TransportServer + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: updatedTS, + }, + }, + } + + changes, problems = configuration.AddOrUpdateTransportServer(updatedTS) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + + // Delete TransportServer + + expectedChanges = []ResourceChange{ + { + Op: Delete, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: updatedTS, + }, + }, + } + + changes, problems = configuration.DeleteTransportServer("default/transportserver") + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("DeleteTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("DeleteTransportServer() returned unexpected result (-want +got):\n%s", diff) + } +} + +func TestAddTransportServerForTLSPassthrough(t *testing.T) { + configuration := createTestConfiguration() + + ts := createTestTLSPassthroughTransportServer("transportserver", "foo.example.com") + + // no problems are expected for all cases + var expectedProblems []ConfigurationProblem + + // Add TransportServer + + expectedChanges := []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 0, + TransportServer: ts, + }, + }, + } + + changes, problems := configuration.AddOrUpdateTransportServer(ts) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + + // DeleteTransportServer + + expectedChanges = []ResourceChange{ + { + Op: Delete, + Resource: &TransportServerConfiguration{ + ListenerPort: 0, + TransportServer: ts, + }, + }, + } + + changes, problems = configuration.DeleteTransportServer("default/transportserver") + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("DeleteTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("DeleteTransportServer() returned unexpected result (-want +got):\n%s", diff) + } +} + +func TestListenerFlip(t *testing.T) { + configuration := createTestConfiguration() + + listeners := []conf_v1.Listener{ + { + Name: "tcp-7777", + Port: 7777, + Protocol: "TCP", + }, + { + Name: "tcp-8888", + Port: 8888, + Protocol: "TCP", + }, + } + addOrUpdateGlobalConfiguration(t, configuration, listeners, noChanges, noProblems) + + ts := createTestTransportServer("transportserver", "tcp-7777", "TCP") + + // no problems are expected for all cases + var expectedProblems []ConfigurationProblem + var expectedChanges []ResourceChange + + // Add TransportServer + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: ts, + }, + }, + } + + changes, problems := configuration.AddOrUpdateTransportServer(ts) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + + // Update TransportServer listener + + updatedListenerTS := ts.DeepCopy() + updatedListenerTS.Generation++ + updatedListenerTS.Spec.Listener.Name = "tcp-8888" + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 8888, + TransportServer: updatedListenerTS, + }, + }, + } + + changes, problems = configuration.AddOrUpdateTransportServer(updatedListenerTS) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + + // Update TransportSever listener to TLS Passthrough + + updatedWithPassthroughTS := updatedListenerTS.DeepCopy() + updatedWithPassthroughTS.Generation++ + updatedWithPassthroughTS.Spec.Listener.Name = "tls-passthrough" + updatedWithPassthroughTS.Spec.Listener.Protocol = "TLS_PASSTHROUGH" + updatedWithPassthroughTS.Spec.Host = "example.com" + + expectedChanges = []ResourceChange{ + { + Op: Delete, + Resource: &TransportServerConfiguration{ + ListenerPort: 8888, + TransportServer: updatedListenerTS, + }, + }, + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 0, + TransportServer: updatedWithPassthroughTS, + }, + }, + } + + changes, problems = configuration.AddOrUpdateTransportServer(updatedWithPassthroughTS) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } +} + +func TestAddInvalidTransportServer(t *testing.T) { + configuration := createTestConfiguration() + + ts := createTestTransportServer("transportserver", "", "TCP") + + expectedProblems := []ConfigurationProblem{ + { + Object: ts, + IsError: true, + Reason: "Rejected", + Message: "TransportServer default/transportserver was rejected with error: spec.listener.name: Required value", + }, + } + var expectedChanges []ResourceChange + + changes, problems := configuration.AddOrUpdateTransportServer(ts) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } +} + +func TestAddTransportServerWithIncorrectClass(t *testing.T) { + configuration := createTestConfiguration() + + // Add TransportServer with incorrect class + + ts := createTestTLSPassthroughTransportServer("transportserver", "foo.example.com") + ts.Spec.IngressClass = "someproxy" + + var expectedProblems []ConfigurationProblem + var expectedChanges []ResourceChange + + changes, problems := configuration.AddOrUpdateTransportServer(ts) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + + // Make the class correct + + updatedTS := ts.DeepCopy() + updatedTS.Generation++ + updatedTS.Spec.IngressClass = "nginx" + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + TransportServer: updatedTS, + }, + }, + } + expectedProblems = nil + + changes, problems = configuration.AddOrUpdateTransportServer(updatedTS) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + + // Make the class incorrect + + expectedChanges = []ResourceChange{ + { + Op: Delete, + Resource: &TransportServerConfiguration{ + TransportServer: updatedTS, + }, + }, + } + expectedProblems = nil + + changes, problems = configuration.AddOrUpdateTransportServer(ts) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } +} + +func TestAddTransportServerWithNonExistingListener(t *testing.T) { + configuration := createTestConfiguration() + + addOrUpdateGlobalConfiguration(t, configuration, []conf_v1.Listener{}, noChanges, noProblems) + + ts := createTestTransportServer("transportserver", "tcp-7777", "TCP") + + expectedProblems := []ConfigurationProblem{ + { + Object: ts, + IsError: false, + Reason: "Rejected", + Message: `Listener tcp-7777 doesn't exist`, + }, + } + var expectedChanges []ResourceChange + + changes, problems := configuration.AddOrUpdateTransportServer(ts) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } +} + +func TestDeleteNonExistingTransportServer(t *testing.T) { + configuration := createTestConfiguration() + + var expectedChanges []ResourceChange + var expectedProblems []ConfigurationProblem + + changes, problems := configuration.DeleteTransportServer("default/transportserver") + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("DeleteTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("DeleteTransportServer() returned unexpected result (-want +got):\n%s", diff) + } +} + +func createTestTransportServer(name string, listenerName string, listenerProtocol string) *conf_v1.TransportServer { + return &conf_v1.TransportServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "default", + CreationTimestamp: metav1.Now(), + Generation: 1, + }, + Spec: conf_v1.TransportServerSpec{ + Listener: conf_v1.TransportServerListener{ + Name: listenerName, + Protocol: listenerProtocol, + }, + Upstreams: []conf_v1.TransportServerUpstream{ + { + Name: "myapp", + Service: "myapp-svc", + Port: 1234, + }, + }, + Action: &conf_v1.TransportServerAction{ + Pass: "myapp", + }, + }, + } +} + +func createTestTransportServerWithHost(name string, host string, listenerName string, secretName string) *conf_v1.TransportServer { + ts := createTestTransportServer(name, listenerName, "TCP") + ts.Spec.Host = host + ts.Spec.TLS = &conf_v1.TransportServerTLS{Secret: secretName} + + return ts +} + +func createTestTLSPassthroughTransportServer(name string, host string) *conf_v1.TransportServer { + ts := createTestTransportServer(name, conf_v1.TLSPassthroughListenerName, conf_v1.TLSPassthroughListenerProtocol) + ts.Spec.Host = host + + return ts +} + +func TestGetTransportServerMetrics(t *testing.T) { + t.Parallel() + tsPass := createTestTLSPassthroughTransportServer("transportserver", "abc.example.com") + tsTCP := createTestTransportServer("transportserver-tcp", "tcp-7777", "TCP") + tsUDP := createTestTransportServer("transportserver-udp", "udp-7777", "UDP") + + tests := []struct { + tses []*conf_v1.TransportServer + expected *TransportServerMetrics + msg string + }{ + { + tses: nil, + expected: &TransportServerMetrics{ + TotalTLSPassthrough: 0, + TotalTCP: 0, + TotalUDP: 0, + }, + msg: "no TransportServers", + }, + { + tses: []*conf_v1.TransportServer{ + tsPass, + }, + expected: &TransportServerMetrics{ + TotalTLSPassthrough: 1, + TotalTCP: 0, + TotalUDP: 0, + }, + msg: "one TLSPassthrough TransportServer", + }, + { + tses: []*conf_v1.TransportServer{ + tsTCP, + }, + expected: &TransportServerMetrics{ + TotalTLSPassthrough: 0, + TotalTCP: 1, + TotalUDP: 0, + }, + msg: "one TCP TransportServer", + }, + { + tses: []*conf_v1.TransportServer{ + tsUDP, + }, + expected: &TransportServerMetrics{ + TotalTLSPassthrough: 0, + TotalTCP: 0, + TotalUDP: 1, + }, + msg: "one UDP TransportServer", + }, + { + tses: []*conf_v1.TransportServer{ + tsPass, tsTCP, tsUDP, + }, + expected: &TransportServerMetrics{ + TotalTLSPassthrough: 1, + TotalTCP: 1, + TotalUDP: 1, + }, + msg: "TLSPassthrough, TCP and UDP TransportServers", + }, + } + + listeners := []conf_v1.Listener{ + { + Name: "tcp-7777", + Port: 7777, + Protocol: "TCP", + }, + { + Name: "udp-7777", + Port: 7777, + Protocol: "UDP", + }, + } + gc := createTestGlobalConfiguration(listeners) + + for _, test := range tests { + configuration := createTestConfiguration() + + _, _, err := configuration.AddOrUpdateGlobalConfiguration(gc) + if err != nil { + t.Fatalf("AddOrUpdateGlobalConfiguration() returned unexpected error %v", err) + } + + for _, ts := range test.tses { + configuration.AddOrUpdateTransportServer(ts) + } + + result := configuration.GetTransportServerMetrics() + if diff := cmp.Diff(test.expected, result); diff != "" { + t.Errorf("GetTransportServerMetrics() returned unexpected result for the case of %s (-want +got):\n%s", test.msg, diff) + } + } +} + +func TestTransportServerListenerHostCollisions(t *testing.T) { + configuration := createTestConfiguration() + + listeners := []conf_v1.Listener{ + { + Name: "tcp-7777", + Port: 7777, + Protocol: "TCP", + }, + { + Name: "tcp-8888", + Port: 8888, + Protocol: "TCP", + }, + } + + addOrUpdateGlobalConfiguration(t, configuration, listeners, noChanges, noProblems) + + // Create TransportServers with the same listener and host + ts1 := createTestTransportServerWithHost("ts1", "example.com", "tcp-7777", "secret1") + ts2 := createTestTransportServerWithHost("ts2", "example.com", "tcp-7777", "secret2") // same listener and host + ts3 := createTestTransportServerWithHost("ts3", "example.org", "tcp-7777", "secret3") // different host + ts4 := createTestTransportServer("ts4", "tcp-7777", "TCP") // No host same listener + ts5 := createTestTransportServer("ts5", "tcp-7777", "TCP") // same as ts4 to induce error with empty host twice + ts6 := createTestTransportServerWithHost("ts6", "example.com", "tcp-8888", "secret4") // different listener + + // Add ts1 to the configuration + expectedChanges := []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: ts1, + }, + }, + } + changes, problems := configuration.AddOrUpdateTransportServer(ts1) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer(ts1) returned unexpected result (-want +got):\n%s", diff) + } + if len(problems) != 0 { + t.Errorf("AddOrUpdateTransportServer(ts1) returned problems %v", problems) + } + + // Try to add ts2, should be rejected due to conflict + changes, problems = configuration.AddOrUpdateTransportServer(ts2) + expectedChanges = nil // No changes expected + expectedProblems := []ConfigurationProblem{ + { + Object: ts2, + IsError: false, + Reason: "Rejected", + Message: "Listener tcp-7777 with host example.com is taken by another resource", + }, + } + + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer(ts2) returned unexpected changes (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer(ts2) returned unexpected problems (-want +got):\n%s", diff) + } + + // Add ts3 with a different host, should be accepted + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: ts3, + }, + }, + } + changes, problems = configuration.AddOrUpdateTransportServer(ts3) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer(ts3) returned unexpected result (-want +got):\n%s", diff) + } + if len(problems) != 0 { + t.Errorf("AddOrUpdateTransportServer(ts3) returned problems %v", problems) + } + + // Add ts4 with no host, should be accepted + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: ts4, + }, + }, + } + changes, problems = configuration.AddOrUpdateTransportServer(ts4) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer(ts4) returned unexpected result (-want +got):\n%s", diff) + } + if len(problems) != 0 { + t.Errorf("AddOrUpdateTransportServer(ts4) returned problems %v", problems) + } + + // Try to add ts5 with no host, should be rejected due to conflict + changes, problems = configuration.AddOrUpdateTransportServer(ts5) + expectedChanges = nil + expectedProblems = []ConfigurationProblem{ + { + Object: ts5, + IsError: false, + Reason: "Rejected", + Message: "Listener tcp-7777 with host empty host is taken by another resource", + }, + } + + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer(ts5) returned unexpected changes (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer(ts5) returned unexpected problems (-want +got):\n%s", diff) + } + + // Try to add ts6 with different listener, but same domain as initial ts, should be fine as different listener + changes, problems = configuration.AddOrUpdateTransportServer(ts6) + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 8888, + TransportServer: ts6, + }, + }, + } + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer(ts6) returned unexpected changes (-want +got):\n%s", diff) + } + + if len(problems) != 0 { + t.Errorf("AddOrUpdateTransportServer(ts6) returned problems %v", problems) + } +} diff --git a/internal/k8s/configuration_vsr_test.go b/internal/k8s/configuration_vsr_test.go new file mode 100644 index 0000000000..0ecf2b0948 --- /dev/null +++ b/internal/k8s/configuration_vsr_test.go @@ -0,0 +1,807 @@ +package k8s + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + conf_v1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// WIP - Jakub +func TestAddAndUpdateVirtualServer(t *testing.T) { + // Add a VirtualServer + vs := createTestVirtualServer("virtualserver", "foo.example.com") + expectedChanges := []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, + }, + }, + } + + // ========= + // Note: call t.Fatal() as there is no point to carry on and update the VS if the VS is not created + // meaning we have errors or `problems` when creating the VS. + configuration := createTestConfiguration() + // no problems are expected for all cases + var expectedProblems []ConfigurationProblem + + changes, problems := configuration.AddOrUpdateVirtualServer(vs) + + if !cmp.Equal(expectedChanges, changes) { + t.Fatal(cmp.Diff(expectedChanges, changes)) + } + if !cmp.Equal(expectedProblems, problems) { + t.Fatal(cmp.Diff(expectedProblems, problems)) + } + // ========= End Add VS ========= + + // Update VirtualServer + + updatedVS := vs.DeepCopy() + updatedVS.Generation++ + updatedVS.Spec.ServerSnippets = "# snippet" + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: updatedVS, + }, + }, + } + + changes, problems = configuration.AddOrUpdateVirtualServer(updatedVS) + if !cmp.Equal(expectedChanges, changes) { + t.Fatal(cmp.Diff(expectedChanges, changes)) + } + if !cmp.Equal(expectedProblems, problems) { + t.Fatal(cmp.Diff(expectedProblems, problems)) + } + // ========= End Update VS ========= + + // Make VirtualServer invalid + + invalidVS := updatedVS.DeepCopy() + invalidVS.Generation++ + invalidVS.Spec.Host = "" + + expectedChanges = []ResourceChange{ + { + Op: Delete, + Resource: &VirtualServerConfiguration{ + VirtualServer: updatedVS, + }, + Error: "spec.host: Required value", + }, + } + + changes, problems = configuration.AddOrUpdateVirtualServer(invalidVS) + 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) + } + + // Restore VirtualServer + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: updatedVS, + }, + }, + } + + changes, problems = configuration.AddOrUpdateVirtualServer(updatedVS) + 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) + } + + // Update VirtualServer host + + updatedHostVS := updatedVS.DeepCopy() + updatedHostVS.Generation++ + updatedHostVS.Spec.Host = "bar.example.com" + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: updatedHostVS, + }, + }, + } + + changes, problems = configuration.AddOrUpdateVirtualServer(updatedHostVS) + 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) + } + + // Delete VirtualServer + expectedChanges = []ResourceChange{ + { + Op: Delete, + Resource: &VirtualServerConfiguration{ + VirtualServer: updatedHostVS, + }, + }, + } + + changes, problems = configuration.DeleteVirtualServer("default/virtualserver") + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("DeleteVirtualServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("DeleteVirtualServer() returned unexpected result (-want +got):\n%s", diff) + } +} + +// Test if correct changes and problems are reported +// if we try to update VS with invalid configuration. +// +// TODO: add workflow (Jakub) +func TestAddVirtualServer_InvalidVS(t *testing.T) { + t.Parallel() +} + +// Negative flow - User attempts to add VSR to not existing VS +func TestAttemptToAddVSRtoNotExistingVS_ReturnsProblems(t *testing.T) { + t.Parallel() + + configuration := createTestConfiguration() + + labels := make(map[string]string) + vsr := createTestVirtualServerRoute("virtualserverroute", "foo.example.com", "/first", labels) + + // Try to add VirtualServerRoute + + var expectedChanges []ResourceChange + expectedProblems := []ConfigurationProblem{ + { + Object: vsr, + Reason: "NoVirtualServerFound", + Message: "VirtualServer is invalid or doesn't exist", + }, + } + + changes, problems := configuration.AddOrUpdateVirtualServerRoute(vsr) + if !cmp.Equal(expectedChanges, changes, cmpopts.IgnoreFields(ConfigurationProblem{}, "Message")) { + t.Error(cmp.Diff(expectedChanges, changes)) + } + if !cmp.Equal(expectedProblems, problems, cmpopts.IgnoreFields(ConfigurationProblem{}, "Message")) { + t.Error(cmp.Diff(expectedProblems, problems)) + } +} + +// TestAddVSRtoVS validates that we can add VSR to VS +// todo: describe conditions and reason why the sest +func TestAddVSRtoVS(t *testing.T) { + t.Parallel() + + configuration := createTestConfiguration() + + // ========= + // Step 1 + // ========= + // Add VirtualServerRoute + // We add VSR that references not existing VS. Chance we expect to see `Problems` + + labels := make(map[string]string) + vsr := createTestVirtualServerRoute("virtualserverroute", "foo.example.com", "/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)) + } + + // ========= + // Step 2 + // ========= + // Create the missing VS with routes. + // Note that the 1st Route in the routes slice below matches the route from the previous step! + + 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}, + }, + }, + } + expectedProblems = nil + + // We update configuration here - add the missing VS (see Step 1) + // At this point we have both VS and VSR in the configuration. They match. + 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 TestMatchVSwithVSRusingSelector(t *testing.T) { + t.Parallel() + + configuration := createTestConfiguration() + + // Add VirtualServerRoute + + labels := make(map[string]string) + vsr := createTestVirtualServerRoute("virtualserverroute", "foo.example.com", "/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}, + }, + }, + } + 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)) + } +} + +// WIP - Jakub +// TODO: vsr route selector test +func TestAddVirtualServerWithVirtualServerRoutesVSR(t *testing.T) { + configuration := createTestConfiguration() + + // Add VirtualServerRoute-1 + + labels := make(map[string]string) + vsr1 := createTestVirtualServerRoute("virtualserverroute-1", "foo.example.com", "/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}, + 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 := createTestVirtualServerRoute("virtualserverroute-2", "foo.example.com", "/second", nil) + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2}, + }, + }, + } + 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) + } + + // Update VirtualServerRoute-1 + + updatedVSR1 := vsr1.DeepCopy() + updatedVSR1.Generation++ + updatedVSR1.Spec.Subroutes[0].LocationSnippets = "# snippet" + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{updatedVSR1, vsr2}, + }, + }, + } + expectedProblems = nil + + changes, problems = configuration.AddOrUpdateVirtualServerRoute(updatedVSR1) + 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 and VirtualServerRoute-4 with selectors + + vsr3 := createTestVirtualServerRoute("virtualserverroute-3", "foo.example.com", "/third", map[string]string{"app": "route"}) + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{updatedVSR1, 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) + } + + // Make VirtualServerRoute-1 invalid + + invalidVSR1 := updatedVSR1.DeepCopy() + invalidVSR1.Generation++ + invalidVSR1.Spec.Host = "" + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr2, vsr3}, + Warnings: []string{"VirtualServerRoute default/virtualserverroute-1 doesn't exist or invalid"}, + }, + }, + } + expectedProblems = []ConfigurationProblem{ + { + Object: invalidVSR1, + IsError: true, + Reason: "Rejected", + Message: "VirtualServerRoute default/virtualserverroute-1 was rejected with error: spec.host: Required value", + }, + } + + changes, problems = configuration.AddOrUpdateVirtualServerRoute(invalidVSR1) + 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) + } + + // Restore VirtualServerRoute-1 + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2, vsr3}, + }, + }, + } + expectedProblems = nil + + 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) + } + + // Make VirtualServerRoute-1 invalid for VirtualServer + + invalidForVSVSR1 := vsr1.DeepCopy() + invalidForVSVSR1.Generation++ + invalidForVSVSR1.Spec.Subroutes[0].Path = "/" + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr2, vsr3}, + Warnings: []string{"VirtualServerRoute default/virtualserverroute-1 is invalid: spec.subroutes[0]: Invalid value: \"/\": must start with '/first'"}, + }, + }, + } + expectedProblems = []ConfigurationProblem{ + { + Object: invalidForVSVSR1, + Reason: "Ignored", + Message: "VirtualServer default/virtualserver ignores VirtualServerRoute", + }, + } + + changes, problems = configuration.AddOrUpdateVirtualServerRoute(invalidForVSVSR1) + 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) + } + + // Restore VirtualServerRoute-1 + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2, vsr3}, + }, + }, + } + expectedProblems = nil + + 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) + } + + // Update host of VirtualServerRoute-2 + + updatedVSR2 := vsr2.DeepCopy() + updatedVSR2.Generation++ + updatedVSR2.Spec.Host = "bar.example.com" + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr3}, + Warnings: []string{"VirtualServerRoute default/virtualserverroute-2 is invalid: spec.host: Invalid value: \"bar.example.com\": must be equal to 'foo.example.com'"}, + }, + }, + } + expectedProblems = []ConfigurationProblem{ + { + Object: updatedVSR2, + Reason: "NoVirtualServerFound", + Message: "VirtualServer is invalid or doesn't exist", + }, + } + + changes, problems = configuration.AddOrUpdateVirtualServerRoute(updatedVSR2) + 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 host of VirtualServer + + updatedVS := vs.DeepCopy() + updatedVS.Generation++ + updatedVS.Spec.Host = "bar.example.com" + expectedChanges = []ResourceChange{ + { + 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'"}, + }, + }, + } + expectedProblems = []ConfigurationProblem{ + { + Object: vsr1, + Reason: "NoVirtualServerFound", + Message: "VirtualServer is invalid or doesn't exist", + }, + } + + changes, problems = configuration.AddOrUpdateVirtualServer(updatedVS) + 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) + } + + // Restore host of VirtualServer + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr3}, + Warnings: []string{"VirtualServerRoute default/virtualserverroute-2 is invalid: spec.host: Invalid value: \"bar.example.com\": must be equal to 'foo.example.com'"}, + }, + }, + } + expectedProblems = []ConfigurationProblem{ + { + Object: updatedVSR2, + Reason: "NoVirtualServerFound", + Message: "VirtualServer is invalid or doesn't exist", + }, + } + + 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) + } + + // Restore host of VirtualServerRoute-2 + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr1, vsr2, vsr3}, + }, + }, + } + 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) + } + + // Remove VirtualServerRoute-1 + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr2, vsr3}, + Warnings: []string{"VirtualServerRoute default/virtualserverroute-1 doesn't exist or invalid"}, + }, + }, + } + expectedProblems = nil + + changes, problems = configuration.DeleteVirtualServerRoute("default/virtualserverroute-1") + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("DeleteVirtualServerRoute() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("DeleteVirtualServerRoute() returned unexpected result (-want +got):\n%s", diff) + } + + // Remove VirtualServer + + expectedChanges = []ResourceChange{ + { + Op: Delete, + Resource: &VirtualServerConfiguration{ + VirtualServer: vs, + VirtualServerRoutes: []*conf_v1.VirtualServerRoute{vsr2}, + Warnings: []string{"VirtualServerRoute default/virtualserverroute-1 doesn't exist or invalid"}, + }, + }, + } + expectedProblems = []ConfigurationProblem{ + { + Object: vsr2, + Reason: "NoVirtualServerFound", + Message: "VirtualServer is invalid or doesn't exist", + }, + } + + changes, problems = configuration.DeleteVirtualServer("default/virtualserver") + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("DeleteVirtualServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("DeleteVirtualServer() returned unexpected result (-want +got):\n%s", diff) + } + + // Remove VirtualServerRoute-2 + + expectedChanges = nil + expectedProblems = nil + + changes, problems = configuration.DeleteVirtualServerRoute("default/virtualserverroute-2") + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("DeleteVirtualServerRoute() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("DeleteVirtualServerRoute() returned unexpected result (-want +got):\n%s", diff) + } +} + +// WIP (Jakub) +// TODO: vsr route selector test +func TestIsEqualForVirtualServersVSR(t *testing.T) { + t.Parallel() + vs := createTestVirtualServerWithRoutes( + "virtualserver", + "foo.example.com", + []conf_v1.Route{ + { + Path: "/", + Route: "virtualserverroute", + }, + }) + vsr := createTestVirtualServerRoute("virtualserverroute", "foo.example.com", "/", 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 3276b632bb..09719cef17 100644 --- a/internal/k8s/controller.go +++ b/internal/k8s/controller.go @@ -340,6 +340,7 @@ func NewLoadBalancerController(input NewLoadBalancerControllerInput) *LoadBalanc } lbc.configuration = NewConfiguration( + lbc.Logger, lbc.HasCorrectIngressClass, input.IsNginxPlus, input.AppProtectEnabled, @@ -820,7 +821,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: @@ -1173,7 +1174,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) @@ -2115,13 +2116,14 @@ 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), } resource := lbc.configuration.hosts[virtualServer.Spec.Host] @@ -3363,7 +3365,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/global_configuration.go b/internal/k8s/global_configuration.go index fb71023b04..3108ecc532 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 cc40b0fd8d..3187c913f5 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 cac87569ab..2a4def2ede 100644 --- a/pkg/apis/configuration/v1/types.go +++ b/pkg/apis/configuration/v1/types.go @@ -182,15 +182,16 @@ type SessionCookie struct { // Route defines a route. type Route struct { - Path string `json:"path"` - Policies []PolicyReference `json:"policies"` - Route string `json:"route"` - Action *Action `json:"action"` - Splits []Split `json:"splits"` - Matches []Match `json:"matches"` - ErrorPages []ErrorPage `json:"errorPages"` - LocationSnippets string `json:"location-snippets"` - Dos string `json:"dos"` + Path string `json:"path"` + Policies []PolicyReference `json:"policies"` + Route string `json:"route"` + RouteSelector *metav1.LabelSelector `json:"routeSelector"` + Action *Action `json:"action"` + Splits []Split `json:"splits"` + Matches []Match `json:"matches"` + ErrorPages []ErrorPage `json:"errorPages"` + LocationSnippets string `json:"location-snippets"` + Dos string `json:"dos"` } // Action defines an action. diff --git a/pkg/apis/configuration/v1/zz_generated.deepcopy.go b/pkg/apis/configuration/v1/zz_generated.deepcopy.go index b617f3cb93..a807d2b113 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" ) @@ -891,6 +892,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 77d0a89aa1..1e5e3329bd 100644 --- a/pkg/apis/configuration/validation/virtualserver.go +++ b/pkg/apis/configuration/validation/virtualserver.go @@ -758,6 +758,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))...) } @@ -772,7 +777,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`" }