Skip to content

Commit 488d0ef

Browse files
authored
Merge pull request #258 from java-operator-sdk/update_both_status_and_cr
Update both status and custom resource
2 parents 37dbf4a + 4508894 commit 488d0ef

File tree

11 files changed

+278
-1
lines changed

11 files changed

+278
-1
lines changed

operator-framework/src/main/java/io/javaoperatorsdk/operator/api/UpdateControl.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ public static <T extends CustomResource> UpdateControl<T> updateStatusSubResourc
2727
return new UpdateControl<>(customResource, true, false);
2828
}
2929

30+
/**
31+
* As a results of this there will be two call to K8S API. First the custom resource will be
32+
* updates then the status sub-resource.
33+
*/
34+
public static <T extends CustomResource> UpdateControl<T> updateCustomResourceAndStatus(
35+
T customResource) {
36+
return new UpdateControl<>(customResource, true, true);
37+
}
38+
3039
public static <T extends CustomResource> UpdateControl<T> noUpdate() {
3140
return new UpdateControl<>(null, false, false);
3241
}
@@ -42,4 +51,8 @@ public boolean isUpdateStatusSubResource() {
4251
public boolean isUpdateCustomResource() {
4352
return updateCustomResource;
4453
}
54+
55+
public boolean isUpdateCustomResourceAndStatusSubResource() {
56+
return updateCustomResource && updateStatusSubResource;
57+
}
4558
}

