k8s.io/apiserver@v0.31.1/pkg/storage/feature/feature_support_checker_test.go (about) 1 /* 2 Copyright 2024 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package feature 18 19 import ( 20 "context" 21 "fmt" 22 "testing" 23 24 "github.com/stretchr/testify/assert" 25 clientv3 "go.etcd.io/etcd/client/v3" 26 "k8s.io/apiserver/pkg/storage" 27 ) 28 29 type mockEndpointVersion struct { 30 Endpoint string 31 Version string 32 Error error 33 } 34 35 // MockEtcdClient is a mock implementation of the EtcdClientInterface interface. 36 type MockEtcdClient struct { 37 EndpointVersion []mockEndpointVersion 38 } 39 40 func (m MockEtcdClient) getEndpoints() []string { 41 var endpoints []string 42 for _, ev := range m.EndpointVersion { 43 endpoints = append(endpoints, ev.Endpoint) 44 } 45 return endpoints 46 } 47 48 func (m MockEtcdClient) getVersion(endpoint string) (string, error) { 49 for _, ev := range m.EndpointVersion { 50 if ev.Endpoint == endpoint { 51 return ev.Version, ev.Error 52 } 53 } 54 // Never should happen, unless tests having a problem. 55 return "", fmt.Errorf("No version found") 56 } 57 58 func (m *MockEtcdClient) Endpoints() []string { 59 return m.getEndpoints() 60 } 61 62 // Status returns a mock status response. 63 func (m *MockEtcdClient) Status(ctx context.Context, endpoint string) (*clientv3.StatusResponse, error) { 64 version, err := m.getVersion(endpoint) 65 if err != nil { 66 return nil, err 67 } 68 // Return a mock status response 69 return &clientv3.StatusResponse{ 70 Version: version, 71 }, nil 72 } 73 74 func TestSupports(t *testing.T) { 75 tests := []struct { 76 testName string 77 featureName string 78 expectedResult bool 79 }{ 80 { 81 testName: "Disabled - with unknown feature", 82 featureName: "some unknown feature", 83 }, 84 { 85 testName: "Disabled - with empty feature", 86 featureName: "", 87 }, 88 { 89 testName: "Disabled - default", 90 featureName: storage.RequestWatchProgress, 91 expectedResult: false, 92 }, 93 } 94 95 for _, tt := range tests { 96 t.Run(tt.testName, func(t *testing.T) { 97 var testFeatureSupportChecker FeatureSupportChecker = newDefaultFeatureSupportChecker() 98 99 supported := testFeatureSupportChecker.Supports(tt.featureName) 100 101 assert.Equal(t, tt.expectedResult, supported) 102 }) 103 } 104 } 105 106 func TestSupportsRequestWatchProgress(t *testing.T) { 107 type testCase struct { 108 endpointsVersion []mockEndpointVersion 109 expectedResult bool 110 expectedError error 111 } 112 tests := []struct { 113 testName string 114 rounds []testCase 115 }{ 116 { 117 testName: "Disabled - default disabled", 118 rounds: []testCase{{expectedResult: false}}, 119 }, 120 { 121 testName: "Enabled - supported versions bound", 122 rounds: []testCase{ 123 {endpointsVersion: []mockEndpointVersion{ 124 {Version: "3.4.31", Endpoint: "localhost:2390"}}, 125 expectedResult: true, 126 }, 127 {endpointsVersion: []mockEndpointVersion{ 128 {Version: "3.5.13", Endpoint: "localhost:2391"}}, 129 expectedResult: true, 130 }, 131 {endpointsVersion: []mockEndpointVersion{ 132 {Version: "3.6.0", Endpoint: "localhost:2392"}}, 133 expectedResult: true}}, 134 }, 135 { 136 testName: "Disabled - supported versions bound, 3.4.30", 137 rounds: []testCase{ 138 {endpointsVersion: []mockEndpointVersion{ 139 {Version: "3.4.30", Endpoint: "localhost:2390"}}, 140 expectedResult: false}}, 141 }, 142 { 143 testName: "Disabled - supported versions bound, 3.5.0", 144 rounds: []testCase{ 145 {endpointsVersion: []mockEndpointVersion{ 146 {Version: "3.5.0", Endpoint: "localhost:2390"}}, 147 expectedResult: false}}, 148 }, 149 { 150 testName: "Disabled - supported versions bound, 3.5.12", 151 rounds: []testCase{ 152 {endpointsVersion: []mockEndpointVersion{ 153 {Version: "3.5.12", Endpoint: "localhost:2390"}}, 154 expectedResult: false}}, 155 }, 156 { 157 testName: "Disabled - disables if called with one client doesn't support it", 158 rounds: []testCase{ 159 {endpointsVersion: []mockEndpointVersion{ 160 {Version: "3.5.13", Endpoint: "localhost:2390"}, 161 {Version: "3.5.10", Endpoint: "localhost:2391"}}, 162 expectedResult: false}}, 163 }, 164 { 165 testName: "Disabled - disables if called with all client doesn't support it", 166 rounds: []testCase{ 167 {endpointsVersion: []mockEndpointVersion{ 168 {Version: "3.5.9", Endpoint: "localhost:2390"}, 169 {Version: "3.5.10", Endpoint: "localhost:2391"}}, 170 expectedResult: false}}, 171 }, 172 { 173 testName: "Enabled - if provided client has at least one endpoint that supports it and no client that doesn't", 174 rounds: []testCase{ 175 {endpointsVersion: []mockEndpointVersion{ 176 {Version: "3.4.31", Endpoint: "localhost:2390"}, 177 {Version: "3.5.13", Endpoint: "localhost:2391"}, 178 {Version: "3.5.14", Endpoint: "localhost:2392"}, 179 {Version: "3.6.0", Endpoint: "localhost:2393"}}, 180 expectedResult: true}}, 181 }, 182 { 183 testName: "Disabled - cannot be re-enabled", 184 rounds: []testCase{ 185 {endpointsVersion: []mockEndpointVersion{ 186 {Version: "3.4.0", Endpoint: "localhost:2390"}, 187 {Version: "3.4.1", Endpoint: "localhost:2391"}}, 188 expectedResult: false}, 189 {endpointsVersion: []mockEndpointVersion{ 190 {Version: "3.6.0", Endpoint: "localhost:2392"}}, 191 expectedResult: false}}, 192 }, 193 { 194 testName: "Enabled - one client supports it and later disabled it with second client", 195 rounds: []testCase{ 196 {endpointsVersion: []mockEndpointVersion{ 197 {Version: "3.6.0", Endpoint: "localhost:2390"}, 198 {Version: "3.5.14", Endpoint: "localhost:2391"}}, 199 expectedResult: true}, 200 {endpointsVersion: []mockEndpointVersion{ 201 {Version: "3.4.0", Endpoint: "localhost:2392"}}, 202 expectedResult: false}}, 203 }, 204 { 205 testName: "Disabled - malformed version would disable the supported cluster and can not be re-enabled again", 206 rounds: []testCase{ 207 {endpointsVersion: []mockEndpointVersion{ 208 {Version: "3.6.0", Endpoint: "localhost:2390"}}, 209 expectedResult: true, 210 }, 211 {endpointsVersion: []mockEndpointVersion{ 212 {Version: "3.4.--aaa", Endpoint: "localhost:2392"}}, 213 expectedResult: false}, 214 {endpointsVersion: []mockEndpointVersion{ 215 {Version: "3.5.13", Endpoint: "localhost:2393"}}, 216 expectedResult: false}}, 217 }, 218 { 219 testName: "Enabled - error on first client, enabled success on second client", 220 rounds: []testCase{ 221 {endpointsVersion: []mockEndpointVersion{ 222 {Version: "3.6.0", Endpoint: "localhost:2390", Error: fmt.Errorf("some error")}}, 223 expectedResult: false, 224 expectedError: fmt.Errorf("failed checking etcd version, endpoint: %q: %w", "localhost:2390", fmt.Errorf("some error")), 225 }, 226 {endpointsVersion: []mockEndpointVersion{ 227 {Version: "3.5.14", Endpoint: "localhost:2391"}}, 228 expectedResult: true}}, 229 }, 230 { 231 testName: "Disabled - enabled success on first client, error on second client, disabled success on third client", 232 rounds: []testCase{ 233 {endpointsVersion: []mockEndpointVersion{ 234 {Version: "3.6.0", Endpoint: "localhost:2390"}}, 235 expectedResult: true, 236 }, 237 {endpointsVersion: []mockEndpointVersion{ 238 {Version: "3.6.0", Endpoint: "localhost:2391", Error: fmt.Errorf("some error")}}, 239 expectedResult: true, 240 expectedError: fmt.Errorf("failed checking etcd version, endpoint: %q: %w", "localhost:2391", fmt.Errorf("some error")), 241 }, 242 {endpointsVersion: []mockEndpointVersion{ 243 {Version: "3.5.10", Endpoint: "localhost:2392"}}, 244 expectedResult: false}}, 245 }, 246 { 247 testName: "Disabled - client doesn't have any endpoints", 248 rounds: []testCase{{endpointsVersion: []mockEndpointVersion{}, expectedResult: false}}, 249 }, 250 } 251 for _, tt := range tests { 252 t.Run(tt.testName, func(t *testing.T) { 253 var testFeatureSupportChecker = newDefaultFeatureSupportChecker() 254 for _, round := range tt.rounds { 255 // Mock Etcd client 256 mockClient := &MockEtcdClient{EndpointVersion: round.endpointsVersion} 257 ctx := context.Background() 258 259 for _, ep := range mockClient.Endpoints() { 260 err := testFeatureSupportChecker.clientSupportsRequestWatchProgress(ctx, mockClient, ep) 261 assert.Equal(t, round.expectedError, err) 262 } 263 264 supported := testFeatureSupportChecker.Supports(storage.RequestWatchProgress) 265 assert.Equal(t, round.expectedResult, supported) 266 } 267 }) 268 } 269 }