Skip to content

Commit

Permalink
docs: migrate v5 changes to docsy (#2397)
Browse files Browse the repository at this point in the history
Signed-off-by: Attila Mészáros <csviri@gmail.com>
  • Loading branch information
csviri authored May 21, 2024
1 parent cc57156 commit 5337e79
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 271 deletions.
251 changes: 84 additions & 167 deletions docsy/content/en/docs/dependent-resources/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ title: Dependent Resources
weight: 60
---

# Dependent Resources

## Motivations and Goals

Most operators need to deal with secondary resources when trying to realize the desired state
Expand Down Expand Up @@ -97,13 +99,13 @@ and labels, which are ignored by default:

```java
public class MyDependentResource extends KubernetesDependentResource<MyDependent, MyPrimary>
implements Matcher<MyDependent, MyPrimary> {
// your implementation
implements Matcher<MyDependent, MyPrimary> {
// your implementation

public Result<MyDependent> match(MyDependent actualResource, MyPrimary primary,
Context<MyPrimary> context) {
return GenericKubernetesResourceMatcher.match(this, actualResource, primary, context, true);
}
public Result<MyDependent> match(MyDependent actualResource, MyPrimary primary,
Context<MyPrimary> context) {
return GenericKubernetesResourceMatcher.match(this, actualResource, primary, context, true);
}
}
```

Expand Down Expand Up @@ -137,24 +139,24 @@ Deleted (or set to be garbage collected). The following example shows how to cre
@KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR)
class DeploymentDependentResource extends CRUDKubernetesDependentResource<Deployment, WebPage> {

public DeploymentDependentResource() {
super(Deployment.class);
}

@Override
protected Deployment desired(WebPage webPage, Context<WebPage> context) {
var deploymentName = deploymentName(webPage);
Deployment deployment = loadYaml(Deployment.class, getClass(), "deployment.yaml");
deployment.getMetadata().setName(deploymentName);
deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace());
deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName);

deployment.getSpec().getTemplate().getMetadata().getLabels()
.put("app", deploymentName);
deployment.getSpec().getTemplate().getSpec().getVolumes().get(0)
.setConfigMap(new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build());
return deployment;
}
public DeploymentDependentResource() {
super(Deployment.class);
}

@Override
protected Deployment desired(WebPage webPage, Context<WebPage> context) {
var deploymentName = deploymentName(webPage);
Deployment deployment = loadYaml(Deployment.class, getClass(), "deployment.yaml");
deployment.getMetadata().setName(deploymentName);
deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace());
deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName);

deployment.getSpec().getTemplate().getMetadata().getLabels()
.put("app", deploymentName);
deployment.getSpec().getTemplate().getSpec().getVolumes().get(0)
.setConfigMap(new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build());
return deployment;
}
}
```

Expand Down Expand Up @@ -190,25 +192,25 @@ instances are managed by JOSDK, an example of which can be seen below:
```java

@ControllerConfiguration(
labelSelector = SELECTOR,
dependents = {
@Dependent(type = ConfigMapDependentResource.class),
@Dependent(type = DeploymentDependentResource.class),
@Dependent(type = ServiceDependentResource.class)
})
labelSelector = SELECTOR,
dependents = {
@Dependent(type = ConfigMapDependentResource.class),
@Dependent(type = DeploymentDependentResource.class),
@Dependent(type = ServiceDependentResource.class)
})
public class WebPageManagedDependentsReconciler
implements Reconciler<WebPage>, ErrorStatusHandler<WebPage> {
implements Reconciler<WebPage>, ErrorStatusHandler<WebPage> {

// omitted code
// omitted code

@Override
public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> context) {
@Override
public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> context) {

final var name = context.getSecondaryResource(ConfigMap.class).orElseThrow()
.getMetadata().getName();
webPage.setStatus(createStatus(name));
return UpdateControl.patchStatus(webPage);
}
final var name = context.getSecondaryResource(ConfigMap.class).orElseThrow()
.getMetadata().getName();
webPage.setStatus(createStatus(name));
return UpdateControl.patchStatus(webPage);
}

}
```
Expand All @@ -223,104 +225,11 @@ It is also possible to wire dependent resources programmatically. In practice th
developer is responsible for initializing and managing the dependent resources as well as calling
their `reconcile` method. However, this makes it possible for developers to fully customize the
reconciliation process. Standalone dependent resources should be used in cases when the managed use
case does not fit.

Note that [Workflows](https://javaoperatorsdk.io/docs/workflows) also can be invoked from standalone
resources.

The following sample is similar to the one above, simply performing additional checks, and
conditionally creating an `Ingress`:

```java

