Skip to content

Commit ec65a25

Browse files
authored
Merge pull request #871 from oracle/owls-71775
Patch pods to add labels or annotations, rather than restarting
2 parents aeff4cb + 33bd9d4 commit ec65a25

File tree

14 files changed

+449
-59
lines changed

14 files changed

+449
-59
lines changed

operator/src/main/java/oracle/kubernetes/operator/helpers/CallBuilder.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import io.kubernetes.client.models.VersionInfo;
4040
import java.util.Optional;
4141
import java.util.function.Consumer;
42+
import javax.json.JsonPatch;
4243
import oracle.kubernetes.operator.TuningParameters;
4344
import oracle.kubernetes.operator.TuningParameters.CallBuilderTuning;
4445
import oracle.kubernetes.operator.calls.AsyncRequestStep;
@@ -51,6 +52,7 @@
5152
import oracle.kubernetes.operator.logging.LoggingFacade;
5253
import oracle.kubernetes.operator.logging.LoggingFactory;
5354
import oracle.kubernetes.operator.logging.MessageKeys;
55+
import oracle.kubernetes.operator.utils.PatchUtils;
5456
import oracle.kubernetes.operator.work.Step;
5557
import oracle.kubernetes.weblogic.domain.v2.Domain;
5658
import oracle.kubernetes.weblogic.domain.v2.DomainList;
@@ -832,6 +834,39 @@ public Step deletePodAsync(
832834
responseStep, new RequestParams("deletePod", namespace, name, deleteOptions), DELETE_POD);
833835
}
834836

