Skip to content

Commit 5b0e4ea

Browse files
authored
Merge pull request #2732 from rossignolloic/fix/discovery_fail_when_api_unavailable
fix: discovery fail when at least one api is unavailable
2 parents 229d876 + 3569b6f commit 5b0e4ea

File tree

5 files changed

+219
-6
lines changed

5 files changed

+219
-6
lines changed

extended/src/main/java/io/kubernetes/client/extended/kubectl/KubectlApiResources.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,24 @@
1515
import io.kubernetes.client.Discovery;
1616
import io.kubernetes.client.extended.kubectl.exception.KubectlException;
1717
import io.kubernetes.client.openapi.ApiException;
18+
import io.kubernetes.client.util.exception.IncompleteDiscoveryException;
1819
import java.util.Set;
20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
1922

2023
public class KubectlApiResources extends Kubectl.ApiClientBuilder<KubectlApiResources>
2124
implements Kubectl.Executable<Set<Discovery.APIResource>> {
2225

26+
private static final Logger LOGGER = LoggerFactory.getLogger(KubectlApiResources.class);
27+
2328
@Override
2429
public Set<Discovery.APIResource> execute() throws KubectlException {
2530
Discovery discovery = new Discovery(this.apiClient);
2631
try {
2732
return discovery.findAll();
33+
} catch (IncompleteDiscoveryException e) {
34+
LOGGER.warn("Error while getting all api resources, some resources will be missing", e);
35+
return e.getDiscoveredResources();
2836
} catch (ApiException e) {
2937
throw new KubectlException(e);
3038
}

util/src/main/java/io/kubernetes/client/Discovery.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import io.kubernetes.client.openapi.models.V1APIGroupList;
2121
import io.kubernetes.client.openapi.models.V1APIResourceList;
2222
import io.kubernetes.client.openapi.models.V1APIVersions;
23+
import io.kubernetes.client.util.exception.IncompleteDiscoveryException;
2324
import java.util.ArrayList;
2425
import java.util.Arrays;
2526
import java.util.Collections;
@@ -50,12 +51,28 @@ public Set<APIResource> findAll() throws ApiException {
5051
for (String version : legacyCoreApi().getVersions()) {
5152
allResources.addAll(findAll("", Arrays.asList(version), version, "/api/" + version));
5253
}
54+
IncompleteDiscoveryException incompleteDiscoveryException = null;
5355
for (V1APIGroup group : groupDiscovery("/apis").getGroups()) {
54-
allResources.addAll(
55-
findAll(
56-
group.getName(),
57-
group.getVersions().stream().map(v -> v.getVersion()).collect(Collectors.toList()),
58-
group.getPreferredVersion().getVersion()));
56+
try {
57+
allResources.addAll(
58+
findAll(
59+
group.getName(),
60+
group.getVersions().stream().map(v -> v.getVersion()).collect(Collectors.toList()),
61+
group.getPreferredVersion().getVersion()));
62+
} catch (ApiException e){
63+
IncompleteDiscoveryException resourceDiscoveryException = new IncompleteDiscoveryException(
64+
String.format("Unable to retrieve the complete list of server APIs: %s/%s : %s",
65+
group.getName(), group.getPreferredVersion().getVersion(), e.getResponseBody()),
66+
e, allResources);
67+
if (incompleteDiscoveryException == null) {
68+
incompleteDiscoveryException = resourceDiscoveryException;
69+
}else {
70+
incompleteDiscoveryException.addSuppressed(resourceDiscoveryException);
71+
}
72+
}
73+
}
74+
if (incompleteDiscoveryException != null) {
75+
throw incompleteDiscoveryException;
5976
}
6077
return allResources;
6178
}

util/src/main/java/io/kubernetes/client/util/ModelMapper.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import io.kubernetes.client.common.KubernetesListObject;
1919
import io.kubernetes.client.common.KubernetesObject;
2020
import io.kubernetes.client.openapi.ApiException;
21+
import io.kubernetes.client.util.exception.IncompleteDiscoveryException;
2122
import java.io.File;
2223

2324
import java.io.IOException;
@@ -266,7 +267,13 @@ public static Set<Discovery.APIResource> refresh(Discovery discovery, Duration r
266267
return lastAPIDiscovery;
267268
}
268269

269-
Set<Discovery.APIResource> apiResources = discovery.findAll();
270+
Set<Discovery.APIResource> apiResources = null;
271+
try {
272+
apiResources = discovery.findAll();
273+
} catch (IncompleteDiscoveryException e) {
274+
logger.warn("Error while getting all api resources, some api resources will not be refreshed", e);
275+
apiResources = e.getDiscoveredResources();
276+
}
270277

271278
for (Discovery.APIResource apiResource : apiResources) {
272279
for (String version : apiResource.getVersions()) {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Copyright 2023 The Kubernetes Authors.
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+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
package io.kubernetes.client.util.exception;
14+
15+
import io.kubernetes.client.Discovery.APIResource;
16+
import io.kubernetes.client.openapi.ApiException;
17+
import java.util.Set;
18+
19+
public class IncompleteDiscoveryException extends ApiException {
20+
21+
private static final long serialVersionUID = 1L;
22+
23+
private final transient Set<APIResource> discoveredResources;
24+
25+
public IncompleteDiscoveryException(String message, ApiException cause, Set<APIResource> discoveredResources) {
26+
super(message, cause, cause.getCode(), cause.getResponseHeaders(), cause.getResponseBody());
27+
this.discoveredResources = discoveredResources;
28+
}
29+
30+
public Set<APIResource> getDiscoveredResources() {
31+
return discoveredResources;
32+
}
33+
34+
}

util/src/test/java/io/kubernetes/client/DiscoveryTest.java

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,24 @@
1818
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
1919
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
2020
import static org.junit.Assert.assertEquals;
21+
import static org.junit.Assert.fail;
2122

2223
import com.github.tomakehurst.wiremock.junit.WireMockRule;
24+
import io.kubernetes.client.Discovery.APIResource;
2325
import io.kubernetes.client.openapi.ApiClient;
2426
import io.kubernetes.client.openapi.ApiException;
27+
import io.kubernetes.client.openapi.models.V1APIGroup;
28+
import io.kubernetes.client.openapi.models.V1APIGroupList;
2529
import io.kubernetes.client.openapi.models.V1APIResource;
2630
import io.kubernetes.client.openapi.models.V1APIResourceList;
2731
import io.kubernetes.client.openapi.models.V1APIVersions;
32+
import io.kubernetes.client.openapi.models.V1GroupVersionForDiscovery;
2833
import io.kubernetes.client.util.ClientBuilder;
34+
import io.kubernetes.client.util.exception.IncompleteDiscoveryException;
2935
import java.io.IOException;
36+
import java.util.ArrayList;
3037
import java.util.Arrays;
38+
import java.util.List;
3139
import java.util.Set;
3240
import org.junit.Before;
3341
import org.junit.Rule;
@@ -101,4 +109,143 @@ public void testGroupResourcesByName() {
101109
assertEquals(true, meow.getNamespaced());
102110
assertEquals("mouse", meow.getSubResources().get(0));
103111
}
112+
113+
@Test
114+
public void findAllShouldReturnAllApiResourceWhenAllResourceDiscoveryApiResponseAreSuccess() throws ApiException {
115+
Discovery discovery = new Discovery(apiClient);
116+
117+
wireMockRule.stubFor(
118+
get("/api")
119+
.willReturn(
120+
aResponse()
121+
.withStatus(200)
122+
.withHeader("Content-Type", "application/json")
123+
.withBody(
124+
apiClient
125+
.getJSON()
126+
.serialize(new V1APIVersions()))));
127+
128+
String group = "test.com";
129+
String version = "v1";
130+
String path="/apis/"+group+'/'+version;
131+
132+
wireMockRule.stubFor(
133+
get("/apis")
134+
.willReturn(
135+
aResponse()
136+
.withStatus(200)
137+
.withHeader("Content-Type", "application/json")
138+
.withBody(
139+
apiClient
140+
.getJSON()
141+
.serialize(new V1APIGroupList()
142+
.addGroupsItem(new V1APIGroup()
143+
.name(group)
144+
.preferredVersion(new V1GroupVersionForDiscovery().version(version))
145+
.versions(Arrays.asList(
146+
new V1GroupVersionForDiscovery().version(version))))))));
147+
148+
wireMockRule.stubFor(
149+
get(urlPathEqualTo(path))
150+
.willReturn(
151+
aResponse()
152+
.withStatus(200)
153+
.withHeader("Content-Type", "application/json")
154+
.withBody(
155+
apiClient
156+
.getJSON()
157+
.serialize(new V1APIResourceList()
158+
.resources(
159+
Arrays.asList(
160+
new V1APIResource()
161+
.name("first"),
162+
new V1APIResource()
163+
.name("second")))))));
164+
165+
List<String> versions = new ArrayList<>();
166+
versions.add(version);
167+
Set<APIResource> apiResources = discovery.findAll();
168+
wireMockRule.verify(1, getRequestedFor(urlPathEqualTo(path)));
169+
assertEquals(2, apiResources.size());
170+
}
171+
172+
@Test
173+
public void findAllShouldThrowImcompleteDiscoveryExceptionWhenAtLeastOneResourceDiscoveryApiResponseIsNotSuccess() throws ApiException {
174+
Discovery discovery = new Discovery(apiClient);
175+
176+
wireMockRule.stubFor(
177+
get("/api")
178+
.willReturn(
179+
aResponse()
180+
.withStatus(200)
181+
.withHeader("Content-Type", "application/json")
182+
.withBody(
183+
apiClient
184+
.getJSON()
185+
.serialize(new V1APIVersions()))));
186+
187+
String groupSuccess = "test.com";
188+
String version = "v1";
189+
String pathSuccess="/apis/"+groupSuccess+'/'+version;
190+
191+
String groupError = "testError.com";
192+
String pathError="/apis/"+groupError+'/'+version;
193+
194+
wireMockRule.stubFor(
195+
get("/apis")
196+
.willReturn(
197+
aResponse()
198+
.withStatus(200)
199+
.withHeader("Content-Type", "application/json")
200+
.withBody(
201+
apiClient
202+
.getJSON()
203+
.serialize(new V1APIGroupList()
204+
.addGroupsItem(new V1APIGroup()
205+
.name(groupError)
206+
.preferredVersion(new V1GroupVersionForDiscovery().version(version))
207+
.versions(Arrays.asList(
208+
new V1GroupVersionForDiscovery().version(version))))
209+
.addGroupsItem(new V1APIGroup()
210+
.name(groupSuccess)
211+
.preferredVersion(new V1GroupVersionForDiscovery().version(version))
212+
.versions(Arrays.asList(
213+
new V1GroupVersionForDiscovery().version(version))))))));
214+
215+
wireMockRule.stubFor(
216+
get(urlPathEqualTo(pathError))
217+
.willReturn(
218+
aResponse()
219+
.withStatus(503)));
220+
221+
wireMockRule.stubFor(
222+
get(urlPathEqualTo(pathSuccess))
223+
.willReturn(
224+
aResponse()
225+
.withStatus(200)
226+
.withHeader("Content-Type", "application/json")
227+
.withBody(
228+
apiClient
229+
.getJSON()
230+
.serialize(new V1APIResourceList()
231+
.resources(
232+
Arrays.asList(
233+
new V1APIResource()
234+
.name("first"),
235+
new V1APIResource()
236+
.name("second")))))));
237+
238+
List<String> versions = new ArrayList<>();
239+
versions.add(version);
240+
Set<APIResource> apiResources = null;
241+
try{
242+
discovery.findAll();
243+
fail("Should have throw ImcompleteDiscoveryException");
244+
} catch (IncompleteDiscoveryException e) {
245+
apiResources = e.getDiscoveredResources();
246+
}
247+
wireMockRule.verify(1, getRequestedFor(urlPathEqualTo(pathError)));
248+
wireMockRule.verify(1, getRequestedFor(urlPathEqualTo(pathSuccess)));
249+
assertEquals(2, apiResources.size());
250+
}
104251
}

0 commit comments

Comments
 (0)