@ControllerConfiguration
public class WebPageStandaloneDependentsReconciler
implements Reconciler<WebPage>, ErrorStatusHandler<WebPage>,
EventSourceInitializer<WebPage> {

private KubernetesDependentResource<ConfigMap, WebPage> configMapDR;
private KubernetesDependentResource<Deployment, WebPage> deploymentDR;
private KubernetesDependentResource<Service, WebPage> serviceDR;
private KubernetesDependentResource<Service, WebPage> ingressDR;

public WebPageStandaloneDependentsReconciler(KubernetesClient kubernetesClient) {
// 1.
createDependentResources(kubernetesClient);
}

@Override
public List<EventSource> prepareEventSources(EventSourceContext<WebPage> context) {
// 2.
return List.of(
configMapDR.initEventSource(context),
deploymentDR.initEventSource(context),
serviceDR.initEventSource(context));
}

@Override
public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> context) {

// 3.
if (!isValidHtml(webPage.getHtml())) {
return UpdateControl.patchStatus(setInvalidHtmlErrorMessage(webPage));
}

// 4.
configMapDR.reconcile(webPage, context);
deploymentDR.reconcile(webPage, context);
serviceDR.reconcile(webPage, context);

// 5.
if (Boolean.TRUE.equals(webPage.getSpec().getExposed())) {
ingressDR.reconcile(webPage, context);
} else {
ingressDR.delete(webPage, context);
}
case does not fit. You can, of course, also use [Workflows](https://javaoperatorsdk.io/docs/workflows) when managing
resources programmatically.

// 6.
webPage.setStatus(
createStatus(configMapDR.getResource(webPage).orElseThrow().getMetadata().getName()));
return UpdateControl.patchStatus(webPage);
}

private void createDependentResources(KubernetesClient client) {
this.configMapDR = new ConfigMapDependentResource();
this.deploymentDR = new DeploymentDependentResource();
this.serviceDR = new ServiceDependentResource();
this.ingressDR = new IngressDependentResource();

Arrays.asList(configMapDR, deploymentDR, serviceDR, ingressDR).forEach(dr -> {
dr.setKubernetesClient(client);
dr.configureWith(new KubernetesDependentResourceConfig()
.setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR));
});
}

// omitted code
}
```

There are multiple things happening here:

1. Dependent resources are explicitly created and can be access later by reference.
2. Event sources are produced by the dependent resources, but needs to be explicitly registered in
this case by implementing
the [`EventSourceInitializer`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializer.java)
interface.
3. The input html is validated, and error message is set in case it is invalid.
4. Reconciliation of dependent resources is called explicitly, but here the workflow
customization is fully in the hand of the developer.
5. An `Ingress` is created but only in case `exposed` flag set to true on custom resource. Tries to
delete it if not.
6. Status is set in a different way, this is just an alternative way to show, that the actual state
can be read using the reference. This could be written in a same way as in the managed example.

See the full source code of
sample [here](https://github.com/operator-framework/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java)
.

Note also the Workflows feature makes it possible to also support this conditional creation use
case in managed dependent resources.
You can see a commented example of how to do
so [here](https://github.com/operator-framework/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java).

## Creating/Updating Kubernetes Resources

Expand Down Expand Up @@ -353,17 +262,17 @@ Since SSA is a complex feature, JOSDK implements a feature flag allowing users t
these implementations. See
in [ConfigurationService](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java#L332-L358).

It is, however, important to note that these implementations are default, generic
implementations that the framework can provide expected behavior out of the box. In many
situations, these will work just fine but it is also possible to provide matching algorithms
It is, however, important to note that these implementations are default, generic
implementations that the framework can provide expected behavior out of the box. In many
situations, these will work just fine but it is also possible to provide matching algorithms
optimized for specific use cases. This is easily done by simply overriding
the `match(...)` [method](https://github.com/java-operator-sdk/java-operator-sdk/blob/e16559fd41bbb8bef6ce9d1f47bffa212a941b09/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java#L156-L156).
the `match(...)` [method](https://github.com/java-operator-sdk/java-operator-sdk/blob/e16559fd41bbb8bef6ce9d1f47bffa212a941b09/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java#L156-L156).

It is also possible to bypass the matching logic altogether to simply rely on the server-side
It is also possible to bypass the matching logic altogether to simply rely on the server-side
apply mechanism if always sending potentially unchanged resources to the cluster is not an issue.
JOSDK's matching mechanism allows to spare some potentially useless calls to the Kubernetes API
server. To bypass the matching feature completely, simply override the `match` method to always
return `false`, thus telling JOSDK that the actual state never matches the desired one, making
server. To bypass the matching feature completely, simply override the `match` method to always
return `false`, thus telling JOSDK that the actual state never matches the desired one, making
it always update the resources using SSA.

WARNING: Older versions of Kubernetes before 1.25 would create an additional resource version for every SSA update
Expand All @@ -390,20 +299,25 @@ tests [here](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/op

When dealing with multiple dependent resources of same type, the dependent resource implementation
needs to know which specific resource should be targeted when reconciling a given dependent
resource, since there will be multiple instances of that type which could possibly be used, each
associated with the same primary resource. In order to do this, JOSDK relies on the
[resource discriminator](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java)
concept. Resource discriminators uniquely identify the target resource of a dependent resource.
In the managed Kubernetes dependent resources case, the discriminator can be declaratively set
using the `@KubernetesDependent` annotation:

```java

