1# Copyright 2021 The gRPC Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14import logging 15from typing import Tuple 16 17from absl import flags 18from absl.testing import absltest 19import grpc 20 21from framework import xds_url_map_testcase 22from framework.helpers import skips 23from framework.test_app import client_app 24 25# Type aliases 26HostRule = xds_url_map_testcase.HostRule 27PathMatcher = xds_url_map_testcase.PathMatcher 28GcpResourceManager = xds_url_map_testcase.GcpResourceManager 29DumpedXdsConfig = xds_url_map_testcase.DumpedXdsConfig 30RpcTypeUnaryCall = xds_url_map_testcase.RpcTypeUnaryCall 31XdsTestClient = client_app.XdsTestClient 32ExpectedResult = xds_url_map_testcase.ExpectedResult 33_Lang = skips.Lang 34 35logger = logging.getLogger(__name__) 36flags.adopt_module_key_flags(xds_url_map_testcase) 37 38# The first batch of RPCs don't count towards the result of test case. They are 39# meant to prove the communication between driver and client is fine. 40_NUM_RPCS = 10 41_LENGTH_OF_RPC_SENDING_SEC = 16 42# We are using sleep to synchronize test driver and the client... Even though 43# the client is sending at QPS rate, we can't assert that exactly QPS * 44# SLEEP_DURATION number of RPC is finished. The final completed RPC might be 45# slightly more or less. 46_NON_RANDOM_ERROR_TOLERANCE = 0.01 47_RPC_BEHAVIOR_HEADER_NAME = 'rpc-behavior' 48 49 50def _build_retry_route_rule(retryConditions, num_retries): 51 return { 52 'priority': 0, 53 'matchRules': [{ 54 'fullPathMatch': '/grpc.testing.TestService/UnaryCall' 55 }], 56 'service': GcpResourceManager().default_backend_service(), 57 'routeAction': { 58 'retryPolicy': { 59 'retryConditions': retryConditions, 60 'numRetries': num_retries, 61 } 62 }, 63 } 64 65 66def _is_supported(config: skips.TestConfig) -> bool: 67 # Per "Retry" in 68 # https://github.com/grpc/grpc/blob/master/doc/grpc_xds_features.md 69 if config.client_lang in _Lang.CPP | _Lang.JAVA | _Lang.PYTHON: 70 return config.version_gte('v1.40.x') 71 elif config.client_lang == _Lang.GO: 72 return config.version_gte('v1.41.x') 73 elif config.client_lang == _Lang.NODE: 74 return config.version_gte('v1.8.x') 75 return True 76 77 78class TestRetryUpTo3AttemptsAndFail(xds_url_map_testcase.XdsUrlMapTestCase): 79 80 @staticmethod 81 def is_supported(config: skips.TestConfig) -> bool: 82 return _is_supported(config) 83 84 @staticmethod 85 def url_map_change( 86 host_rule: HostRule, 87 path_matcher: PathMatcher) -> Tuple[HostRule, PathMatcher]: 88 path_matcher["routeRules"] = [ 89 _build_retry_route_rule(retryConditions=["unavailable"], 90 num_retries=3) 91 ] 92 return host_rule, path_matcher 93 94 def xds_config_validate(self, xds_config: DumpedXdsConfig): 95 self.assertNumEndpoints(xds_config, 1) 96 retry_config = xds_config.rds['virtualHosts'][0]['routes'][0]['route'][ 97 'retryPolicy'] 98 self.assertEqual(3, retry_config['numRetries']) 99 self.assertEqual('unavailable', retry_config['retryOn']) 100 101 def rpc_distribution_validate(self, test_client: XdsTestClient): 102 self.configure_and_send(test_client, 103 rpc_types=(RpcTypeUnaryCall,), 104 metadata=[ 105 (RpcTypeUnaryCall, 106 _RPC_BEHAVIOR_HEADER_NAME, 107 'succeed-on-retry-attempt-4,error-code-14') 108 ], 109 num_rpcs=_NUM_RPCS) 110 self.assertRpcStatusCode(test_client, 111 expected=(ExpectedResult( 112 rpc_type=RpcTypeUnaryCall, 113 status_code=grpc.StatusCode.UNAVAILABLE, 114 ratio=1),), 115 length=_LENGTH_OF_RPC_SENDING_SEC, 116 tolerance=_NON_RANDOM_ERROR_TOLERANCE) 117 118 119class TestRetryUpTo4AttemptsAndSucceed(xds_url_map_testcase.XdsUrlMapTestCase): 120 121 @staticmethod 122 def is_supported(config: skips.TestConfig) -> bool: 123 return _is_supported(config) 124 125 @staticmethod 126 def url_map_change( 127 host_rule: HostRule, 128 path_matcher: PathMatcher) -> Tuple[HostRule, PathMatcher]: 129 path_matcher["routeRules"] = [ 130 _build_retry_route_rule(retryConditions=["unavailable"], 131 num_retries=4) 132 ] 133 return host_rule, path_matcher 134 135 def xds_config_validate(self, xds_config: DumpedXdsConfig): 136 self.assertNumEndpoints(xds_config, 1) 137 retry_config = xds_config.rds['virtualHosts'][0]['routes'][0]['route'][ 138 'retryPolicy'] 139 self.assertEqual(4, retry_config['numRetries']) 140 self.assertEqual('unavailable', retry_config['retryOn']) 141 142 def rpc_distribution_validate(self, test_client: XdsTestClient): 143 self.configure_and_send(test_client, 144 rpc_types=(RpcTypeUnaryCall,), 145 metadata=[ 146 (RpcTypeUnaryCall, 147 _RPC_BEHAVIOR_HEADER_NAME, 148 'succeed-on-retry-attempt-4,error-code-14') 149 ], 150 num_rpcs=_NUM_RPCS) 151 self.assertRpcStatusCode(test_client, 152 expected=(ExpectedResult( 153 rpc_type=RpcTypeUnaryCall, 154 status_code=grpc.StatusCode.OK, 155 ratio=1),), 156 length=_LENGTH_OF_RPC_SENDING_SEC, 157 tolerance=_NON_RANDOM_ERROR_TOLERANCE) 158 159 160if __name__ == '__main__': 161 absltest.main() 162