Skip to content

Add Webpage Sample to V2 + fix for observed generation #693

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Nov 23, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.javaoperatorsdk.operator.api;

import java.util.Optional;

import io.fabric8.kubernetes.client.CustomResource;
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;

Expand All @@ -24,6 +22,6 @@ public interface ObservedGenerationAware {

void setObservedGeneration(Long generation);

Optional<Long> getObservedGeneration();
Long getObservedGeneration();

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.javaoperatorsdk.operator.api;

import java.util.Optional;

/**
* A helper base class for status sub-resources classes to extend to support generate awareness.
*/
Expand All @@ -15,7 +13,7 @@ public void setObservedGeneration(Long generation) {
}

@Override
public Optional<Long> getObservedGeneration() {
return Optional.ofNullable(observedGeneration);
public Long getObservedGeneration() {
return observedGeneration;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ private PostExecutionControl<R> handleDispatch(ExecutionScope<R> executionScope)
Context context =
new DefaultContext(executionScope.getRetryInfo());
if (markedForDeletion) {
return handleDelete(resource, context);
return handleCleanup(resource, context);
} else {
return handleCreateOrUpdate(executionScope, resource, context);
return handleReconcile(executionScope, resource, context);
}
}

Expand All @@ -99,7 +99,7 @@ private boolean shouldNotDispatchToDelete(R resource) {
return configuration().useFinalizer() && !resource.hasFinalizer(configuration().getFinalizer());
}

private PostExecutionControl<R> handleCreateOrUpdate(
private PostExecutionControl<R> handleReconcile(
ExecutionScope<R> executionScope, R resource, Context context) {
if (configuration().useFinalizer() && !resource.hasFinalizer(configuration().getFinalizer())) {
/*
Expand All @@ -114,7 +114,7 @@ private PostExecutionControl<R> handleCreateOrUpdate(
try {
var resourceForExecution =
cloneResourceForErrorStatusHandlerIfNeeded(resource, context);
return createOrUpdateExecution(executionScope, resourceForExecution, context);
return reconcileExecution(executionScope, resourceForExecution, context);
} catch (RuntimeException e) {
handleLastAttemptErrorStatusHandler(resource, context, e);
throw e;
Expand All @@ -137,7 +137,7 @@ private R cloneResourceForErrorStatusHandlerIfNeeded(R resource, Context context
}
}

private PostExecutionControl<R> createOrUpdateExecution(ExecutionScope<R> executionScope,
private PostExecutionControl<R> reconcileExecution(ExecutionScope<R> executionScope,
R resource, Context context) {
log.debug(
"Executing createOrUpdate for resource {} with version: {} with execution scope: {}",
Expand Down Expand Up @@ -222,7 +222,7 @@ private void updatePostExecutionControlWithReschedule(
baseControl.getScheduleDelay().ifPresent(postExecutionControl::withReSchedule);
}

private PostExecutionControl<R> handleDelete(R resource, Context context) {
private PostExecutionControl<R> handleCleanup(R resource, Context context) {
log.debug(
"Executing delete for resource: {} with version: {}",
getName(resource),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public final class ResourceEventFilters {
var actualGeneration = newResource.getMetadata().getGeneration();
var observedGeneration = ((ObservedGenerationAware) status)
.getObservedGeneration();
return observedGeneration.map(aLong -> actualGeneration > aLong).orElse(true);
return observedGeneration == null || actualGeneration > observedGeneration;
}
}
return oldResource == null || !generationAware ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ void setObservedGenerationForStatusIfNeeded() {

PostExecutionControl<ObservedGenCustomResource> control = lDispatcher.handleExecution(
executionScopeWithCREvent(observedGenResource));
assertThat(control.getUpdatedCustomResource().get().getStatus().getObservedGeneration().get())
assertThat(control.getUpdatedCustomResource().get().getStatus().getObservedGeneration())
.isEqualTo(1L);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.javaoperatorsdk.operator;

import java.util.concurrent.TimeUnit;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.javaoperatorsdk.operator.config.runtime.DefaultConfigurationService;
import io.javaoperatorsdk.operator.junit.OperatorExtension;
import io.javaoperatorsdk.operator.sample.observedgeneration.ObservedGenerationTestCustomResource;
import io.javaoperatorsdk.operator.sample.observedgeneration.ObservedGenerationTestReconciler;

import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;

public class ObservedGenerationHandlingIT {
@RegisterExtension
OperatorExtension operator =
OperatorExtension.builder()
.withConfigurationService(DefaultConfigurationService.instance())
.withReconciler(new ObservedGenerationTestReconciler())
.build();

@Test
public void testReconciliationOfNonCustomResourceAndStatusUpdate() {
var resource = new ObservedGenerationTestCustomResource();
resource.setMetadata(new ObjectMeta());
resource.getMetadata().setName("observed-gen1");

var createdResource = operator.create(ObservedGenerationTestCustomResource.class, resource);

await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
var d = operator.get(ObservedGenerationTestCustomResource.class,
createdResource.getMetadata().getName());
assertThat(d.getStatus().getObservedGeneration()).isNotNull();
assertThat(d.getStatus().getObservedGeneration()).isEqualTo(1);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.javaoperatorsdk.operator.sample.observedgeneration;

import io.fabric8.kubernetes.api.model.Namespaced;
import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.model.annotation.Group;
import io.fabric8.kubernetes.model.annotation.Kind;
import io.fabric8.kubernetes.model.annotation.ShortNames;
import io.fabric8.kubernetes.model.annotation.Version;

@Group("sample.javaoperatorsdk")
@Version("v1")
@Kind("ObservedGenerationTestCustomResource")
@ShortNames("og")
public class ObservedGenerationTestCustomResource
extends CustomResource<Void, ObservedGenerationTestCustomResourceStatus>
implements Namespaced {

@Override
protected ObservedGenerationTestCustomResourceStatus initStatus() {
return new ObservedGenerationTestCustomResourceStatus();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.javaoperatorsdk.operator.sample.observedgeneration;

import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus;

public class ObservedGenerationTestCustomResourceStatus extends ObservedGenerationAwareStatus {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.javaoperatorsdk.operator.sample.observedgeneration;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.javaoperatorsdk.operator.api.reconciler.*;

import static io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.NO_FINALIZER;

@ControllerConfiguration(finalizerName = NO_FINALIZER)
public class ObservedGenerationTestReconciler
implements Reconciler<ObservedGenerationTestCustomResource> {

private static final Logger log = LoggerFactory.getLogger(ObservedGenerationTestReconciler.class);

@Override
public UpdateControl<ObservedGenerationTestCustomResource> reconcile(
ObservedGenerationTestCustomResource resource, Context context) {
log.info("Reconcile ObservedGenerationTestCustomResource: {}",
resource.getMetadata().getName());
return UpdateControl.updateStatusSubResource(resource);
}
}
1 change: 1 addition & 0 deletions sample-operators/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

<modules>
<module>tomcat-operator</module>
<module>webpage</module>
</modules>

<dependencies>
Expand Down
53 changes: 53 additions & 0 deletions sample-operators/webpage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# WebServer Operator

This is a simple example of how a Custom Resource backed by an Operator can serve as
an abstraction layer. This Operator will use a webserver resource, which mainly contains a
static webpage definition and creates an NGINX Deployment backed by a ConfigMap which holds
the HTML.

This is an example input:
```yaml
apiVersion: "sample.javaoperatorsdk/v1"
kind: WebPage
metadata:
name: mynginx-hello
spec:
html: |
<html>
<head>
<title>Webserver Operator</title>
</head>
<body>
Hello World!
</body>
</html>
```

### Try

The quickest way to try the operator is to run it on your local machine, while it connects to a local or remote
Kubernetes cluster. When you start it, it will use the current kubectl context on your machine to connect to the cluster.

Before you run it you have to install the CRD on your cluster by running `kubectl apply -f k8s/crd.yaml`

When the Operator is running you can create some Webserver Custom Resources. You can find a sample custom resource in
`k8s/webpage.yaml`. You can create it by running `kubectl apply -f k8s/webpage.yaml`

After the Operator has picked up the new webserver resource (see the logs) it should create the NGINX server in the
same namespace where the webserver resource is created. To connect to the server using your browser you can
run `kubectl get service` and view the service created by the Operator. It should have a NodePort configured. If you are
running a single-node cluster (e.g. Docker for Mac or Minikube) you can connect to the VM on this port to access the
page. Otherwise you can change the service to a LoadBalancer (e.g on a public cloud).

You can also try to change the HTML code in `k8s/webpage.yaml` and do another `kubectl apply -f k8s/webpage.yaml`.
This should update the actual NGINX deployment with the new configuration.

### Build

You can build the sample using `mvn jib:dockerBuild` this will produce a Docker image you can push to the registry
of your choice. The JAR file is built using your local Maven and JDK and then copied into the Docker image.

### Deployment

1. Deploy the CRD: `kubectl apply -f k8s/crd.yaml`
2. Deploy the operator: `kubectl apply -f k8s/operator.yaml`
39 changes: 39 additions & 0 deletions sample-operators/webpage/k8s/crd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
apiVersion: apiextensions.k8s.io/v1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be needed: the generator should be used for that and what we want people to use instead of manually writing CRDs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed. removed crd.yaml addd dependency.

kind: CustomResourceDefinition
metadata:
name: webpages.sample.javaoperatorsdk
spec:
group: sample.javaoperatorsdk
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
html:
type: string
status:
type: object
properties:
observedGeneration:
type: integer
htmlConfigMap:
type: string
areWeGood:
type: string
errorMessage:
type: string
subresources:
status: { }
scope: Namespaced
names:
plural: webpages
singular: webpage
kind: WebPage
shortNames:
- wp
98 changes: 98 additions & 0 deletions sample-operators/webpage/k8s/operator.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
apiVersion: v1
kind: Namespace
metadata:
name: webserver-operator

---
apiVersion: v1
kind: ServiceAccount
metadata:
name: webserver-operator
namespace: webserver-operator

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: webserver-operator
namespace: webserver-operator
spec:
selector:
matchLabels:
app: webserver-operator
replicas: 1
template:
metadata:
labels:
app: webserver-operator
spec:
serviceAccountName: webserver-operator
containers:
- name: operator
image: webserver-operator
imagePullPolicy: Never
ports:
- containerPort: 80
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 1
timeoutSeconds: 1
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
timeoutSeconds: 1

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: operator-admin
subjects:
- kind: ServiceAccount
name: webserver-operator
namespace: webserver-operator
roleRef:
kind: ClusterRole
name: webserver-operator
apiGroup: ""

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: webserver-operator
rules:
- apiGroups:
- ""
resources:
- deployments
- services
- configmaps
- pods
verbs:
- '*'
- apiGroups:
- "apps"
resources:
- deployments
- services
- configmaps
verbs:
- '*'
- apiGroups:
- "apiextensions.k8s.io"
resources:
- customresourcedefinitions
verbs:
- '*'
- apiGroups:
- "sample.javaoperatorsdk"
resources:
- webservers
- webservers/status
verbs:
- '*'
Loading