Skip to content

Commit 975ad94

Browse files
authored
Check job condition to determine if job has failed in addition to checking job status (#2201)
* Fix to check job condition to determine if job has failed in addition to checking status. * Added unit tests. * Minor changes. * Remove duplicate test.
1 parent 56d513f commit 975ad94

File tree

2 files changed

+71
-7
lines changed

2 files changed

+71
-7
lines changed

operator/src/main/java/oracle/kubernetes/operator/JobWatcher.java

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33

44
package oracle.kubernetes.operator;
55

6+
import java.util.Collections;
67
import java.util.List;
78
import java.util.Map;
89
import java.util.Optional;
910
import java.util.concurrent.ConcurrentHashMap;
1011
import java.util.concurrent.ThreadFactory;
1112
import java.util.concurrent.atomic.AtomicBoolean;
1213
import java.util.function.Consumer;
14+
import javax.annotation.Nonnull;
1315

1416
import io.kubernetes.client.openapi.ApiException;
1517
import io.kubernetes.client.openapi.models.V1Job;
@@ -137,16 +139,38 @@ static boolean isFailed(V1Job job) {
137139
return false;
138140
}
139141

140-
V1JobStatus status = job.getStatus();
141-
if (status != null) {
142-
if (status.getFailed() != null && status.getFailed() > 0) {
143-
LOGGER.severe(MessageKeys.JOB_IS_FAILED, job.getMetadata().getName());
144-
return true;
145-
}
142+
if (isStatusFailed(job) || isConditionFailed(job)) {
143+
LOGGER.severe(MessageKeys.JOB_IS_FAILED, job.getMetadata().getName());
144+
return true;
146145
}
147146
return false;
148147
}
149148

149+
private static boolean isStatusFailed(V1Job job) {
150+
return Optional.ofNullable(job.getStatus()).map(V1JobStatus::getFailed).map(failed -> (failed > 0)).orElse(false);
151+
}
152+
153+
private static boolean isConditionFailed(V1Job job) {
154+
return getJobConditions(job).stream().anyMatch(JobWatcher::isJobConditionFailed);
155+
}
156+
157+
private static List<V1JobCondition> getJobConditions(@Nonnull V1Job job) {
158+
return Optional.ofNullable(job.getStatus()).map(V1JobStatus::getConditions).orElse(Collections.emptyList());
159+
}
160+
161+
private static boolean isJobConditionFailed(V1JobCondition jobCondition) {
162+
return getType(jobCondition).equals("Failed") && getStatus(jobCondition).equals("True");
163+
}
164+
165+
private static String getType(V1JobCondition jobCondition) {
166+
return Optional.ofNullable(jobCondition).map(V1JobCondition::getType).orElse("");
167+
}
168+
169+
private static String getStatus(V1JobCondition jobCondition) {
170+
return Optional.ofNullable(jobCondition).map(V1JobCondition::getStatus).orElse("");
171+
}
172+
173+
150174
static String getFailedReason(V1Job job) {
151175
V1JobStatus status = job.getStatus();
152176
if (status != null && status.getConditions() != null) {
@@ -305,4 +329,4 @@ private long getJobStartedSeconds() {
305329
return -1;
306330
}
307331
}
308-
}
332+
}

operator/src/test/java/oracle/kubernetes/operator/JobWatcherTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
package oracle.kubernetes.operator;
55

66
import java.math.BigInteger;
7+
import java.util.ArrayList;
8+
import java.util.Arrays;
79
import java.util.Collections;
810
import java.util.concurrent.atomic.AtomicBoolean;
911
import java.util.function.Function;
@@ -128,6 +130,28 @@ public void whenJobConditionStatusFalse_reportNotComplete() {
128130
assertThat(JobWatcher.isComplete(cachedJob), is(false));
129131
}
130132

133+
@Test
134+
public void whenJobConditionTypeFailedWithTrueStatus_reportFailed() {
135+
markJobConditionFailed(cachedJob);
136+
137+
assertThat(JobWatcher.isFailed(cachedJob), is(true));
138+
}
139+
140+
@Test
141+
public void whenJobConditionTypeFailedWithNoStatus_reportNotFailed() {
142+
cachedJob.status(new V1JobStatus().addConditionsItem(new V1JobCondition().type("Failed").status("")));
143+
144+
assertThat(JobWatcher.isFailed(cachedJob), is(false));
145+
}
146+
147+
@Test
148+
public void whenJobHasStatusWithNoConditionsAndNotFailed_reportNotFailed() {
149+
cachedJob.status(new V1JobStatus().conditions(Collections.emptyList()));
150+
151+
assertThat(JobWatcher.isFailed(cachedJob), is(false));
152+
}
153+
154+
131155
@Test
132156
public void whenJobRunningAndReadyConditionIsTrue_reportComplete() {
133157
markJobCompleted(cachedJob);
@@ -151,6 +175,10 @@ private V1Job markJobFailed(V1Job job) {
151175
return setFailedWithReason(job, null);
152176
}
153177

178+
private V1Job markJobConditionFailed(V1Job job) {
179+
return setFailedConditionWithReason(job, null);
180+
}
181+
154182
private V1Job markJobTimedOut(V1Job job) {
155183
return markJobTimedOut(job, "DeadlineExceeded");
156184
}
@@ -163,6 +191,11 @@ private V1Job setFailedWithReason(V1Job job, String reason) {
163191
return job.status(new V1JobStatus().failed(1).addConditionsItem(createCondition("Failed").reason(reason)));
164192
}
165193

194+
private V1Job setFailedConditionWithReason(V1Job job, String reason) {
195+
return job.status(new V1JobStatus().conditions(
196+
new ArrayList<>(Arrays.asList(new V1JobCondition().type("Failed").status("True").reason(reason)))));
197+
}
198+
166199
@Test
167200
public void whenJobHasNoStatus_reportNotFailed() {
168201
assertThat(JobWatcher.isFailed(cachedJob), is(false));
@@ -248,6 +281,13 @@ public void whenWaitForReadyAppliedToFailedJob_performNextStep() {
248281
assertThat(terminalStep.wasRun(), is(true));
249282
}
250283

284+
@Test
285+
public void whenWaitForReadyAppliedToJobWithFailedCondition_performNextStep() {
286+
startWaitForReady(this::markJobConditionFailed);
287+
288+
assertThat(terminalStep.wasRun(), is(true));
289+
}
290+
251291
// Starts the waitForReady step with job modified as needed
252292
private void startWaitForReady(Function<V1Job,V1Job> jobFunction) {
253293
AtomicBoolean stopping = new AtomicBoolean(false);

0 commit comments

Comments
 (0)