support setting periodSeconds and failureThreashold in the Keyclock CR (#40117)

* add probe spec

Signed-off-by: AvivGuiser <avivguiser@gmail.com>

* make default for probes if not configured, add skeleton test files

Signed-off-by: AvivGuiser <avivguiser@gmail.com>

* fix tests

Signed-off-by: AvivGuiser <avivguiser@gmail.com>

* fix tests

Signed-off-by: AvivGuiser <avivguiser@gmail.com>

* add docs

Signed-off-by: AvivGuiser <avivguiser@gmail.com>

* move test to unittest and apiserver test

Signed-off-by: AvivGuiser <avivguiser@gmail.com>

* adding asserts to check new fields

Signed-off-by: AvivGuiser <avivguiser@gmail.com>

* fix test

Signed-off-by: AvivGuiser <aviv.guiser@placer.ai>

* update docs

Signed-off-by: AvivGuiser <aviv.guiser@placer.ai>

---------

Signed-off-by: AvivGuiser <avivguiser@gmail.com>
Signed-off-by: AvivGuiser <aviv.guiser@placer.ai>
This commit is contained in:
AvivGuiser
2025-06-13 20:32:20 +03:00
committed by GitHub
parent 76bc9fadcb
commit 7736ca20e9
8 changed files with 125 additions and 28 deletions
@@ -48,6 +48,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.CacheSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpManagementSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.ProbeSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.SchedulingSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.Truststore;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TruststoreSource;
@@ -327,6 +328,9 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
// probes
var protocol = isTlsConfigured(keycloakCR) ? "HTTPS" : "HTTP";
var port = HttpManagementSpec.managementPort(keycloakCR);
var readinessOptionalSpec = Optional.ofNullable(keycloakCR.getSpec().getReadinessProbeSpec());
var livenessOptionalSpec = Optional.ofNullable(keycloakCR.getSpec().getLivenessProbeSpec());
var startupOptionalSpec = Optional.ofNullable(keycloakCR.getSpec().getStartupProbeSpec());
var relativePath = readConfigurationValue(Constants.KEYCLOAK_HTTP_MANAGEMENT_RELATIVE_PATH_KEY, keycloakCR, context)
.or(() -> readConfigurationValue(Constants.KEYCLOAK_HTTP_RELATIVE_PATH_KEY, keycloakCR, context))
.map(path -> !path.endsWith("/") ? path + "/" : path)
@@ -334,8 +338,8 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
if (!containerBuilder.hasReadinessProbe()) {
containerBuilder.withNewReadinessProbe()
.withPeriodSeconds(10)
.withFailureThreshold(3)
.withPeriodSeconds(readinessOptionalSpec.map(ProbeSpec::getProbePeriodSeconds).orElse(10))
.withFailureThreshold(readinessOptionalSpec.map(ProbeSpec::getProbeFailureThreshold).orElse(3))
.withNewHttpGet()
.withScheme(protocol)
.withNewPort(port)
@@ -345,8 +349,8 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
}
if (!containerBuilder.hasLivenessProbe()) {
containerBuilder.withNewLivenessProbe()
.withPeriodSeconds(10)
.withFailureThreshold(3)
.withPeriodSeconds(livenessOptionalSpec.map(ProbeSpec::getProbePeriodSeconds).orElse(10))
.withFailureThreshold(livenessOptionalSpec.map(ProbeSpec::getProbeFailureThreshold).orElse(3))
.withNewHttpGet()
.withScheme(protocol)
.withNewPort(port)
@@ -356,8 +360,8 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
}
if (!containerBuilder.hasStartupProbe()) {
containerBuilder.withNewStartupProbe()
.withPeriodSeconds(1)
.withFailureThreshold(600)
.withPeriodSeconds(startupOptionalSpec.map(ProbeSpec::getProbePeriodSeconds).orElse(1))
.withFailureThreshold(startupOptionalSpec.map(ProbeSpec::getProbeFailureThreshold).orElse(600))
.withNewHttpGet()
.withScheme(protocol)
.withNewPort(port)
@@ -29,6 +29,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpManagementSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.IngressSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.NetworkPolicySpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.ProbeSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.ProxySpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.SchedulingSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TracingSpec;
@@ -135,6 +136,20 @@ public class KeycloakSpec {
@JsonPropertyDescription("Configuration related to Keycloak deployment updates.")
private UpdateSpec updateSpec;
@JsonProperty("readinessProbe")
@JsonPropertyDescription("Configuration for readiness probe, by default it is 10 for periodSeconds and 3 for failureThreshold")
private ProbeSpec readinessProbeSpec;
@JsonProperty("livenessProbe")
@JsonPropertyDescription("Configuration for liveness probe, by default it is 10 for periodSeconds and 3 for failureThreshold")
private ProbeSpec livenessProbeSpec;
@JsonProperty("startupProbe")
@JsonPropertyDescription("Configuration for startup probe, by default it is 1 for periodSeconds and 600 for failureThreshold")
private ProbeSpec startupProbeSpec;
public HttpSpec getHttpSpec() {
return httpSpec;
}
@@ -316,4 +331,22 @@ public class KeycloakSpec {
public void setUpdateSpec(UpdateSpec updateSpec) {
this.updateSpec = updateSpec;
}
public ProbeSpec getLivenessProbeSpec() {return livenessProbeSpec;}
public void setLivenessProbeSpec(ProbeSpec livenessProbeSpec) {
this.livenessProbeSpec = livenessProbeSpec;
}
public ProbeSpec getReadinessProbeSpec() {return readinessProbeSpec;}
public void setReadinessProbeSpec(ProbeSpec readinessProbeSpec) {
this.readinessProbeSpec = readinessProbeSpec;
}
public ProbeSpec getStartupProbeSpec() {return startupProbeSpec;}
public void setStartupProbeSpec(ProbeSpec startupProbeSpec) {
this.startupProbeSpec = startupProbeSpec;
}
}
@@ -0,0 +1,22 @@
package org.keycloak.operator.crds.v2alpha1.deployment.spec;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.sundr.builder.annotations.Buildable;
@JsonInclude(JsonInclude.Include.NON_NULL)
@Buildable(editableEnabled = false, builderPackage = "io.fabric8.kubernetes.api.builder")
public class ProbeSpec {
@JsonProperty("periodSeconds")
private int probePeriodSeconds;
@JsonProperty("failureThreshold")
private int probeFailureThreshold;
public int getProbeFailureThreshold() {return probeFailureThreshold;}
public void setProbeFailureThreshold(int probeFailureThreshold) {this.probeFailureThreshold = probeFailureThreshold;}
public int getProbePeriodSeconds() {return probePeriodSeconds;}
public void setProbePeriodSeconds(int probePeriodSeconds) {this.probePeriodSeconds = probePeriodSeconds;}
}
@@ -47,6 +47,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusCondition;
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.BootstrapAdminSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpecBuilder;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.ProbeSpec;
import org.keycloak.operator.testsuite.apiserver.DisabledIfApiServerTest;
import org.keycloak.operator.testsuite.unit.WatchedResourcesTest;
import org.keycloak.operator.testsuite.utils.CRAssert;
@@ -102,6 +102,14 @@ public class CRSerializationTest {
HttpManagementSpec managementSpec = keycloak.getSpec().getHttpManagementSpec();
assertNotNull(managementSpec);
assertEquals(9003, managementSpec.getPort());
assertEquals(50,keycloak.getSpec().getReadinessProbeSpec().getProbePeriodSeconds());
assertEquals(3,keycloak.getSpec().getReadinessProbeSpec().getProbeFailureThreshold());
assertEquals(60,keycloak.getSpec().getLivenessProbeSpec().getProbePeriodSeconds());
assertEquals(1,keycloak.getSpec().getLivenessProbeSpec().getProbeFailureThreshold());
assertEquals(40,keycloak.getSpec().getStartupProbeSpec().getProbePeriodSeconds());
assertEquals(2,keycloak.getSpec().getStartupProbeSpec().getProbeFailureThreshold());
}
@Test
@@ -676,6 +676,37 @@ public class PodTemplateTest {
assertThat(podTemplate.getSpec().getAffinity()).isNotEqualTo(affinity);
}
@Test
public void testProbe(){
PodTemplateSpec additionalPodTemplate = null;
var readinessProbe = new ProbeBuilder().withFailureThreshold(1).withPeriodSeconds(2).build();
var livenessProbe = new ProbeBuilder().withFailureThreshold(3).withPeriodSeconds(4).build();
var startupProbe = new ProbeBuilder().withFailureThreshold(5).withPeriodSeconds(6).build();
var readinessPodTemplate = getDeployment(additionalPodTemplate, null,
s-> s.withNewReadinessProbeSpec()
.withProbeFailureThreshold(1)
.withProbePeriodSeconds(2)
.endReadinessProbeSpec()).getSpec().getTemplate();
assertThat(readinessPodTemplate.getSpec().getContainers().get(0).getReadinessProbe().getPeriodSeconds()).isEqualTo(readinessProbe.getPeriodSeconds());
assertThat(readinessPodTemplate.getSpec().getContainers().get(0).getReadinessProbe().getFailureThreshold()).isEqualTo(readinessProbe.getFailureThreshold());
var livenessPodTemplate = getDeployment(additionalPodTemplate, null,
s-> s.withNewLivenessProbeSpec()
.withProbeFailureThreshold(3)
.withProbePeriodSeconds(4)
.endLivenessProbeSpec()).getSpec().getTemplate();
assertThat(livenessPodTemplate.getSpec().getContainers().get(0).getLivenessProbe().getPeriodSeconds()).isEqualTo(livenessProbe.getPeriodSeconds());
assertThat(livenessPodTemplate.getSpec().getContainers().get(0).getLivenessProbe().getFailureThreshold()).isEqualTo(livenessProbe.getFailureThreshold());
var startupPodTemplate = getDeployment(additionalPodTemplate, null,
s-> s.withNewStartupProbeSpec()
.withProbeFailureThreshold(5)
.withProbePeriodSeconds(6)
.endStartupProbeSpec()).getSpec().getTemplate();
assertThat(startupPodTemplate.getSpec().getContainers().get(0).getStartupProbe().getPeriodSeconds()).isEqualTo(startupProbe.getPeriodSeconds());
assertThat(startupPodTemplate.getSpec().getContainers().get(0).getStartupProbe().getFailureThreshold()).isEqualTo(startupProbe.getFailureThreshold());
}
private Job getUpdateJob(Consumer<KeycloakSpecBuilder> newSpec, Consumer<KeycloakSpecBuilder> oldSpec, Consumer<StatefulSetBuilder> existingModifier) {
// create an existing from the old spec and modifier
StatefulSetBuilder existingBuilder = getDeployment(null, null, oldSpec).toBuilder();
@@ -32,6 +32,15 @@ spec:
annotations:
myAnnotation: myValue
anotherAnnotation: anotherValue
readinessProbe:
periodSeconds: 50
failureThreshold: 3
livenessProbe:
periodSeconds: 60
failureThreshold: 1
startupProbe:
periodSeconds: 40
failureThreshold: 2
networkPolicy:
enabled: true
http: