Skip to content

Commit 67b8bb8

Browse files
Merge pull request #2501 from FWiesner/issue/2292
2 parents aaabfee + 2aaa79b commit 67b8bb8

File tree

14 files changed

+610
-91
lines changed

14 files changed

+610
-91
lines changed

kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/base/BaseOperation.java

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
package io.fabric8.kubernetes.client.dsl.base;
1717

1818
import io.fabric8.kubernetes.api.model.ObjectReference;
19-
import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil;
19+
import io.fabric8.kubernetes.client.utils.CreateOrReplaceHelper;
2020
import org.slf4j.Logger;
2121
import org.slf4j.LoggerFactory;
2222

@@ -404,20 +404,22 @@ public final T createOrReplace(T... items) {
404404

405405
return withName(itemToCreateOrReplace.getMetadata().getName()).createOrReplace(itemToCreateOrReplace);
406406
}
407+
T finalItemToCreateOrReplace = itemToCreateOrReplace;
408+
CreateOrReplaceHelper<T> createOrReplaceHelper = new CreateOrReplaceHelper<>(
409+
this::create,
410+
this::replace,
411+
m -> {
412+
try {
413+
return waitUntilCondition(Objects::nonNull, 1, TimeUnit.SECONDS);
414+
} catch (InterruptedException interruptedException) {
415+
interruptedException.printStackTrace();
416+
}
417+
return null;
418+
},
419+
m -> fromServer().get()
420+
);
407421

408-
try {
409-
// Create
410-
KubernetesResourceUtil.setResourceVersion(itemToCreateOrReplace, null);
411-
return create(itemToCreateOrReplace);
412-
} catch (KubernetesClientException exception) {
413-
if (exception.getCode() != HttpURLConnection.HTTP_CONFLICT) {
414-
throw exception;
415-
}
416-
// Conflict; Do Replace
417-
final T itemFromServer = fromServer().get();
418-
KubernetesResourceUtil.setResourceVersion(itemToCreateOrReplace, KubernetesResourceUtil.getResourceVersion(itemFromServer));
419-
return replace(itemToCreateOrReplace);
420-
}
422+
return createOrReplaceHelper.createOrReplace(finalItemToCreateOrReplace);
421423
}
422424

423425
@Override

kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableImpl.java

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@
1717

1818
import io.fabric8.kubernetes.api.model.DeletionPropagation;
1919
import io.fabric8.kubernetes.api.model.ListOptions;
20-
import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil;
2120
import io.fabric8.kubernetes.client.utils.Utils;
2221

23-
import java.net.HttpURLConnection;
2422
import java.util.function.Predicate;
2523

2624
import java.io.ByteArrayInputStream;
@@ -57,6 +55,9 @@
5755
import io.fabric8.kubernetes.client.internal.readiness.Readiness;
5856
import okhttp3.OkHttpClient;
5957

58+
import static io.fabric8.kubernetes.client.utils.CreateOrReplaceHelper.createOrReplaceItem;
59+
import static io.fabric8.kubernetes.client.utils.DeleteAndCreateHelper.deleteAndCreateItem;
60+
6061
public class NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableImpl extends OperationSupport implements
6162
NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicable<HasMetadata, Boolean>,
6263
Waitable<HasMetadata, HasMetadata>,
@@ -135,29 +136,10 @@ public HasMetadata createOrReplace() {
135136
HasMetadata meta = acceptVisitors(asHasMetadata(item), visitors);
136137
ResourceHandler<HasMetadata, HasMetadataVisitiableBuilder> h = handlerOf(meta);
137138
String namespaceToUse = meta.getMetadata().getNamespace();
138-
139-
String resourceVersion = KubernetesResourceUtil.getResourceVersion(meta);
140-
try {
141-
// Create
142-
KubernetesResourceUtil.setResourceVersion(meta, null);
143-
return h.create(client, config, namespaceToUse, meta);
144-
} catch (KubernetesClientException exception) {
145-
if (exception.getCode() != HttpURLConnection.HTTP_CONFLICT) {
146-
throw exception;
147-
}
148-
149-
// Conflict; check deleteExisting flag otherwise replace
150-
if (Boolean.TRUE.equals(deletingExisting)) {
151-
Boolean deleted = h.delete(client, config, namespaceToUse, propagationPolicy, meta);
152-
if (Boolean.FALSE.equals(deleted)) {
153-
throw new KubernetesClientException("Failed to delete existing item:" + meta);
154-
}
155-
return h.create(client, config, namespaceToUse, meta);
156-
} else {
157-
KubernetesResourceUtil.setResourceVersion(meta, resourceVersion);
158-
return h.replace(client, config, namespaceToUse, meta);
159-
}
139+
if (Boolean.TRUE.equals(deletingExisting)) {
140+
return deleteAndCreateItem(client, config, meta, h, namespaceToUse, propagationPolicy);
160141
}
142+
return createOrReplaceItem(client, config, meta, h, namespaceToUse);
161143
}
162144

163145
@Override
@@ -327,5 +309,4 @@ private static <T> ResourceHandler handlerOf(T item) {
327309
throw new IllegalArgumentException("Could not find a registered handler for item: [" + item + "].");
328310
}
329311
}
330-
331312
}

kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl.java

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
import io.fabric8.kubernetes.client.dsl.base.OperationSupport;
3535
import io.fabric8.kubernetes.client.handlers.KubernetesListHandler;
3636
import io.fabric8.kubernetes.client.internal.readiness.Readiness;
37-
import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil;
3837
import io.fabric8.kubernetes.client.utils.Serialization;
3938
import io.fabric8.kubernetes.client.utils.Utils;
4039

@@ -45,7 +44,6 @@
4544
import java.io.ByteArrayInputStream;
4645
import java.io.IOException;
4746
import java.io.InputStream;
48-
import java.net.HttpURLConnection;
4947
import java.nio.charset.StandardCharsets;
5048
import java.util.ArrayList;
5149
import java.util.Collection;
@@ -62,6 +60,9 @@
6260
import java.util.concurrent.TimeoutException;
6361
import java.util.function.Predicate;
6462

63+
import static io.fabric8.kubernetes.client.utils.CreateOrReplaceHelper.createOrReplaceItem;
64+
import static io.fabric8.kubernetes.client.utils.DeleteAndCreateHelper.deleteAndCreateItem;
65+
6566
public class NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl extends OperationSupport implements ParameterNamespaceListVisitFromServerGetDeleteRecreateWaitApplicable<HasMetadata, Boolean>,
6667
Waitable<List<HasMetadata>, HasMetadata>, Readiable {
6768

@@ -285,29 +286,11 @@ public List<HasMetadata> createOrReplace() {
285286
List<HasMetadata> result = new ArrayList<>();
286287
for (HasMetadata meta : acceptVisitors(asHasMetadata(item, true), visitors)) {
287288
ResourceHandler<HasMetadata, HasMetadataVisitiableBuilder> h = handlerOf(meta);
288-
String namespaceToUse = meta.getMetadata().getNamespace();
289+
String namespaceToUse = meta.getMetadata().getNamespace();
289290

290-
String resourceVersion = KubernetesResourceUtil.getResourceVersion(meta);
291-
try {
292-
// Create
293-
KubernetesResourceUtil.setResourceVersion(meta, null);
294-
result.add(h.create(client, config, namespaceToUse, meta));
295-
} catch (KubernetesClientException exception) {
296-
if (exception.getCode() != HttpURLConnection.HTTP_CONFLICT) {
297-
throw exception;
298-
}
299-
300-
// Conflict; check deleteExisting flag otherwise replace
301-
if (Boolean.TRUE.equals(deletingExisting)) {
302-
Boolean deleted = h.delete(client, config, namespaceToUse, propagationPolicy, meta);
303-
if (Boolean.FALSE.equals(deleted)) {
304-
throw new KubernetesClientException("Failed to delete existing item:" + meta);
305-
}
306-
result.add(h.create(client, config, namespaceToUse, meta));
307-
} else {
308-
KubernetesResourceUtil.setResourceVersion(meta, resourceVersion);
309-
result.add(h.replace(client, config, namespaceToUse, meta));
310-
}
291+
HasMetadata createdItem = createOrReplaceOrDeleteExisting(meta, h, namespaceToUse);
292+
if (createdItem != null) {
293+
result.add(createdItem);
311294
}
312295
}
313296
return result;
@@ -455,4 +438,12 @@ private static <T> ResourceHandler handlerOf(T item) {
455438
throw new IllegalArgumentException("Could not find a registered handler for item: [" + item + "].");
456439
}
457440
}
441+
442+
private HasMetadata createOrReplaceOrDeleteExisting(HasMetadata meta, ResourceHandler<HasMetadata, HasMetadataVisitiableBuilder> h, String namespaceToUse) {
443+
if (Boolean.TRUE.equals(deletingExisting)) {
444+
return deleteAndCreateItem(client, config, meta, h, namespaceToUse, propagationPolicy);
445+
}
446+
return createOrReplaceItem(client, config, meta, h, namespaceToUse);
447+
}
448+
458449
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* Copyright (C) 2015 Red Hat, Inc.
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+
package io.fabric8.kubernetes.client.utils;
17+
18+
import io.fabric8.kubernetes.api.model.HasMetadata;
19+
import io.fabric8.kubernetes.client.Config;
20+
import io.fabric8.kubernetes.client.HasMetadataVisitiableBuilder;
21+
import io.fabric8.kubernetes.client.KubernetesClientException;
22+
import io.fabric8.kubernetes.client.ResourceHandler;
23+
import okhttp3.OkHttpClient;
24+
25+
import java.net.HttpURLConnection;
26+
import java.util.Objects;
27+
import java.util.concurrent.CompletableFuture;
28+
import java.util.concurrent.TimeUnit;
29+
import java.util.function.UnaryOperator;
30+
31+
public class CreateOrReplaceHelper<T extends HasMetadata> {
32+
public static final int CREATE_OR_REPLACE_RETRIES = 3;
33+
private final UnaryOperator<T> createTask;
34+
private final UnaryOperator<T> replaceTask;
35+
private final UnaryOperator<T> waitTask;
36+
private final UnaryOperator<T> reloadTask;
37+
38+
public CreateOrReplaceHelper(UnaryOperator<T> createTask, UnaryOperator<T> replaceTask, UnaryOperator<T> waitTask, UnaryOperator<T> reloadTask) {
39+
this.createTask = createTask;
40+
this.replaceTask = replaceTask;
41+
this.waitTask = waitTask;
42+
this.reloadTask = reloadTask;
43+
}
44+
45+
public T createOrReplace(T item) {
46+
String resourceVersion = KubernetesResourceUtil.getResourceVersion(item);
47+
final CompletableFuture<T> future = new CompletableFuture<>();
48+
int nTries = 0;
49+
while (!future.isDone() && nTries < CREATE_OR_REPLACE_RETRIES) {
50+
try {
51+
// Create
52+
KubernetesResourceUtil.setResourceVersion(item, null);
53+
return createTask.apply(item);
54+
} catch (KubernetesClientException exception) {
55+
if (shouldRetry(exception.getCode())) {
56+
T itemFromServer = reloadTask.apply(item);
57+
if (itemFromServer == null) {
58+
waitTask.apply(item);
59+
nTries++;
60+
continue;
61+
}
62+
} else if (exception.getCode() != HttpURLConnection.HTTP_CONFLICT) {
63+
throw exception;
64+
}
65+
66+
future.complete(replace(item, resourceVersion));
67+
}
68+
}
69+
return future.join();
70+
}
71+
72+
public static HasMetadata createOrReplaceItem(OkHttpClient client, Config config, HasMetadata meta, ResourceHandler<HasMetadata, HasMetadataVisitiableBuilder> h, String namespaceToUse) {
73+
CreateOrReplaceHelper<HasMetadata> createOrReplaceHelper = new CreateOrReplaceHelper<>(
74+
m -> h.create(client, config, namespaceToUse, m),
75+
m -> h.replace(client, config, namespaceToUse, m),
76+
m -> {
77+
try {
78+
return h.waitUntilCondition(client, config, namespaceToUse, m, Objects::nonNull, 1, TimeUnit.SECONDS);
79+
} catch (InterruptedException interruptedException) {
80+
interruptedException.printStackTrace();
81+
}
82+
return null;
83+
},
84+
m -> h.reload(client, config, namespaceToUse, m)
85+
);
86+
87+
return createOrReplaceHelper.createOrReplace(meta);
88+
}
89+
90+
private T replace(T item, String resourceVersion) {
91+
KubernetesResourceUtil.setResourceVersion(item, resourceVersion);
92+
return replaceTask.apply(item);
93+
}
94+
95+
private boolean shouldRetry(int responseCode) {
96+
return responseCode > 499;
97+
}
98+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Copyright (C) 2015 Red Hat, Inc.
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+
package io.fabric8.kubernetes.client.utils;
17+
18+
import io.fabric8.kubernetes.api.model.DeletionPropagation;
19+
import io.fabric8.kubernetes.api.model.HasMetadata;
20+
import io.fabric8.kubernetes.client.Config;
21+
import io.fabric8.kubernetes.client.HasMetadataVisitiableBuilder;
22+
import io.fabric8.kubernetes.client.KubernetesClientException;
23+
import io.fabric8.kubernetes.client.ResourceHandler;
24+
import okhttp3.OkHttpClient;
25+
26+
import java.util.function.Function;
27+
import java.util.function.UnaryOperator;
28+
29+
public class DeleteAndCreateHelper<T extends HasMetadata> {
30+
private final UnaryOperator<T> createTask;
31+
private final Function<T, Boolean> deleteTask;
32+
33+
public DeleteAndCreateHelper(UnaryOperator<T> createTask, Function<T, Boolean> deleteTask) {
34+
this.createTask = createTask;
35+
this.deleteTask = deleteTask;
36+
}
37+
38+
public T deleteAndCreate(T item) {
39+
Boolean deleted = deleteTask.apply(item);
40+
if (Boolean.FALSE.equals(deleted)) {
41+
throw new KubernetesClientException("Failed to delete existing item:" + item.getMetadata().getName());
42+
}
43+
return createTask.apply(item);
44+
}
45+
46+
public static HasMetadata deleteAndCreateItem(OkHttpClient client, Config config, HasMetadata meta, ResourceHandler<HasMetadata, HasMetadataVisitiableBuilder> h, String namespaceToUse, DeletionPropagation propagationPolicy) {
47+
DeleteAndCreateHelper<HasMetadata> deleteAndCreateHelper = new DeleteAndCreateHelper<>(
48+
m -> h.create(client, config, namespaceToUse, m),
49+
m -> h.delete(client, config, namespaceToUse, propagationPolicy, m)
50+
);
51+
52+
return deleteAndCreateHelper.deleteAndCreate(meta);
53+
}
54+
}

kubernetes-client/src/main/java/io/fabric8/kubernetes/client/utils/Utils.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ public static String getPluralFromKind(String kind) {
358358
break;
359359
case "NetworkPolicy":
360360
case "PodSecurityPolicy":
361+
case "ServiceEntry": // an Istio resource. Needed as getPluralFromKind is currently not configurable #2489
361362
// Delete last character
362363
pluralBuffer.deleteCharAt(pluralBuffer.length() - 1);
363364
pluralBuffer.append("ies");
@@ -471,4 +472,5 @@ public static List<String> getCommandPlatformPrefix() {
471472
private static String getOperatingSystemFromSystemProperty() {
472473
return System.getProperty(OS_NAME);
473474
}
475+
474476
}

kubernetes-client/src/test/java/io/fabric8/kubernetes/client/dsl/base/BaseOperationTest.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,13 @@
3838
import io.fabric8.kubernetes.client.utils.URLUtils;
3939
import org.junit.jupiter.api.Test;
4040

41-
import io.fabric8.kubernetes.client.Watch;
42-
import io.fabric8.kubernetes.client.Watcher;
43-
import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
4441
import io.fabric8.kubernetes.client.dsl.internal.PodOperationContext;
4542
import io.fabric8.kubernetes.client.dsl.internal.core.v1.PodOperationsImpl;
4643

47-
public class BaseOperationTest {
44+
class BaseOperationTest {
4845

4946
@Test
50-
public void testSimpleFieldQueryParamConcatenation() {
47+
void testSimpleFieldQueryParamConcatenation() {
5148
Map<String, String> fieldsMap = new HashMap<>();
5249
fieldsMap.put("yesKey1", "yesValue1");
5350
fieldsMap.put("yesKey2", "yesValue2");
@@ -68,7 +65,7 @@ public void testSimpleFieldQueryParamConcatenation() {
6865
}
6966

7067
@Test
71-
public void testSkippingFieldNotMatchingNullValues() {
68+
void testSkippingFieldNotMatchingNullValues() {
7269
final PodOperationsImpl operation = new PodOperationsImpl(new PodOperationContext());
7370
operation
7471
.withField("key1", "value1")
@@ -83,22 +80,22 @@ public void testSkippingFieldNotMatchingNullValues() {
8380
}
8481

8582
@Test
86-
public void testDefaultGracePeriod() {
83+
void testDefaultGracePeriod() {
8784
final BaseOperation operation = new BaseOperation(new OperationContext());
8885

8986
assertThat(operation.getGracePeriodSeconds(), is(-1L));
9087
}
9188

9289
@Test
93-
public void testChainingGracePeriodAndPropagationPolicy() {
90+
void testChainingGracePeriodAndPropagationPolicy() {
9491
final BaseOperation operation = new BaseOperation(new OperationContext());
9592
EditReplacePatchDeletable<?, ?, ?, Boolean> operationWithPropagationPolicy = operation.withPropagationPolicy(DeletionPropagation.FOREGROUND);
9693
assertThat(operationWithPropagationPolicy, is(notNullValue()));
9794
assertNotNull(operationWithPropagationPolicy.withGracePeriod(10));
9895
}
9996

10097
@Test
101-
public void testListOptions() throws MalformedURLException {
98+
void testListOptions() throws MalformedURLException {
10299
// Given
103100
URL url = new URL("https://172.17.0.2:8443/api/v1/namespaces/default/pods");
104101
final BaseOperation<Pod, PodList, DoneablePod, Resource<Pod, DoneablePod>> operation = new BaseOperation<>(new OperationContext());

0 commit comments

Comments
 (0)