1# Copyright 2022 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 List
16
17from absl import flags
18from absl.testing import absltest
19
20from framework import xds_k8s_flags
21from framework import xds_k8s_testcase
22from framework import xds_url_map_testcase
23from framework.helpers import skips
24
25logger = logging.getLogger(__name__)
26flags.adopt_module_key_flags(xds_k8s_testcase)
27flags.mark_flag_as_required('server_image_canonical')
28
29# Type aliases
30RpcTypeUnaryCall = xds_url_map_testcase.RpcTypeUnaryCall
31RpcTypeEmptyCall = xds_url_map_testcase.RpcTypeEmptyCall
32_XdsTestServer = xds_k8s_testcase.XdsTestServer
33_XdsTestClient = xds_k8s_testcase.XdsTestClient
34_Lang = skips.Lang
35
36# Testing consts
37_QPS = 100
38_REPLICA_COUNT = 5
39
40
41class OutlierDetectionTest(xds_k8s_testcase.RegularXdsKubernetesTestCase):
42    """
43    Implementation of https://github.com/grpc/grpc/blob/master/doc/xds-test-descriptions.md#outlier_detection
44
45    This test verifies that the client applies the outlier detection
46    configuration and temporarily drops traffic to a server that fails
47    requests.
48    """
49
50    @classmethod
51    def setUpClass(cls):
52        """Force the java test server for languages not yet supporting
53        the `rpc-behavior` feature.
54
55        https://github.com/grpc/grpc/blob/master/doc/xds-test-descriptions.md#server
56        """
57        super().setUpClass()
58        if cls.lang_spec.client_lang != _Lang.JAVA:
59            # TODO(mlumish): Once rpc-behavior supported by a language, make the
60            #                override version-conditional.
61            cls.server_image = xds_k8s_flags.SERVER_IMAGE_CANONICAL.value
62
63    @staticmethod
64    def is_supported(config: skips.TestConfig) -> bool:
65        if config.client_lang in _Lang.CPP | _Lang.PYTHON:
66            return config.version_gte('v1.48.x')
67        if config.client_lang == _Lang.JAVA:
68            return config.version_gte('v1.49.x')
69        if config.client_lang == _Lang.NODE:
70            return config.version_gte('v1.6.x')
71        if config.client_lang == _Lang.GO:
72            # TODO(zasweq): Update when the feature makes in a version branch.
73            return config.version_gte('master')
74        return False
75
76    def test_outlier_detection(self) -> None:
77
78        with self.subTest('00_create_health_check'):
79            self.td.create_health_check()
80
81        with self.subTest('01_create_backend_service'):
82            self.td.create_backend_service(
83                outlier_detection={
84                    'interval': {
85                        'seconds': 2,
86                        'nanos': 0
87                    },
88                    'successRateRequestVolume': 20
89                })
90
91        with self.subTest('02_create_url_map'):
92            self.td.create_url_map(self.server_xds_host, self.server_xds_port)
93
94        with self.subTest('03_create_target_proxy'):
95            self.td.create_target_proxy()
96
97        with self.subTest('04_create_forwarding_rule'):
98            self.td.create_forwarding_rule(self.server_xds_port)
99
100        test_servers: List[_XdsTestServer]
101        with self.subTest('05_start_test_servers'):
102            test_servers = self.startTestServers(replica_count=_REPLICA_COUNT)
103
104        with self.subTest('06_add_server_backends_to_backend_services'):
105            self.setupServerBackends()
106
107        test_client: _XdsTestClient
108        with self.subTest('07_start_test_client'):
109            test_client = self.startTestClient(test_servers[0], qps=_QPS)
110
111        with self.subTest('08_test_client_xds_config_exists'):
112            self.assertXdsConfigExists(test_client)
113
114        with self.subTest('09_test_servers_received_rpcs_from_test_client'):
115            self.assertRpcsEventuallyGoToGivenServers(test_client, test_servers)
116
117        rpc_types = (RpcTypeUnaryCall,)
118        with self.subTest('10_chosen_server_removed_by_outlier_detection'):
119            test_client.update_config.configure(
120                rpc_types=rpc_types,
121                metadata=(
122                    (RpcTypeUnaryCall, 'rpc-behavior',
123                     f'hostname={test_servers[0].hostname} error-code-2'),))
124            self.assertRpcsEventuallyGoToGivenServers(test_client,
125                                                      test_servers[1:])
126
127        with self.subTest('11_ejected_server_returned_after_failures_stopped'):
128            test_client.update_config.configure(rpc_types=rpc_types)
129            self.assertRpcsEventuallyGoToGivenServers(test_client, test_servers)
130
131
132if __name__ == '__main__':
133    absltest.main(failfast=True)
134