@KubernetesDependent(resourceDiscriminator = ConfigMap1Discriminator.class)
public class MultipleManagedDependentResourceConfigMap1 {
//...
}
```
resource, since there could be multiple instances of that type which could possibly be used, each
associated with the same primary resource. In this situation, JOSDK automatically selects the appropriate secondary
resource matching the desired state associated with the primary resource. This makes sense because the desired
state computation already needs to be able to discriminate among multiple related secondary resources to tell JOSDK how
they should be reconciled.

There might be casees, though, where it might be problematic to call the `desired` method several times (for example, because it is costly to do so), it is always possible to override this automated discrimination using several means:

- Implement your own `getSecondaryResource` method on your `DependentResource` implementation from scratch.
- Override the `selectManagedSecondaryResource` method, if your `DependentResource` extends `AbstractDependentResource`.
This should be relatively simple to override this method to optimize the matching to your needs. You can see an
example of such an implementation in
the [`ExternalWithStateDependentResource`](https://github.com/operator-framework/java-operator-sdk/blob/6cd0f884a7c9b60c81bd2d52da54adbd64d6e118/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/ExternalWithStateDependentResource.java#L43-L49)
class.
- Override the `managedSecondaryResourceID` method, if your `DependentResource` extends `KubernetesDependentResource`,
where it's very often possible to easily determine the `ResourceID` of the secondary resource. This would probably be
the easiest solution if you're working with Kubernetes resources.

### Sharing an Event Source Between Dependent Resources

Dependent resources usually also provide event sources. When dealing with multiple dependents of
the same type, one needs to decide whether these dependent resources should track the same
Expand All @@ -419,10 +333,10 @@ would look as follows:
useEventSourceWithName = "configMapSource")
```

A sample is provided as an integration test both
for [managed](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedDependentSameTypeIT.java)
and
for [standalone](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentResourceIT.java)
A sample is provided as an integration test both:
for [managed](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedDependentNoDiscriminatorIT.java)

For [standalone](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentResourceIT.java)
cases.

## Bulk Dependent Resources
Expand Down Expand Up @@ -485,15 +399,18 @@ also be created, one per dependent resource.
See [integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/ExternalStateBulkIT.java)
as a sample.


## GenericKubernetesResource based Dependent Resources

In rare circumstances resource handling where there is no class representation or just typeless handling might be needed.
Fabric8 Client provides [GenericKubernetesResource](https://github.com/fabric8io/kubernetes-client/blob/main/doc/CHEATSHEET.md#resource-typeless-api)
to support that.
In rare circumstances resource handling where there is no class representation or just typeless handling might be
needed.
Fabric8 Client
provides [GenericKubernetesResource](https://github.com/fabric8io/kubernetes-client/blob/main/doc/CHEATSHEET.md#resource-typeless-api)
to support that.

For dependent resource this is supported by [GenericKubernetesDependentResource](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesDependentResource.java#L8-L8)
. See samples [here](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource).
For dependent resource this is supported
by [GenericKubernetesDependentResource](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesDependentResource.java#L8-L8)
. See
samples [here](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource).

## Other Dependent Resource Features

Expand Down
Loading

0 comments on commit 5337e79

Please sign in to comment.