operator-framework/src/main/java/io/javaoperatorsdk/operator/processing/EventDispatcher.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,21 @@ private PostExecutionControl handleCreateOrUpdate(
103103
UpdateControl<? extends CustomResource> updateControl =
104104
controller.createOrUpdateResource(resource, context);
105105
CustomResource updatedCustomResource = null;
106-
if (updateControl.isUpdateStatusSubResource()) {
106+
if (updateControl.isUpdateCustomResourceAndStatusSubResource()) {
107+
updatedCustomResource = updateCustomResource(updateControl.getCustomResource());
108+
updateControl
109+
.getCustomResource()
110+
.getMetadata()
111+
.setResourceVersion(updatedCustomResource.getMetadata().getResourceVersion());
112+
updatedCustomResource =
113+
customResourceFacade.updateStatus(updateControl.getCustomResource());
114+
} else if (updateControl.isUpdateStatusSubResource()) {
107115
updatedCustomResource =
108116
customResourceFacade.updateStatus(updateControl.getCustomResource());
109117
} else if (updateControl.isUpdateCustomResource()) {
110118
updatedCustomResource = updateCustomResource(updateControl.getCustomResource());
111119
}
120+
112121
if (updatedCustomResource != null) {
113122
return PostExecutionControl.customResourceUpdated(updatedCustomResource);
114123
} else {

operator-framework/src/test/java/io/javaoperatorsdk/operator/EventDispatcherTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,19 @@ void updatesOnlyStatusSubResource() {
7272
verify(customResourceFacade, never()).replaceWithLock(any());
7373
}
7474

75+
@Test
76+
void updatesBothResourceAndStatus() {
77+
when(controller.createOrUpdateResource(eq(testCustomResource), any()))
78+
.thenReturn(UpdateControl.updateCustomResourceAndStatus(testCustomResource));
79+
when(customResourceFacade.replaceWithLock(testCustomResource)).thenReturn(testCustomResource);
80+
81+
eventDispatcher.handleExecution(
82+
executionScopeWithCREvent(Watcher.Action.MODIFIED, testCustomResource));
83+
84+
verify(customResourceFacade, times(1)).replaceWithLock(testCustomResource);
85+
verify(customResourceFacade, times(1)).updateStatus(testCustomResource);
86+
}
87+
7588
@Test
7689
void callCreateOrUpdateOnModifiedResource() {
7790
eventDispatcher.handleExecution(

operator-framework/src/test/java/io/javaoperatorsdk/operator/IntegrationTestSupport.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,10 @@ public KubernetesClient getK8sClient() {
183183
return crOperations;
184184
}
185185

186+
public CustomResource getCustomResource(String name) {
187+
return getCrOperations().inNamespace(TEST_NAMESPACE).withName(name).get();
188+
}
189+
186190
public Operator getOperator() {
187191
return operator;
188192
}

operator-framework/src/test/java/io/javaoperatorsdk/operator/TestUtils.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,12 @@ public static TestCustomResource testCustomResource(String uid) {
3131
resource.getSpec().setValue("test-value");
3232
return resource;
3333
}
34+
35+
public static void waitXms(int x) {
36+
try {
37+
Thread.sleep(x);
38+
} catch (InterruptedException e) {
39+
throw new IllegalStateException(e);
40+
}
41+
}
3442
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package io.javaoperatorsdk.operator;
2+
3+
import static io.javaoperatorsdk.operator.IntegrationTestSupport.TEST_NAMESPACE;
4+
import static io.javaoperatorsdk.operator.TestUtils.waitXms;
5+
import static io.javaoperatorsdk.operator.doubleupdate.DoubleUpdateTestCustomResourceController.TEST_ANNOTATION;
6+
import static org.assertj.core.api.Assertions.assertThat;
7+
import static org.awaitility.Awaitility.await;
8+
9+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
10+
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
11+
import io.fabric8.kubernetes.client.KubernetesClient;
12+
import io.javaoperatorsdk.operator.doubleupdate.DoubleUpdateTestCustomResource;
13+
import io.javaoperatorsdk.operator.doubleupdate.DoubleUpdateTestCustomResourceController;
14+
import io.javaoperatorsdk.operator.doubleupdate.DoubleUpdateTestCustomResourceSpec;
15+
import io.javaoperatorsdk.operator.doubleupdate.DoubleUpdateTestCustomResourceStatus;
16+
import java.util.concurrent.TimeUnit;
17+
import org.junit.jupiter.api.BeforeEach;
18+
import org.junit.jupiter.api.Test;
19+
import org.junit.jupiter.api.TestInstance;
20+
21+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
22+
public class UpdatingResAndSubResIT {
23+
24+
private IntegrationTestSupport integrationTestSupport = new IntegrationTestSupport();
25+
26+
@BeforeEach
27+
public void initAndCleanup() {
28+
KubernetesClient k8sClient = new DefaultKubernetesClient();
29+
integrationTestSupport.initialize(
30+
k8sClient, new DoubleUpdateTestCustomResourceController(), "doubleupdate-test-crd.yaml");
31+
integrationTestSupport.cleanup();
32+
}
33+
34+
@Test
35+
public void updatesSubResourceStatus() {
36+
integrationTestSupport.teardownIfSuccess(
37+
() -> {
38+
DoubleUpdateTestCustomResource resource = createTestCustomResource("1");
39+
integrationTestSupport.getCrOperations().inNamespace(TEST_NAMESPACE).create(resource);
40+
41+
awaitStatusUpdated(resource.getMetadata().getName());
42+
// wait for sure, there are no more events
43+
waitXms(300);
44+
45+
DoubleUpdateTestCustomResource customResource =
46+
(DoubleUpdateTestCustomResource)
47+
integrationTestSupport.getCustomResource(resource.getMetadata().getName());
48+
assertThat(integrationTestSupport.numberOfControllerExecutions()).isEqualTo(1);
49+
assertThat(customResource.getStatus().getState())
50+
.isEqualTo(DoubleUpdateTestCustomResourceStatus.State.SUCCESS);
51+
assertThat(customResource.getMetadata().getAnnotations().get(TEST_ANNOTATION))
52+
.isNotNull();
53+
});
54+
}
55+
56+
void awaitStatusUpdated(String name) {
57+
await("cr status updated")
58+
.atMost(5, TimeUnit.SECONDS)
59+
.untilAsserted(
60+
() -> {
61+
DoubleUpdateTestCustomResource cr =
62+
(DoubleUpdateTestCustomResource)
63+
integrationTestSupport
64+
.getCrOperations()
65+
.inNamespace(TEST_NAMESPACE)
66+
.withName(name)
67+
.get();
68+
assertThat(cr.getMetadata().getFinalizers()).hasSize(1);
69+
assertThat(cr).isNotNull();
70+
assertThat(cr.getStatus()).isNotNull();
71+
assertThat(cr.getStatus().getState())
72+
.isEqualTo(DoubleUpdateTestCustomResourceStatus.State.SUCCESS);
73+
});
74+
}
75+
76+
public DoubleUpdateTestCustomResource createTestCustomResource(String id) {
77+
DoubleUpdateTestCustomResource resource = new DoubleUpdateTestCustomResource();
78+
resource.setMetadata(
79+
new ObjectMetaBuilder()
80+
.withName("doubleupdateresource-" + id)
81+
.withNamespace(TEST_NAMESPACE)
82+
.build());
83+
resource.setKind("DoubleUpdateSample");
84+
resource.setSpec(new DoubleUpdateTestCustomResourceSpec());
85+
resource.getSpec().setValue(id);
86+
return resource;
87+
}
88+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package io.javaoperatorsdk.operator.doubleupdate;
2+
3+
import io.fabric8.kubernetes.client.CustomResource;
4+
5+
public class DoubleUpdateTestCustomResource extends CustomResource {
6+
7+
private DoubleUpdateTestCustomResourceSpec spec;
8+
9+
private DoubleUpdateTestCustomResourceStatus status;
10+
11+
public DoubleUpdateTestCustomResourceSpec getSpec() {
12+
return spec;
13+
}
14+
15+
public void setSpec(DoubleUpdateTestCustomResourceSpec spec) {
16+
this.spec = spec;
17+
}
18+
19+
public DoubleUpdateTestCustomResourceStatus getStatus() {
20+
return status;
21+
}
22+
23+
public void setStatus(DoubleUpdateTestCustomResourceStatus status) {
24+
this.status = status;
25+
}
26+
27+
@Override
28+
public String toString() {
29+
return "DoubleUpdateTestCustomResource{"
30+
+ "spec="
31+
+ spec
32+
+ ", status="
33+
+ status
34+
+ ", extendedFrom="
35+
+ super.toString()
36+
+ '}';
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package io.javaoperatorsdk.operator.doubleupdate;
2+
3+
import io.javaoperatorsdk.operator.TestExecutionInfoProvider;
4+
import io.javaoperatorsdk.operator.api.*;
5+
import java.util.HashMap;
6+
import java.util.concurrent.atomic.AtomicInteger;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
10+
@Controller(crdName = DoubleUpdateTestCustomResourceController.CRD_NAME)
11+
public class DoubleUpdateTestCustomResourceController
12+
implements ResourceController<DoubleUpdateTestCustomResource>, TestExecutionInfoProvider {
13+
14+
public static final String CRD_NAME = "doubleupdatesamples.sample.javaoperatorsdk";
15+
private static final Logger log =
16+
LoggerFactory.getLogger(DoubleUpdateTestCustomResourceController.class);
17+
public static final String TEST_ANNOTATION = "TestAnnotation";
18+
public static final String TEST_ANNOTATION_VALUE = "TestAnnotationValue";
19+
private final AtomicInteger numberOfExecutions = new AtomicInteger(0);
20+
21+
@Override
22+
public DeleteControl deleteResource(
23+
DoubleUpdateTestCustomResource resource, Context<DoubleUpdateTestCustomResource> context) {
24+
return DeleteControl.DEFAULT_DELETE;
25+
}
26+
27+
@Override
28+
public UpdateControl<DoubleUpdateTestCustomResource> createOrUpdateResource(
29+
DoubleUpdateTestCustomResource resource, Context<DoubleUpdateTestCustomResource> context) {
30+
numberOfExecutions.addAndGet(1);
31+
32+
log.info("Value: " + resource.getSpec().getValue());
33+
34+
resource.getMetadata().setAnnotations(new HashMap<>());
35+
resource.getMetadata().getAnnotations().put(TEST_ANNOTATION, TEST_ANNOTATION_VALUE);
36+
ensureStatusExists(resource);
37+
resource.getStatus().setState(DoubleUpdateTestCustomResourceStatus.State.SUCCESS);
38+
39+
return UpdateControl.updateCustomResourceAndStatus(resource);
40+
}
41+
42+
private void ensureStatusExists(DoubleUpdateTestCustomResource resource) {
43+
DoubleUpdateTestCustomResourceStatus status = resource.getStatus();
44+
if (status == null) {
45+
status = new DoubleUpdateTestCustomResourceStatus();
46+
resource.setStatus(status);
47+
}
48+
}
49+
50+
public int getNumberOfExecutions() {
51+
return numberOfExecutions.get();
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.javaoperatorsdk.operator.doubleupdate;
2+
3+
public class DoubleUpdateTestCustomResourceSpec {
4+
5+
private String value;
6+
7+
public String getValue() {
8+
return value;
9+
}
10+
11+
public DoubleUpdateTestCustomResourceSpec setValue(String value) {
12+
this.value = value;
13+
return this;
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.javaoperatorsdk.operator.doubleupdate;
2+
3+
public class DoubleUpdateTestCustomResourceStatus {
4+
5+
private State state;
6+
7+
public State getState() {
8+
return state;
9+
}
10+
11+
public DoubleUpdateTestCustomResourceStatus setState(State state) {
12+
this.state = state;
13+
return this;
14+
}
15+
16+
public enum State {
17+
SUCCESS,
18+
ERROR
19+
}
20+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: apiextensions.k8s.io/v1beta1
2+
kind: CustomResourceDefinition
3+
metadata:
4+
name: doubleupdatesamples.sample.javaoperatorsdk
5+
spec:
6+
group: sample.javaoperatorsdk
7+
version: v1
8+
subresources:
9+
status: {}
10+
scope: Namespaced
11+
names:
12+
plural: doubleupdatesamples
13+
singular: doubleupdatesample
14+
kind: DoubleUpdateSample
15+
shortNames:
16+
- du

0 commit comments

Comments
 (0)