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)