837+
private com.squareup.okhttp.Call patchPodAsync(
838+
ApiClient client, String name, String namespace, Object patch, ApiCallback<V1Pod> callback)
839+
throws ApiException {
840+
return new CoreV1Api(client).patchNamespacedPodAsync(name, namespace, patch, pretty, callback);
841+
}
842+
843+
private final CallFactory<V1Pod> PATCH_POD =
844+
(requestParams, usage, cont, callback) ->
845+
wrap(
846+
patchPodAsync(
847+
usage,
848+
requestParams.name,
849+
requestParams.namespace,
850+
requestParams.body,
851+
callback));
852+
853+
/**
854+
* Asynchronous step for patching a pod
855+
*
856+
* @param name Name
857+
* @param namespace Namespace
858+
* @param patchBody instructions on what to patch
859+
* @param responseStep Response step for when call completes
860+
* @return Asynchronous step
861+
*/
862+
public Step patchPodAsync(
863+
String name, String namespace, JsonPatch patchBody, ResponseStep<V1Pod> responseStep) {
864+
return createRequestAsync(
865+
responseStep,
866+
new RequestParams("patchPod", namespace, name, PatchUtils.toKubernetesPatch(patchBody)),
867+
PATCH_POD);
868+
}
869+
835870
private com.squareup.okhttp.Call deleteCollectionPodAsync(
836871
ApiClient client, String namespace, String _continue, ApiCallback<V1Status> callback)
837872
throws ApiException {

operator/src/main/java/oracle/kubernetes/operator/helpers/KubernetesUtils.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2018, Oracle Corporation and/or its affiliates. All rights reserved.
1+
// Copyright 2018,2019 Oracle Corporation and/or its affiliates. All rights reserved.
22
// Licensed under the Universal Permissive License v 1.0 as shown at
33
// http://oss.oracle.com/licenses/upl.
44

@@ -8,6 +8,7 @@
88
import java.util.HashMap;
99
import java.util.Map;
1010
import java.util.Objects;
11+
import javax.json.JsonPatchBuilder;
1112
import org.apache.commons.collections.MapUtils;
1213

1314
class KubernetesUtils {
@@ -58,4 +59,45 @@ private static boolean isOperatorLabel(Map.Entry<String, String> label) {
5859
static boolean areAnnotationsValid(V1ObjectMeta build, V1ObjectMeta current) {
5960
return mapEquals(current.getAnnotations(), build.getAnnotations());
6061
}
62+
63+
/**
64+
* Returns true if the current map is missing values from the required map. This method is
65+
* typically used to compare labels and annotations against specifications derived from the
66+
* domain.
67+
*
68+
* @param current a map of the values found in a Kubernetes resource
69+
* @param required a map of the values specified for the resource by the domain
70+
* @return true if there is a problem that must be fixed by patching
71+
*/
72+
static boolean isMissingValues(Map<String, String> current, Map<String, String> required) {
73+
if (!hasAllRequiredNames(current, required)) return true;
74+
for (String name : required.keySet())
75+
if (!Objects.equals(current.get(name), required.get(name))) return true;
76+
77+
return false;
78+
}
79+
80+
private static boolean hasAllRequiredNames(Map<String, ?> current, Map<String, ?> required) {
81+
return current.keySet().containsAll(required.keySet());
82+
}
83+
84+
/**
85+
* Adds patches to the specified patch builder to correct differences in the current vs required
86+
* maps.
87+
*
88+
* @param patchBuilder a builder for the patches
89+
* @param basePath the base for the patch path (excluding the name)
90+
* @param current a map of the values found in a Kubernetes resource
91+
* @param required a map of the values specified for the resource by the domain
92+
*/
93+
static void addPatches(
94+
JsonPatchBuilder patchBuilder,
95+
String basePath,
96+
Map<String, String> current,
97+
Map<String, String> required) {
98+
for (String name : required.keySet()) {
99+
if (!current.containsKey(name)) patchBuilder.add(basePath + name, required.get(name));
100+
else patchBuilder.replace(basePath + name, required.get(name));
101+
}
102+
}
61103
}

operator/src/main/java/oracle/kubernetes/operator/helpers/PodHelper.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,19 @@
44

55
package oracle.kubernetes.operator.helpers;
66

7-
import io.kubernetes.client.models.*;
7+
import io.kubernetes.client.models.V1DeleteOptions;
8+
import io.kubernetes.client.models.V1EnvVar;
9+
import io.kubernetes.client.models.V1ObjectMeta;
10+
import io.kubernetes.client.models.V1Pod;
11+
import io.kubernetes.client.models.V1PodSpec;
812
import java.util.ArrayList;
913
import java.util.List;
1014
import java.util.Map;
11-
import oracle.kubernetes.operator.*;
15+
import oracle.kubernetes.operator.DomainStatusUpdater;
16+
import oracle.kubernetes.operator.LabelConstants;
17+
import oracle.kubernetes.operator.PodAwaiterStepFactory;
18+
import oracle.kubernetes.operator.ProcessingConstants;
19+
import oracle.kubernetes.operator.TuningParameters;
1220
import oracle.kubernetes.operator.logging.MessageKeys;
1321
import oracle.kubernetes.operator.steps.DefaultResponseStep;
1422
import oracle.kubernetes.operator.work.Component;
@@ -66,6 +74,11 @@ String getPodExistsMessageKey() {
6674
return MessageKeys.ADMIN_POD_EXISTS;
6775
}
6876

77+
@Override
78+
String getPodPatchedMessageKey() {
79+
return MessageKeys.ADMIN_POD_PATCHED;
80+
}
81+
6982
@Override
7083
String getPodReplacedMessageKey() {
7184
return MessageKeys.ADMIN_POD_REPLACED;
@@ -220,6 +233,11 @@ String getPodExistsMessageKey() {
220233
return MessageKeys.MANAGED_POD_EXISTS;
221234
}
222235

236+
@Override
237+
String getPodPatchedMessageKey() {
238+
return MessageKeys.MANAGED_POD_PATCHED;
239+
}
240+
223241
@Override
224242
protected String getPodReplacedMessageKey() {
225243
return MessageKeys.MANAGED_POD_REPLACED;

operator/src/main/java/oracle/kubernetes/operator/helpers/PodStepContext.java

Lines changed: 101 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,42 @@
99

1010
import io.kubernetes.client.custom.IntOrString;
1111
import io.kubernetes.client.custom.Quantity;
12-
import io.kubernetes.client.models.*;
12+
import io.kubernetes.client.models.V1Container;
13+
import io.kubernetes.client.models.V1ContainerPort;
14+
import io.kubernetes.client.models.V1DeleteOptions;
15+
import io.kubernetes.client.models.V1EnvVar;
16+
import io.kubernetes.client.models.V1ExecAction;
17+
import io.kubernetes.client.models.V1HTTPGetAction;
18+
import io.kubernetes.client.models.V1Handler;
19+
import io.kubernetes.client.models.V1Lifecycle;
20+
import io.kubernetes.client.models.V1ObjectMeta;
21+
import io.kubernetes.client.models.V1PersistentVolume;
22+
import io.kubernetes.client.models.V1PersistentVolumeList;
23+
import io.kubernetes.client.models.V1Pod;
24+
import io.kubernetes.client.models.V1PodSpec;
25+
import io.kubernetes.client.models.V1Probe;
26+
import io.kubernetes.client.models.V1ResourceRequirements;
27+
import io.kubernetes.client.models.V1Status;
28+
import io.kubernetes.client.models.V1Volume;
29+
import io.kubernetes.client.models.V1VolumeMount;
1330
import java.io.File;
14-
import java.util.*;
15-
import oracle.kubernetes.operator.*;
31+
import java.util.ArrayList;
32+
import java.util.Arrays;
33+
import java.util.Collections;
34+
import java.util.HashSet;
35+
import java.util.Iterator;
36+
import java.util.List;
37+
import java.util.Map;
38+
import java.util.Objects;
39+
import java.util.Optional;
40+
import java.util.Set;
41+
import javax.json.Json;
42+
import javax.json.JsonPatchBuilder;
43+
import oracle.kubernetes.operator.KubernetesConstants;
44+
import oracle.kubernetes.operator.LabelConstants;
45+
import oracle.kubernetes.operator.PodAwaiterStepFactory;
46+
import oracle.kubernetes.operator.ProcessingConstants;
47+
import oracle.kubernetes.operator.TuningParameters;
1648
import oracle.kubernetes.operator.calls.CallResponse;
1749
import oracle.kubernetes.operator.logging.LoggingFacade;
1850
import oracle.kubernetes.operator.logging.LoggingFactory;
@@ -145,7 +177,7 @@ protected boolean isDomainHomeInImage() {
145177
return getDomain().isDomainHomeInImage();
146178
}
147179

148-
String getEffectiveLogHome() {
180+
private String getEffectiveLogHome() {
149181
if (!getDomain().getLogHomeEnabled()) return null;
150182
String logHome = getLogHome();
151183
if (logHome == null || "".equals(logHome.trim())) {
@@ -274,6 +306,21 @@ private Step replacePod(Step next) {
274306
return new CallBuilder().createPodAsync(getNamespace(), getPodModel(), replaceResponse(next));
275307
}
276308

309+
private Step patchCurrentPod(V1Pod currentPod, Step next) {
310+
JsonPatchBuilder patchBuilder = Json.createPatchBuilder();
311+
312+
KubernetesUtils.addPatches(
313+
patchBuilder, "/metadata/labels/", currentPod.getMetadata().getLabels(), getPodLabels());
314+
KubernetesUtils.addPatches(
315+
patchBuilder,
316+
"/metadata/annotations/",
317+
currentPod.getMetadata().getAnnotations(),
318+
getPodAnnotations());
319+
320+
return new CallBuilder()
321+
.patchPodAsync(getPodName(), getNamespace(), patchBuilder.build(), patchResponse(next));
322+
}
323+
277324
private void logPodCreated() {
278325
LOGGER.info(getPodCreatedMessageKey(), getDomainUID(), getServerName());
279326
}
@@ -282,6 +329,10 @@ private void logPodExists() {
282329
LOGGER.fine(getPodExistsMessageKey(), getDomainUID(), getServerName());
283330
}
284331

332+
private void logPodPatched() {
333+
LOGGER.info(getPodPatchedMessageKey(), getDomainUID(), getServerName());
334+
}
335+
285336
private void logPodReplaced() {
286337
LOGGER.info(getPodReplacedMessageKey(), getDomainUID(), getServerName());
287338
}
@@ -290,6 +341,8 @@ private void logPodReplaced() {
290341

291342
abstract String getPodExistsMessageKey();
292343

344+
abstract String getPodPatchedMessageKey();
345+
293346
abstract String getPodReplacedMessageKey();
294347

295348
Step createCyclePodStep(Step next) {
@@ -309,6 +362,12 @@ public NextAction apply(Packet packet) {
309362
}
310363
}
311364

365+
private boolean mustPatchPod(V1Pod currentPod) {
366+
return KubernetesUtils.isMissingValues(currentPod.getMetadata().getLabels(), getPodLabels())
367+
|| KubernetesUtils.isMissingValues(
368+
currentPod.getMetadata().getAnnotations(), getPodAnnotations());
369+
}
370+
312371
private boolean canUseCurrentPod(V1Pod currentPod) {
313372
return isCurrentPodValid(getPodModel(), currentPod);
314373
}
@@ -326,9 +385,7 @@ private static boolean isCurrentPodValid(V1Pod build, V1Pod current) {
326385

327386
private static boolean isCurrentPodMetadataValid(V1ObjectMeta build, V1ObjectMeta current) {
328387
return VersionHelper.matchesResourceVersion(current, DEFAULT_DOMAIN_VERSION)
329-
&& isRestartVersionValid(build, current)
330-
&& KubernetesUtils.areLabelsValid(build, current)
331-
&& KubernetesUtils.areAnnotationsValid(build, current);
388+
&& isRestartVersionValid(build, current);
332389
}
333390

334391
private static boolean isCurrentPodSpecValid(
@@ -472,12 +529,14 @@ public NextAction apply(Packet packet) {
472529
V1Pod currentPod = getSko().getPod().get();
473530
if (currentPod == null) {
474531
return doNext(createNewPod(getNext()), packet);
475-
} else if (canUseCurrentPod(currentPod)) {
476-
logPodExists();
477-
return doNext(packet);
478-
} else {
532+
} else if (!canUseCurrentPod(currentPod)) {
479533
LOGGER.info(MessageKeys.CYCLING_POD, currentPod, getPodModel());
480534
return doNext(replaceCurrentPod(getNext()), packet);
535+
} else if (mustPatchPod(currentPod)) {
536+
return doNext(patchCurrentPod(currentPod, getNext()), packet);
537+
} else {
538+
logPodExists();
539+
return doNext(packet);
481540
}
482541
}
483542
}
@@ -560,6 +619,37 @@ public NextAction onSuccess(Packet packet, CallResponse<V1Pod> callResponse) {
560619
}
561620
}
562621

622+
private ResponseStep<V1Pod> patchResponse(Step next) {
623+
return new PatchPodResponseStep(next);
624+
}
625+
626+
private class PatchPodResponseStep extends ResponseStep<V1Pod> {
627+
private final Step next;
628+
629+
PatchPodResponseStep(Step next) {
630+
super(next);
631+
this.next = next;
632+
}
633+
634+
@Override
635+
public NextAction onFailure(Packet packet, CallResponse<V1Pod> callResponse) {
636+
return super.onFailure(getConflictStep(), packet, callResponse);
637+
}
638+
639+
@Override
640+
public NextAction onSuccess(Packet packet, CallResponse<V1Pod> callResponse) {
641+
642+
V1Pod newPod = callResponse.getResult();
643+
logPodPatched();
644+
if (newPod != null) {
645+
setRecordedPod(newPod);
646+
}
647+
648+
PodAwaiterStepFactory pw = PodHelper.getPodAwaiterStepFactory(packet);
649+
return doNext(pw.waitForReady(newPod, next), packet);
650+
}
651+
}
652+
563653
Step verifyPersistentVolume(Step next) {
564654
return new VerifyPersistentVolumeStep(next);
565655
}

operator/src/main/java/oracle/kubernetes/operator/logging/MessageKeys.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ private MessageKeys() {}
123123
public static final String ROLLING_CLUSTERS_STARTING = "WLSKO-0117";
124124
public static final String CYCLING_SERVERS = "WLSKO-0118";
125125
public static final String ROLLING_SERVERS = "WLSKO-0119";
126+
public static final String ADMIN_POD_PATCHED = "WLSKO-0120";
127+
public static final String MANAGED_POD_PATCHED = "WLSKO-0121";
126128
public static final String POD_DELETED = "WLSKO-0122";
127129
public static final String SERVER_SERVICE_DELETED = "WLSKO-0123";
128130
public static final String CLUSTER_SERVICE_DELETED = "WLSKO-0124";
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2019 Oracle Corporation and/or its affiliates. All rights reserved.
2+
// Licensed under the Universal Permissive License v 1.0 as shown at
3+
// http://oss.oracle.com/licenses/upl.
4+
5+
package oracle.kubernetes.operator.utils;
6+
7+
import com.google.gson.Gson;
8+
import com.google.gson.JsonElement;
9+
import com.google.gson.JsonObject;
10+
import java.util.List;
11+
import java.util.stream.Collectors;
12+
import javax.json.JsonPatch;
13+
import javax.json.JsonValue;
14+
15+
public class PatchUtils {
16+
public static List<JsonObject> toKubernetesPatch(JsonPatch jsonPatch) {
17+
return jsonPatch
18+
.toJsonArray()
19+
.stream()
20+
.map(PatchUtils::toJsonObject)
21+
.collect(Collectors.toList());
22+
}
23+
24+
private static JsonObject toJsonObject(JsonValue value) {
25+
return new Gson().fromJson(value.toString(), JsonElement.class).getAsJsonObject();
26+
}
27+
}

operator/src/main/resources/Operator.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,8 @@ WLSKO-0116=Restart of servers for Domain with UID {0} in the list {1} is startin
118118
WLSKO-0117=Rolling restart of servers for Domain with UID {0} in the list of clusters {1} is starting
119119
WLSKO-0118=Cycling of servers for Domain with UID {0} in the list {1} now
120120
WLSKO-0119=Rolling of servers for Domain with UID {0} in the list {1} now with ready servers {2}
121-
WLSKO-0120=
122-
WLSKO-0121=
121+
WLSKO-0120=Patching administration server Pod for WebLogic domain with UID: {0}. Administration server name: {1}.
122+
WLSKO-0121=Patching managed server Pod for WebLogic domain with UID: {0}. Managed server name: {1}.
123123
WLSKO-0122=Pod for domain with domainUID {0} in namespace {1} and with server name {2} deleted; validating domain
124124
WLSKO-0123=Service for domain with domainUID {0} in namespace {1} and with server name {2} deleted; validating domain
125125
WLSKO-0124=Service for domain with domainUID {0} in namespace {1} and with cluster name {2} deleted; validating domain

0 commit comments

Comments
 (0)