use of org.entando.kubernetes.controller.spi.client.SerializedEntandoResource in project entando-k8s-controller-coordinator by entando-k8s.
the class DefaultSimpleKubernetesClient method performStatusUpdate.
private SerializedEntandoResource performStatusUpdate(SerializedEntandoResource customResource, Consumer<SerializedEntandoResource> consumer, UnaryOperator<EventBuilder> eventPopulator) {
final EventBuilder doneableEvent = new EventBuilder().withNewMetadata().withNamespace(customResource.getMetadata().getNamespace()).withName(customResource.getMetadata().getName() + "-" + NameUtils.randomNumeric(8)).withOwnerReferences(ResourceUtils.buildOwnerReference(customResource)).withLabels(ResourceUtils.labelsFromResource(customResource)).endMetadata().withCount(1).withFirstTimestamp(dateTimeFormatter.get().format(LocalDateTime.now())).withLastTimestamp(dateTimeFormatter.get().format(LocalDateTime.now())).withNewSource(NameUtils.controllerNameOf(customResource), null).withNewInvolvedObject().withKind(customResource.getKind()).withNamespace(customResource.getMetadata().getNamespace()).withName(customResource.getMetadata().getName()).withUid(customResource.getMetadata().getUid()).withResourceVersion(customResource.getMetadata().getResourceVersion()).withApiVersion(customResource.getApiVersion()).withFieldPath("status").endInvolvedObject();
client.v1().events().inNamespace(customResource.getMetadata().getNamespace()).create(eventPopulator.apply(doneableEvent).build());
return ioSafe(() -> {
SerializedEntandoResource ser = customResource;
CustomResourceDefinitionContext definition = Optional.ofNullable(ser.getDefinition()).orElse(resolveDefinitionContext(ser));
ser.setDefinition(definition);
RawCustomResourceOperationsImpl resource = client.customResource(definition).inNamespace(customResource.getMetadata().getNamespace()).withName(customResource.getMetadata().getName());
final ObjectMapper objectMapper = new ObjectMapper();
ser = objectMapper.readValue(objectMapper.writeValueAsString(resource.get()), SerializedEntandoResource.class);
ser.setDefinition(definition);
consumer.accept(ser);
final Map<String, Object> map = resource.updateStatus(objectMapper.writeValueAsString(ser));
return objectMapper.readValue(objectMapper.writeValueAsString(map), SerializedEntandoResource.class);
});
}
use of org.entando.kubernetes.controller.spi.client.SerializedEntandoResource in project entando-k8s-controller-coordinator by entando-k8s.
the class EntandoResourceObserver method processGenerationIncrement.
private boolean processGenerationIncrement(SerializedEntandoResource newResource) {
OperatorProcessingInstruction instruction = CoordinatorUtils.resolveProcessingInstruction(newResource);
if (instruction == OperatorProcessingInstruction.FORCE) {
// Remove to avoid recursive updates
final SerializedEntandoResource latestResource = operations.removeAnnotation(newResource, AnnotationNames.PROCESSING_INSTRUCTION.getName());
markResourceVersionProcessed(latestResource);
logResource(Level.FINE, "Processing of %s %s/%s has been forced using entando.org/processing-instruction.", latestResource);
return true;
} else if (instruction == OperatorProcessingInstruction.DEFER || instruction == OperatorProcessingInstruction.IGNORE) {
logResource(Level.FINE, "Processing of %s %s/%s has been deferred or ignored using entando.org/processing-instruction.", newResource);
return false;
} else {
final boolean needsObservation = newResource.getStatus().getObservedGeneration() == null || newResource.getMetadata().getGeneration() == null || newResource.getStatus().getObservedGeneration() < newResource.getMetadata().getGeneration();
if (needsObservation) {
logResource(Level.FINE, "%s %s/%s is processed after a metadata.generation increment.", newResource);
} else {
logResource(Level.FINE, "%s %s/%s was ignored because its metadata.generation is still the same as the status.observedGeneration.", newResource);
}
return needsObservation;
}
}
use of org.entando.kubernetes.controller.spi.client.SerializedEntandoResource in project entando-k8s-controller-coordinator by entando-k8s.
the class DefaultSimpleKubernetesClientTest method shouldUpdateStatusOfOpaqueCustomResource.
@Test
@Description("Should track phase updates on the status of opaque custom resources and in Kubernetes events")
void shouldUpdateStatusOfOpaqueCustomResource() throws IOException {
ValueHolder<TestResource> testResource = new ValueHolder<>();
step("Given I have created an instance of the CustomResourceDefinition TestResource", () -> {
testResource.set(createTestResource(new TestResource().withNames(MY_APP_NAMESPACE_1, MY_APP).withSpec(new BasicDeploymentSpecBuilder().withReplicas(1).build())));
attachResource("TestResource", testResource.get());
});
SerializedEntandoResource serializedEntandoResource = objectMapper.readValue(objectMapper.writeValueAsBytes(testResource.get()), SerializedEntandoResource.class);
step("And it is represented in an opaque format using the SerializedEntandoResource class", () -> {
serializedEntandoResource.setDefinition(CustomResourceDefinitionContext.fromCustomResourceType(TestResource.class));
attachResource("Opaque Resource", serializedEntandoResource);
});
step("When I update its phase to 'successful'", () -> getMyClient().updatePhase(serializedEntandoResource, EntandoDeploymentPhase.SUCCESSFUL));
step("Then the updated status reflects on the TestResource", () -> {
final TestResource actual = getFabric8Client().customResources(TestResource.class).inNamespace(MY_APP_NAMESPACE_1).withName(MY_APP).get();
assertThat(actual.getStatus().getPhase()).isEqualTo(EntandoDeploymentPhase.SUCCESSFUL);
attachResource("TestResource", actual);
});
step("And a PHASE_CHANGE event has been issued to Kubernetes", () -> {
final List<Event> events = getMyClient().listEventsFor(testResource.get());
attachResources("Events", events);
assertThat(events).allMatch(event -> event.getInvolvedObject().getName().equals(testResource.get().getMetadata().getName()));
assertThat(events).anyMatch(event -> event.getAction().equals("PHASE_CHANGE"));
});
}
use of org.entando.kubernetes.controller.spi.client.SerializedEntandoResource in project entando-k8s-controller-coordinator by entando-k8s.
the class LivenessTests method shouldCreateControllerPodPointingToResource.
@Test
@Description("Should create a controller pod that points to the newly created resource")
void shouldCreateControllerPodPointingToResource() {
System.setProperty(EntandoOperatorSpiConfigProperty.ENTANDO_CONTROLLER_POD_NAME.getJvmSystemProperty(), "my-pod");
step("Given the Coordinator observes its own namespace", () -> {
System.setProperty(EntandoOperatorConfigProperty.ENTANDO_NAMESPACES_TO_OBSERVE.getJvmSystemProperty(), clientDouble.getNamespace());
coordinator.onStartup(new StartupEvent());
});
ValueHolder<SerializedEntandoResource> testResource = new ValueHolder<>();
step("And I have created a new TestResource resource", () -> {
final TestResource r = new TestResource().withNames(clientDouble.getNamespace(), "test-keycloak").withSpec(new BasicDeploymentSpecBuilder().withDbms(DbmsVendor.EMBEDDED).build());
r.getMetadata().setGeneration(1L);
testResource.set(clientDouble.createOrPatchEntandoResource(CoordinatorTestUtils.toSerializedResource(r)));
attachment("TestResource", objectMapper.writeValueAsString(testResource.get()));
});
final File file = Paths.get("/tmp/EntandoControllerCoordinator.ready").toFile();
clientDouble.getCluster().getResourceProcessor().getAllWatchers().forEach(watcher -> {
Liveness.alive();
step(format("When the watcher %s is closed", watcher.getClass().getSimpleName()), () -> {
watcher.onClose(new WatcherException("Closed"));
});
step("Then the liveness probe will fail", () -> {
assertThat(file).doesNotExist();
});
});
}
use of org.entando.kubernetes.controller.spi.client.SerializedEntandoResource in project entando-k8s-controller-coordinator by entando-k8s.
the class ControllerCoordinatorProcessingCriteriaTest method testGenerationObservedIsCurrentButForceInstructed.
@Test
@Description("Resource modification events should be processed when using the annotation 'entando.org/processing-instruction=force' even " + "when the 'generation' property on the metadata of the resource is the same as the 'observedGeneration' property on" + " its status")
void testGenerationObservedIsCurrentButForceInstructed() {
step("Given the Coordinator observes this namespace", () -> System.setProperty(EntandoOperatorConfigProperty.ENTANDO_NAMESPACES_TO_OBSERVE.getJvmSystemProperty(), OBSERVED_NAMESPACE));
final ValueHolder<SerializedEntandoResource> testResource = new ValueHolder<>();
step("And I have created an TestResource resource with the 'force' processing instruction", () -> {
testResource.set(createTestResource(10L, Collections.singletonMap(AnnotationNames.PROCESSING_INSTRUCTION.getName(), "force")));
attachment("TestResource", objectMapper.writeValueAsString(testResource.get()));
});
step("And the generation in the metadata is the same as the observedGeneration in the status", () -> {
// NB This step assumes that the ClientDouble is holding this exact instance of the resource when the ControllerCoordinator
// starts
testResource.get().getStatus().updateDeploymentPhase(EntandoDeploymentPhase.STARTED, 10L);
attachment("TestResource", objectMapper.writeValueAsString(testResource.get()));
});
step("When I start the ControllerCoordinator", () -> coordinator.onStartup(new StartupEvent()));
step("Then a new pod gets created", () -> {
coordinator.getObserver(CustomResourceDefinitionContext.fromCustomResourceType(TestResource.class)).shutDownAndWait(1, SECONDS);
await().ignoreExceptions().atMost(3, TimeUnit.SECONDS).until(() -> {
final Map<String, String> labels = labelsFromResource(testResource.get());
return clientDouble.loadPod(CONTROLLER_NAMESPACE, labels) != null;
});
});
step("And the 'force' processing instruction has been removed to avoid recursive processing", () -> {
final SerializedEntandoResource latestKeycloakServer = clientDouble.load(TestResource.class, testResource.get().getMetadata().getNamespace(), testResource.get().getMetadata().getName());
assertThat(CoordinatorUtils.resolveProcessingInstruction(latestKeycloakServer)).isEqualTo(OperatorProcessingInstruction.NONE);
attachment("TestResource", objectMapper.writeValueAsString(testResource.get()));
});
step("And the forced event was logged", () -> {
final Optional<String> logEntry = LogInterceptor.getLogEntries().stream().filter(s -> s.contains("has been forced")).findFirst();
assertThat(logEntry).isPresent();
});
}
Aggregations