0

I have written CustomHealthIndicator to check the producer binding is running, if not then Pause the Consumer binding. We have implemented the application using Spring-cloud-streams-kafka-binder in functional style. Below is the snippet of code doing that:

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.cloud.stream.binder.Binding;
import org.springframework.cloud.stream.binding.BindingsLifecycleController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class BindingHealthIndicator implements HealthIndicator {

    private final ApplicationContext context;
    private final ApplicationEventPublisher eventPublisher;

    private String PRODUCER_ERROR_MESSAGE = "The Src topic is unavailable";

    private String CONSUMER_ERROR_MESSAGE = "The Dest topic is unavailable";
    private String SUCCESS_MESSAGE = "The Consumer and producer topics are Healthy";

    public BindingHealthIndicator(ApplicationContext context, ApplicationEventPublisher eventPublisher) {
        this.context = context;
        this.eventPublisher = eventPublisher;
    }

    @Override
    public Health health() {
        BindingsLifecycleController bindingsController = context.getBean(BindingsLifecycleController.class);
        Boolean producerBindingStatus;
        try {
            Binding producerBinding = bindingsController.queryState("consumeAndProcessAllAccountUpdate-out-0");
            producerBindingStatus = producerBinding.isRunning();
        } catch(Exception ex) {
            bindingsController.changeState("consumeAndProcessAllAccountUpdate-in-0", BindingsLifecycleController.State.PAUSED);
            AvailabilityChangeEvent.publish(this.eventPublisher, PRODUCER_ERROR_MESSAGE, ReadinessState.REFUSING_TRAFFIC);
            log.error(PRODUCER_ERROR_MESSAGE);
            return Health.down().withDetail("description", PRODUCER_ERROR_MESSAGE).build();
        }
        Binding consumerBinding = bindingsController.queryState("consumeAndProcessAllAccountUpdate-in-0");
        Boolean consumerBindingStatus = consumerBinding.isRunning();

        if(!(producerBindingStatus)) {
            log.error(PRODUCER_ERROR_MESSAGE);
            bindingsController.changeState("consumeAndProcessAllAccountUpdate-in-0", BindingsLifecycleController.State.PAUSED);
            AvailabilityChangeEvent.publish(this.eventPublisher, PRODUCER_ERROR_MESSAGE, ReadinessState.REFUSING_TRAFFIC);
            return Health.down().withDetail("description", PRODUCER_ERROR_MESSAGE).build();
        }
        if(!(consumerBindingStatus)) {
            log.error(CONSUMER_ERROR_MESSAGE);
            AvailabilityChangeEvent.publish(this.eventPublisher, CONSUMER_ERROR_MESSAGE, ReadinessState.REFUSING_TRAFFIC);
            return Health.down().withDetail("description", CONSUMER_ERROR_MESSAGE).build();
        }
        bindingsController.changeState("consumeAndProcessAllAccountUpdate-in-0", BindingsLifecycleController.State.RESUMED);
        AvailabilityChangeEvent.publish(this.eventPublisher, SUCCESS_MESSAGE, ReadinessState.ACCEPTING_TRAFFIC);
        return Health.up().withDetail("description", SUCCESS_MESSAGE).build();
    }
}

I want to simulate through Unit testing that the producer binder to be having issues or to have the isRunning return false, and check if consumer is PAUSED, could you please suggest the correct approach to be followed?

  • Here is a test we use internally in the Kafka binder for health indicator. See if you can follow similar strategies. https://github.com/spring-cloud/spring-cloud-stream/blob/main/binders/kafka-binder/spring-cloud-stream-binder-kafka/src/test/java/org/springframework/cloud/stream/binder/kafka/KafkaBinderHealthIndicatorTest.java. – sobychacko Feb 23 '23 at 19:43
  • In addition, here is another test in which it tests `BindingLifecycleController` directlry - https://github.com/spring-cloud/spring-cloud-stream/blob/main/core/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/function/ImplicitFunctionBindingTests.java – sobychacko Feb 23 '23 at 19:46
  • When i try this: https://github.com/spring-cloud/spring-cloud-stream/blob/main/core/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/function/ImplicitFunctionBindingTests.java#L124 The binding returned by below: Binding input = ctrl.queryState("consumeAndProcessAllAccountUpdate-out-0"); is Late Binding and not of type AbstractMessageChannelBinder so when i try to get the isRunning state the LateBinding object doesnt have that method and the assert fails. Why is the binding returning LateBinding object in Test class? – Lakshmi Pisharody Feb 23 '23 at 22:18
  • It is possible that the binding code threw some exceptions. In that case, the binding created is a `LateBinding.` – sobychacko Feb 24 '23 at 15:05
  • Could you create an issue in Spring Cloud Stream and point to this SO thread? It looks like we probably need to give some guidelines in the docs for betting testing the `BindingsLifecycleController.` – sobychacko Feb 24 '23 at 15:06

0 Answers0