Skip to content

Commit 019771f

Browse files
authored
Refactor WebPage Sample (#976)
1 parent d3aadb5 commit 019771f

File tree

8 files changed

+405
-126
lines changed

8 files changed

+405
-126
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.javaoperatorsdk.operator.processing.dependent.kubernetes;
2+
3+
import io.fabric8.kubernetes.api.model.HasMetadata;
4+
import io.javaoperatorsdk.operator.api.reconciler.dependent.Creator;
5+
import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
6+
import io.javaoperatorsdk.operator.api.reconciler.dependent.Updater;
7+
8+
/**
9+
* Adaptor Class for standalone mode for resources that manages Create, Update and Delete
10+
*
11+
* @param <R> Managed resource
12+
* @param <P> Primary Resource
13+
*/
14+
public abstract class CrudKubernetesDependentResource<R extends HasMetadata, P extends HasMetadata>
15+
extends
16+
KubernetesDependentResource<R, P> implements Creator<R, P>, Updater<R, P>, Deleter<P> {
17+
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
apiVersion: "sample.javaoperatorsdk/v1"
22
kind: WebPage
33
metadata:
4+
labels:
5+
low-level: "true"
46
name: hellows
57
spec:
68
html: |
@@ -9,6 +11,6 @@ spec:
911
<title>Hello Operator World</title>
1012
</head>
1113
<body>
12-
Hello World!
14+
Hello World!
1315
</body>
1416
</html>

sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPage.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,12 @@ public class WebPage extends CustomResource<WebPageSpec, WebPageStatus>
1414
protected WebPageStatus initStatus() {
1515
return new WebPageStatus();
1616
}
17+
18+
@Override
19+
public String toString() {
20+
return "WebPage{" +
21+
"spec=" + spec +
22+
", status=" + status +
23+
'}';
24+
}
1725
}

sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.javaoperatorsdk.operator.sample;
22

33
import java.io.IOException;
4+
import java.util.Arrays;
45

56
import org.slf4j.Logger;
67
import org.slf4j.LoggerFactory;
@@ -26,7 +27,11 @@ public static void main(String[] args) throws IOException {
2627
Config config = new ConfigBuilder().withNamespace(null).build();
2728
KubernetesClient client = new DefaultKubernetesClient(config);
2829
Operator operator = new Operator(client, DefaultConfigurationService.instance());
29-
operator.register(new WebPageReconciler(client));
30+
if (Arrays.stream(args).anyMatch(arg -> arg.equals("--classic"))) {
31+
operator.register(new WebPageReconciler(client));
32+
} else {
33+
operator.register(new WebPageReconcilerDependentResources(client));
34+
}
3035
operator.installShutdownHook();
3136
operator.start();
3237

sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java

Lines changed: 156 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -9,125 +9,192 @@
99
import io.fabric8.kubernetes.api.model.*;
1010
import io.fabric8.kubernetes.api.model.apps.Deployment;
1111
import io.fabric8.kubernetes.client.KubernetesClient;
12+
import io.javaoperatorsdk.operator.ReconcilerUtils;
13+
import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
1214
import io.javaoperatorsdk.operator.api.reconciler.*;
1315
import io.javaoperatorsdk.operator.api.reconciler.Context;
14-
import io.javaoperatorsdk.operator.api.reconciler.dependent.Updater;
15-
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
16-
import io.javaoperatorsdk.operator.processing.event.ResourceID;
17-
import io.javaoperatorsdk.operator.processing.event.source.AssociatedSecondaryResourceIdentifier;
1816
import io.javaoperatorsdk.operator.processing.event.source.EventSource;
17+
import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource;
1918

20-
import static io.javaoperatorsdk.operator.ReconcilerUtils.loadYaml;
2119
import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_FINALIZER;
2220

23-
@ControllerConfiguration(finalizerName = NO_FINALIZER)
21+
/** Shows how to implement reconciler using the low level api directly. */
22+
@ControllerConfiguration(
23+
finalizerName = NO_FINALIZER,
24+
labelSelector = WebPageReconciler.LOW_LEVEL_LABEL_KEY)
2425
public class WebPageReconciler
2526
implements Reconciler<WebPage>, ErrorStatusHandler<WebPage>, EventSourceInitializer<WebPage> {
2627

27-
private final Logger log = LoggerFactory.getLogger(getClass());
28+
public static final String LOW_LEVEL_LABEL_KEY = "low-level";
29+
public static final String INDEX_HTML = "index.html";
2830

29-
private final KubernetesClient kubernetesClient;
31+
private static final Logger log = LoggerFactory.getLogger(WebPageReconciler.class);
3032

31-
private KubernetesDependentResource<ConfigMap, WebPage> configMapDR;
32-
private KubernetesDependentResource<Deployment, WebPage> deploymentDR;
33-
private KubernetesDependentResource<Service, WebPage> serviceDR;
33+
private final KubernetesClient kubernetesClient;
3434

3535
public WebPageReconciler(KubernetesClient kubernetesClient) {
3636
this.kubernetesClient = kubernetesClient;
37-
createDependentResources(kubernetesClient);
3837
}
3938

39+
InformerEventSource configMapEventSource;
40+
4041
@Override
4142
public List<EventSource> prepareEventSources(EventSourceContext<WebPage> context) {
42-
return List.of(
43-
configMapDR.eventSource(context),
44-
deploymentDR.eventSource(context),
45-
serviceDR.eventSource(context));
43+
configMapEventSource =
44+
new InformerEventSource<>(InformerConfiguration.from(context, ConfigMap.class)
45+
.withLabelSelector(LOW_LEVEL_LABEL_KEY)
46+
.build(), context);
47+
var deploymentEventSource =
48+
new InformerEventSource<>(InformerConfiguration.from(context, Deployment.class)
49+
.withLabelSelector(LOW_LEVEL_LABEL_KEY)
50+
.build(), context);
51+
var serviceEventSource =
52+
new InformerEventSource<>(InformerConfiguration.from(context, Service.class)
53+
.withLabelSelector(LOW_LEVEL_LABEL_KEY)
54+
.build(), context);
55+
return List.of(configMapEventSource, deploymentEventSource, serviceEventSource);
4656
}
4757

4858
@Override
4959
public UpdateControl<WebPage> reconcile(WebPage webPage, Context context) {
60+
log.info("Reconciling web page: {}", webPage);
5061
if (webPage.getSpec().getHtml().contains("error")) {
5162
throw new ErrorSimulationException("Simulating error");
5263
}
64+
String ns = webPage.getMetadata().getNamespace();
65+
String configMapName = configMapName(webPage);
66+
String deploymentName = deploymentName(webPage);
67+
68+
69+
ConfigMap desiredHtmlConfigMap = makeDesiredHtmlConfigMap(ns, configMapName, webPage);
70+
Deployment desiredDeployment =
71+
makeDesiredDeployment(webPage, deploymentName, ns, configMapName);
72+
Service desiredService = makeDesiredService(webPage, ns, desiredDeployment);
73+
74+
var previousConfigMap = context.getSecondaryResource(ConfigMap.class).orElse(null);
75+
if (!match(desiredHtmlConfigMap, previousConfigMap)) {
76+
log.info(
77+
"Creating or updating ConfigMap {} in {}",
78+
desiredHtmlConfigMap.getMetadata().getName(),
79+
ns);
80+
kubernetesClient.configMaps().inNamespace(ns).createOrReplace(desiredHtmlConfigMap);
81+
}
5382

54-
configMapDR.reconcile(webPage, context);
55-
deploymentDR.reconcile(webPage, context);
56-
serviceDR.reconcile(webPage, context);
83+
var existingDeployment = context.getSecondaryResource(Deployment.class).orElse(null);
84+
if (!match(desiredDeployment, existingDeployment)) {
85+
log.info(
86+
"Creating or updating Deployment {} in {}",
87+
desiredDeployment.getMetadata().getName(),
88+
ns);
89+
kubernetesClient.apps().deployments().inNamespace(ns).createOrReplace(desiredDeployment);
90+
}
5791

58-
WebPageStatus status = new WebPageStatus();
92+
var existingService = context.getSecondaryResource(Service.class).orElse(null);
93+
if (!match(desiredService, existingService)) {
94+
log.info(
95+
"Creating or updating Deployment {} in {}",
96+
desiredDeployment.getMetadata().getName(),
97+
ns);
98+
kubernetesClient.services().inNamespace(ns).createOrReplace(desiredService);
99+
}
100+
101+
if (previousConfigMap != null && !StringUtils.equals(
102+
previousConfigMap.getData().get(INDEX_HTML),
103+
desiredHtmlConfigMap.getData().get(INDEX_HTML))) {
104+
log.info("Restarting pods because HTML has changed in {}", ns);
105+
kubernetesClient.pods().inNamespace(ns).withLabel("app", deploymentName(webPage)).delete();
106+
}
107+
webPage.setStatus(createStatus(desiredHtmlConfigMap.getMetadata().getName()));
108+
return UpdateControl.updateStatus(webPage);
109+
}
59110

60-
status.setHtmlConfigMap(configMapDR.getResource(webPage).orElseThrow().getMetadata().getName());
111+
private WebPageStatus createStatus(String configMapName) {
112+
WebPageStatus status = new WebPageStatus();
113+
status.setHtmlConfigMap(configMapName);
61114
status.setAreWeGood("Yes!");
62115
status.setErrorMessage(null);
63-
webPage.setStatus(status);
116+
return status;
117+
}
64118

65-
return UpdateControl.updateStatus(webPage);
119+
private boolean match(Deployment desiredDeployment, Deployment deployment) {
120+
if (deployment == null) {
121+
return false;
122+
} else {
123+
return desiredDeployment.getSpec().getReplicas().equals(deployment.getSpec().getReplicas()) &&
124+
desiredDeployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage()
125+
.equals(
126+
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage());
127+
}
66128
}
67129

68-
@Override
69-
public Optional<WebPage> updateErrorStatus(
70-
WebPage resource, RetryInfo retryInfo, RuntimeException e) {
71-
resource.getStatus().setErrorMessage("Error: " + e.getMessage());
72-
return Optional.of(resource);
130+
private boolean match(Service desiredService, Service service) {
131+
if (service == null) {
132+
return false;
133+
}
134+
return desiredService.getSpec().getSelector().equals(service.getSpec().getSelector());
73135
}
74136

75-
private void createDependentResources(KubernetesClient client) {
76-
this.configMapDR = new ConfigMapDependentResource();
77-
78-
this.deploymentDR =
79-
new KubernetesDependentResource<>() {
80-
81-
@Override
82-
protected Deployment desired(WebPage webPage, Context context) {
83-
var deploymentName = deploymentName(webPage);
84-
Deployment deployment = loadYaml(Deployment.class, getClass(), "deployment.yaml");
85-
deployment.getMetadata().setName(deploymentName);
86-
deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace());
87-
deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName);
88-
89-
deployment
90-
.getSpec()
91-
.getTemplate()
92-
.getMetadata()
93-
.getLabels()
94-
.put("app", deploymentName);
95-
deployment
96-
.getSpec()
97-
.getTemplate()
98-
.getSpec()
99-
.getVolumes()
100-
.get(0)
101-
.setConfigMap(
102-
new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build());
103-
return deployment;
104-
}
105-
106-
@Override
107-
protected Class<Deployment> resourceType() {
108-
return Deployment.class;
109-
}
110-
};
111-
112-
this.serviceDR =
113-
new KubernetesDependentResource<>() {
114-
115-
@Override
116-
protected Service desired(WebPage webPage, Context context) {
117-
Service service = loadYaml(Service.class, getClass(), "service.yaml");
118-
service.getMetadata().setName(serviceName(webPage));
119-
service.getMetadata().setNamespace(webPage.getMetadata().getNamespace());
120-
Map<String, String> labels = new HashMap<>();
121-
labels.put("app", deploymentName(webPage));
122-
service.getSpec().setSelector(labels);
123-
return service;
124-
}
125-
126-
@Override
127-
protected Class<Service> resourceType() {
128-
return Service.class;
129-
}
130-
};
137+
private boolean match(ConfigMap desiredHtmlConfigMap, ConfigMap existingConfigMap) {
138+
if (existingConfigMap == null) {
139+
return false;
140+
} else {
141+
return desiredHtmlConfigMap.getData().equals(existingConfigMap.getData());
142+
}
143+
}
144+
145+
private Service makeDesiredService(WebPage webPage, String ns, Deployment desiredDeployment) {
146+
Service desiredService = ReconcilerUtils.loadYaml(Service.class, getClass(), "service.yaml");
147+
desiredService.getMetadata().setName(serviceName(webPage));
148+
desiredService.getMetadata().setNamespace(ns);
149+
desiredService.getMetadata().setLabels(lowLevelLabel());
150+
desiredService
151+
.getSpec()
152+
.setSelector(desiredDeployment.getSpec().getTemplate().getMetadata().getLabels());
153+
desiredService.addOwnerReference(webPage);
154+
return desiredService;
155+
}
156+
157+
private Deployment makeDesiredDeployment(WebPage webPage, String deploymentName, String ns,
158+
String configMapName) {
159+
Deployment desiredDeployment =
160+
ReconcilerUtils.loadYaml(Deployment.class, getClass(), "deployment.yaml");
161+
desiredDeployment.getMetadata().setName(deploymentName);
162+
desiredDeployment.getMetadata().setNamespace(ns);
163+
desiredDeployment.getMetadata().setLabels(lowLevelLabel());
164+
desiredDeployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName);
165+
desiredDeployment.getSpec().getTemplate().getMetadata().getLabels().put("app", deploymentName);
166+
desiredDeployment
167+
.getSpec()
168+
.getTemplate()
169+
.getSpec()
170+
.getVolumes()
171+
.get(0)
172+
.setConfigMap(new ConfigMapVolumeSourceBuilder().withName(configMapName).build());
173+
desiredDeployment.addOwnerReference(webPage);
174+
return desiredDeployment;
175+
}
176+
177+
private ConfigMap makeDesiredHtmlConfigMap(String ns, String configMapName, WebPage webPage) {
178+
Map<String, String> data = new HashMap<>();
179+
data.put("index.html", webPage.getSpec().getHtml());
180+
ConfigMap configMap =
181+
new ConfigMapBuilder()
182+
.withMetadata(
183+
new ObjectMetaBuilder()
184+
.withName(configMapName)
185+
.withNamespace(ns)
186+
.withLabels(lowLevelLabel())
187+
.build())
188+
.withData(data)
189+
.build();
190+
configMap.addOwnerReference(webPage);
191+
return configMap;
192+
}
193+
194+
private Map<String, String> lowLevelLabel() {
195+
Map<String, String> labels = new HashMap<>();
196+
labels.put(LOW_LEVEL_LABEL_KEY, "true");
197+
return labels;
131198
}
132199

133200
private static String configMapName(WebPage nginx) {
@@ -142,45 +209,10 @@ private static String serviceName(WebPage nginx) {
142209
return nginx.getMetadata().getName();
143210
}
144211

145-
private class ConfigMapDependentResource extends KubernetesDependentResource<ConfigMap, WebPage>
146-
implements
147-
AssociatedSecondaryResourceIdentifier<WebPage>, Updater<ConfigMap, WebPage> {
148-
149-
@Override
150-
protected ConfigMap desired(WebPage webPage, Context context) {
151-
Map<String, String> data = new HashMap<>();
152-
data.put("index.html", webPage.getSpec().getHtml());
153-
return new ConfigMapBuilder()
154-
.withMetadata(
155-
new ObjectMetaBuilder()
156-
.withName(WebPageReconciler.configMapName(webPage))
157-
.withNamespace(webPage.getMetadata().getNamespace())
158-
.build())
159-
.withData(data)
160-
.build();
161-
}
162-
163-
@Override
164-
public boolean match(ConfigMap actual, ConfigMap target, Context context) {
165-
return StringUtils.equals(
166-
actual.getData().get("index.html"), target.getData().get("index.html"));
167-
}
168-
169-
@Override
170-
public void update(ConfigMap actual, ConfigMap target, WebPage primary, Context context) {
171-
super.update(actual, target, primary, context);
172-
var ns = actual.getMetadata().getNamespace();
173-
log.info("Restarting pods because HTML has changed in {}", ns);
174-
kubernetesClient
175-
.pods()
176-
.inNamespace(ns)
177-
.withLabel("app", deploymentName(primary))
178-
.delete();
179-
}
180-
181-
@Override
182-
public ResourceID associatedSecondaryID(WebPage primary) {
183-
return new ResourceID(configMapName(primary), primary.getMetadata().getNamespace());
184-
}
212+
@Override
213+
public Optional<WebPage> updateErrorStatus(
214+
WebPage resource, RetryInfo retryInfo, RuntimeException e) {
215+
resource.getStatus().setErrorMessage("Error: " + e.getMessage());
216+
return Optional.of(resource);
185217
}
186218
}

0 commit comments

Comments
 (0)