github.com/nginxinc/kubernetes-ingress@v1.12.5/tests/suite/test_transport_server_udp_load_balance.py (about)

     1  import pytest
     2  import re
     3  import socket
     4  
     5  from suite.resources_utils import (
     6      wait_before_test,
     7      get_ts_nginx_template_conf,
     8      scale_deployment,
     9      get_events,
    10      wait_for_event_increment,
    11  )
    12  from suite.custom_resources_utils import (
    13      patch_ts,
    14      read_ts,
    15      delete_ts,
    16      create_ts_from_yaml,
    17  )
    18  from settings import TEST_DATA
    19  
    20  @pytest.mark.ts
    21  @pytest.mark.parametrize(
    22      "crd_ingress_controller, transport_server_setup",
    23      [
    24          (
    25              {
    26                  "type": "complete",
    27                  "extra_args":
    28                      [
    29                          "-global-configuration=nginx-ingress/nginx-configuration",
    30                          "-enable-leader-election=false"
    31                      ]
    32              },
    33              {"example": "transport-server-udp-load-balance"},
    34          )
    35      ],
    36      indirect=True,
    37  )
    38  class TestTransportServerUdpLoadBalance:
    39  
    40      def restore_ts(self, kube_apis, transport_server_setup) -> None:
    41          """
    42          Function to revert a TransportServer resource to a valid state.
    43          """
    44          patch_src = f"{TEST_DATA}/transport-server-udp-load-balance/standard/transport-server.yaml"
    45          patch_ts(
    46              kube_apis.custom_objects,
    47              transport_server_setup.name,
    48              patch_src,
    49              transport_server_setup.namespace,
    50          )
    51  
    52      def test_number_of_replicas(
    53          self, kube_apis, crd_ingress_controller, transport_server_setup, ingress_controller_prerequisites
    54      ):
    55          """
    56          The load balancing of UDP should result in 4 servers to match the 4 replicas of a service.
    57          """
    58          original = scale_deployment(kube_apis.apps_v1_api, "udp-service", transport_server_setup.namespace, 4)
    59          num_servers = 0
    60          retry = 0
    61  
    62          while(num_servers is not 4 and retry <= 50):
    63              result_conf = get_ts_nginx_template_conf(
    64                  kube_apis.v1,
    65                  transport_server_setup.namespace,
    66                  transport_server_setup.name,
    67                  transport_server_setup.ingress_pod_name,
    68                  ingress_controller_prerequisites.namespace
    69              )
    70              
    71              pattern = 'server .*;'
    72              num_servers = len(re.findall(pattern, result_conf))
    73              retry += 1
    74              wait_before_test(1)
    75              print(f"Retry #{retry}")
    76          
    77          assert num_servers is 4
    78  
    79          scale_deployment(kube_apis.apps_v1_api, "udp-service", transport_server_setup.namespace, original)
    80          retry = 0
    81          while(num_servers is not original and retry <= 50):
    82              result_conf = get_ts_nginx_template_conf(
    83                  kube_apis.v1,
    84                  transport_server_setup.namespace,
    85                  transport_server_setup.name,
    86                  transport_server_setup.ingress_pod_name,
    87                  ingress_controller_prerequisites.namespace
    88              )
    89              
    90              pattern = 'server .*;'
    91              num_servers = len(re.findall(pattern, result_conf))
    92              retry += 1
    93              wait_before_test(1)
    94              print(f"Retry #{retry}")
    95          
    96          assert num_servers is original
    97  
    98      def test_udp_request_load_balanced(
    99              self, kube_apis, crd_ingress_controller, transport_server_setup, ingress_controller_prerequisites
   100      ):
   101          """
   102          Requests to the load balanced UDP service should result in responses from 3 different endpoints.
   103          """
   104          wait_before_test()
   105          port = transport_server_setup.public_endpoint.udp_server_port
   106          host = transport_server_setup.public_endpoint.public_ip
   107  
   108          print(f"sending udp requests to: {host}:{port}")
   109  
   110          endpoints = {}
   111          retry = 0
   112          while(len(endpoints) is not 3 and retry <= 30):
   113              for i in range(20):
   114                  client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
   115                  client.sendto("ping".encode('utf-8'), (host, port))
   116                  data, address = client.recvfrom(4096)
   117                  endpoint = data.decode()
   118                  print(f' req number {i}; response: {endpoint}')
   119                  if endpoint not in endpoints:
   120                      endpoints[endpoint] = 1
   121                  else:
   122                      endpoints[endpoint] = endpoints[endpoint] + 1
   123                  client.close()
   124              retry += 1
   125              wait_before_test(1)
   126              print(f"Retry #{retry}")
   127  
   128          assert len(endpoints) is 3
   129  
   130          result_conf = get_ts_nginx_template_conf(
   131              kube_apis.v1,
   132              transport_server_setup.namespace,
   133              transport_server_setup.name,
   134              transport_server_setup.ingress_pod_name,
   135              ingress_controller_prerequisites.namespace
   136          )
   137  
   138          pattern = 'server .*;'
   139          servers = re.findall(pattern, result_conf)
   140          for key in endpoints.keys():
   141              found = False
   142              for server in servers:
   143                  if key in server:
   144                      found = True
   145              assert found
   146  
   147      def test_udp_request_load_balanced_multiple(
   148              self, kube_apis, crd_ingress_controller, transport_server_setup
   149      ):
   150          """
   151          Requests to the load balanced UDP service should result in responses from 3 different endpoints.
   152          """
   153          port = transport_server_setup.public_endpoint.udp_server_port
   154          host = transport_server_setup.public_endpoint.public_ip
   155  
   156          # Step 1, confirm load balancing is working.
   157          print(f"sending udp requests to: {host}:{port}")
   158          client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
   159          client.sendto("ping".encode('utf-8'), (host, port))
   160          data, address = client.recvfrom(4096)
   161          endpoint = data.decode()
   162          print(f'response: {endpoint}')
   163          client.close()
   164  
   165          # Step 2, add a second TransportServer with the same port and confirm te collision
   166          transport_server_file = f"{TEST_DATA}/transport-server-udp-load-balance/second-transport-server.yaml"
   167          ts_resource = create_ts_from_yaml(
   168              kube_apis.custom_objects, transport_server_file, transport_server_setup.namespace
   169          )
   170          wait_before_test()
   171  
   172          second_ts_name = ts_resource['metadata']['name']
   173          response = read_ts(
   174              kube_apis.custom_objects,
   175              transport_server_setup.namespace,
   176              second_ts_name,
   177          )
   178          assert (
   179                  response["status"]
   180                  and response["status"]["reason"] == "Rejected"
   181                  and response["status"]["state"] == "Warning"
   182                  and response["status"]["message"] == "Listener udp-server is taken by another resource"
   183          )
   184  
   185          # Step 3, remove the default TransportServer with the same port
   186          delete_ts(kube_apis.custom_objects, transport_server_setup.resource, transport_server_setup.namespace)
   187  
   188          wait_before_test()
   189          response = read_ts(
   190              kube_apis.custom_objects,
   191              transport_server_setup.namespace,
   192              second_ts_name,
   193          )
   194          assert (
   195                  response["status"]
   196                  and response["status"]["reason"] == "AddedOrUpdated"
   197                  and response["status"]["state"] == "Valid"
   198          )
   199  
   200          # Step 4, confirm load balancing is still working.
   201          client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
   202          client.sendto("ping".encode('utf-8'), (host, port))
   203          data, address = client.recvfrom(4096)
   204          endpoint = data.decode()
   205          print(f'response: {endpoint}')
   206          client.close()
   207          assert endpoint is not ""
   208  
   209          # cleanup
   210          delete_ts(kube_apis.custom_objects, ts_resource, transport_server_setup.namespace)
   211          transport_server_file = f"{TEST_DATA}/transport-server-udp-load-balance/standard/transport-server.yaml"
   212          create_ts_from_yaml(
   213              kube_apis.custom_objects, transport_server_file, transport_server_setup.namespace
   214          )
   215          wait_before_test()
   216  
   217      @pytest.mark.parametrize("file", ["wrong-port-transport-server.yaml", "missing-service-transport-server.yaml"])
   218      def test_udp_request_fails(
   219              self, kube_apis, crd_ingress_controller, transport_server_setup, file
   220      ):
   221          patch_src = f"{TEST_DATA}/transport-server-udp-load-balance/{file}"
   222          patch_ts(
   223              kube_apis.custom_objects,
   224              transport_server_setup.name,
   225              patch_src,
   226              transport_server_setup.namespace,
   227          )
   228          # 4s includes 3s timeout for a health check to fail in case a backend pod doesn't respond or responds with
   229          # an unexpected response
   230          wait_before_test()
   231  
   232          port = transport_server_setup.public_endpoint.udp_server_port
   233          host = transport_server_setup.public_endpoint.public_ip
   234  
   235          print(f"sending udp requests to: {host}:{port}")
   236          for i in range(3):
   237              client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
   238              client.settimeout(2)
   239              client.sendto("ping".encode('utf-8'), (host, port))
   240              try:
   241                  client.recvfrom(4096)
   242                  # it should timeout
   243                  print(f"incorrect config from {file} should have resulted in an error")
   244                  assert False
   245              except socket.timeout:
   246                  print("successfully timed out")
   247              client.close()
   248  
   249          self.restore_ts(kube_apis, transport_server_setup)
   250  
   251      @pytest.mark.skip_for_nginx_oss
   252      def test_udp_passing_healthcheck_with_match(
   253              self, kube_apis, crd_ingress_controller, transport_server_setup, ingress_controller_prerequisites
   254      ):
   255          """
   256          Configure a passing health check and check that all backend pods return responses.
   257          """
   258  
   259          # Step 1 - configure a passing health check
   260  
   261          patch_src = f"{TEST_DATA}/transport-server-udp-load-balance/passing-hc-transport-server.yaml"
   262          patch_ts(
   263              kube_apis.custom_objects,
   264              transport_server_setup.name,
   265              patch_src,
   266              transport_server_setup.namespace,
   267          )
   268          # 4s includes 3s timeout for a health check to fail in case a backend pod doesn't respond or responds with
   269          # an unexpected response
   270          wait_before_test(4)
   271  
   272          result_conf = get_ts_nginx_template_conf(
   273              kube_apis.v1,
   274              transport_server_setup.namespace,
   275              transport_server_setup.name,
   276              transport_server_setup.ingress_pod_name,
   277              ingress_controller_prerequisites.namespace
   278          )
   279  
   280          match = f"match_ts_{transport_server_setup.namespace}_transport-server_udp-app"
   281  
   282          assert "health_check interval=5s port=3334" in result_conf
   283          assert f"passes=1 jitter=0s fails=1 udp match={match}" in result_conf
   284          assert "health_check_timeout 3s;"
   285          assert 'send "health"' in result_conf
   286          assert 'expect  "healthy"' in result_conf
   287  
   288          # Step 2 - confirm load balancing works
   289  
   290          port = transport_server_setup.public_endpoint.udp_server_port
   291          host = transport_server_setup.public_endpoint.public_ip
   292  
   293          print(f"sending udp requests to: {host}:{port}")
   294  
   295          retry = 0
   296          endpoints = {}
   297          while(len(endpoints) is not 3 and retry <=30):
   298              for i in range(20):
   299                  client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
   300                  client.sendto("ping".encode('utf-8'), (host, port))
   301                  data, address = client.recvfrom(4096)
   302                  endpoint = data.decode()
   303                  print(f' req number {i}; response: {endpoint}')
   304                  if endpoint not in endpoints:
   305                      endpoints[endpoint] = 1
   306                  else:
   307                      endpoints[endpoint] = endpoints[endpoint] + 1
   308                  client.close()
   309              retry += 1
   310              wait_before_test(1)
   311              print(f"Retry #{retry}")
   312  
   313          assert len(endpoints) is 3
   314  
   315          # Step 3 - restore
   316  
   317          self.restore_ts(kube_apis, transport_server_setup)
   318  
   319      @pytest.mark.skip_for_nginx_oss
   320      def test_udp_failing_healthcheck_with_match(
   321              self, kube_apis, crd_ingress_controller, transport_server_setup, ingress_controller_prerequisites
   322      ):
   323          """
   324          Configure a failing health check and check that NGINX Plus doesn't respond.
   325          """
   326  
   327          # Step 1 - configure a failing health check
   328  
   329          patch_src = f"{TEST_DATA}/transport-server-udp-load-balance/failing-hc-transport-server.yaml"
   330          patch_ts(
   331              kube_apis.custom_objects,
   332              transport_server_setup.name,
   333              patch_src,
   334              transport_server_setup.namespace,
   335          )
   336          wait_before_test(4)
   337  
   338          result_conf = get_ts_nginx_template_conf(
   339              kube_apis.v1,
   340              transport_server_setup.namespace,
   341              transport_server_setup.name,
   342              transport_server_setup.ingress_pod_name,
   343              ingress_controller_prerequisites.namespace
   344          )
   345  
   346          match = f"match_ts_{transport_server_setup.namespace}_transport-server_udp-app"
   347  
   348          assert "health_check interval=5s port=3334" in result_conf
   349          assert f"passes=1 jitter=0s fails=1 udp match={match}" in result_conf
   350          assert "health_check_timeout 3s;"
   351          assert 'send "health"' in result_conf
   352          assert 'expect  "unmatched"' in result_conf
   353  
   354          # Step 2 - confirm load balancing doesn't work
   355  
   356          port = transport_server_setup.public_endpoint.udp_server_port
   357          host = transport_server_setup.public_endpoint.public_ip
   358  
   359          client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
   360          client.settimeout(2)
   361          client.sendto("ping".encode('utf-8'), (host, port))
   362          try:
   363              # client.recvfrom(4096)
   364              data, address = client.recvfrom(4096)
   365              endpoint = data.decode()
   366              print(f' req number  response: {endpoint}')
   367              # it should timeout
   368              pytest.fail("expected a timeout")
   369          except socket.timeout:
   370              print("successfully timed out")
   371          client.close()
   372  
   373          # Step 3 - restore
   374  
   375          self.restore_ts(kube_apis, transport_server_setup)