Search code examples
spring-bootspring-cloudnetflix-eurekaspring-cloud-feignopenfeign

Feign Client couldn't find custom registered eureka service


I will tell little bit about what I am trying to achieve. I have a spring boot application which is an Eureka client and registers itself as a data-service service. After this application startup (ApplicationReadeEvent.class) I am registering also another custom created Eureka client and seems the registration is successful. I am able to see that newly registered service (workflow-service) when accessing to http://localhost:8761. The reason I decided to do it inside data-service application is because I needn't it out of this context and I need it only in DEV environment. Later instead of it would be plugged the real workflow-service developed by other team. The problem here is that when I trying to access to this service through a feign client I am receiving an exception:

com.netflix.client.ClientException: Load balancer does not have available server for client: workflow-service

enter image description here

Here is my custom service registration code:

package XXX;

import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.appinfo.HealthCheckHandler;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.DiscoveryClient;
import org.mockserver.integration.ClientAndServer;
import org.mockserver.model.HttpRequest;
import org.mockserver.model.HttpResponse;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.cloud.commons.util.InetUtilsProperties;
import org.springframework.cloud.netflix.eureka.*;
import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaRegistration;
import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.EventListener;

import java.net.SocketException;

@Configuration
@ConditionalOnExpression("${workflow.engine.mock.enabled:false}")
public class MockWorkflowEngineConfiguration {

    @Value("${workflow.engine.mock.application.name}") private String workflowEngineApplicationName;
    @Value("${workflow.engine.mock.application.port}") private Integer workflowEnginePort;

    @Autowired private EurekaInstanceConfigBean originalInstanceConfig;
    @Autowired private EurekaClientConfigBean originalClientConfig;
    @Autowired private ApplicationInfoManager applicationInfoManager;
    @Autowired private ApplicationContext applicationContext;
    @Autowired private ApplicationEventPublisher applicationEventPublisher;
    @Autowired private ObjectProvider<HealthCheckHandler> healthCheckHandler;
    @Autowired private EurekaServiceRegistry eurekaServiceRegistry;

    private EurekaRegistration workflowEngineEurekaRegistration;
    private DiscoveryClient workflowEngineDiscoveryClient;
    private ClientAndServer workflowEngineMockClient;

    @EventListener(ApplicationReadyEvent.class)
    public void initializeMockWorkflowEngine() throws SocketException{
        workflowEngineDiscoveryClient = new CloudEurekaClient(
                        createWorkflowEngineAppInfoManager(),
                        duplicateEurekaClientConfig(),
                        applicationEventPublisher);

        workflowEngineEurekaRegistration = EurekaRegistration.builder((CloudEurekaInstanceConfig) workflowEngineDiscoveryClient.getApplicationInfoManager().getEurekaInstanceConfig())
                .with(workflowEngineDiscoveryClient)
                .with(workflowEngineDiscoveryClient.getApplicationInfoManager())
                .with(healthCheckHandler).build();

        eurekaServiceRegistry.register(workflowEngineEurekaRegistration);

        workflowEngineMockClient = new ClientAndServer(workflowEnginePort);

        workflowEngineMockClient.when(
                HttpRequest.request()
                    .withMethod("GET")
                    .withPath("/job")
        )
        .respond(
                HttpResponse.response()
                .withStatusCode(200)
                .withBody("{ id: '1', name: 'default'}")
        );
    }

    @EventListener(ContextClosedEvent.class)
    public void shutdownMockWorkflowEngine(){
        workflowEngineDiscoveryClient.shutdown();
        eurekaServiceRegistry.deregister(workflowEngineEurekaRegistration);
        workflowEngineMockClient.stop(true);
    }

    private ApplicationInfoManager createWorkflowEngineAppInfoManager() throws SocketException {
        EurekaInstanceConfigBean newInstanceConfig =
                new EurekaInstanceConfigBean(new InetUtils(new InetUtilsProperties()));

        newInstanceConfig.setEnvironment(applicationContext.getEnvironment());
        newInstanceConfig.setAppname(workflowEngineApplicationName);
        newInstanceConfig.setInstanceId(applicationInfoManager.getInfo().getHostName() + ":" + workflowEngineApplicationName + ":" + workflowEnginePort);
        newInstanceConfig.setInitialStatus(InstanceInfo.InstanceStatus.UP);
        newInstanceConfig.setNonSecurePortEnabled(originalInstanceConfig.isNonSecurePortEnabled());
        newInstanceConfig.setNonSecurePort(workflowEnginePort);
        newInstanceConfig.setHostname(applicationInfoManager.getInfo().getHostName());
        newInstanceConfig.setSecurePortEnabled(originalInstanceConfig.isSecurePortEnabled());
        newInstanceConfig.setSecurePort(originalInstanceConfig.getSecurePort());
        newInstanceConfig.setDataCenterInfo(originalInstanceConfig.getDataCenterInfo());
        newInstanceConfig.setHealthCheckUrl(originalInstanceConfig.getHealthCheckUrl());
        newInstanceConfig.setSecureHealthCheckUrl(originalInstanceConfig.getSecureHealthCheckUrl());
        newInstanceConfig.setHomePageUrl(originalInstanceConfig.getHomePageUrl());
        newInstanceConfig.setStatusPageUrl(originalInstanceConfig.getStatusPageUrl());
        newInstanceConfig.setStatusPageUrlPath(originalInstanceConfig.getStatusPageUrlPath());
        newInstanceConfig.setIpAddress(originalInstanceConfig.getIpAddress());
        newInstanceConfig.setPreferIpAddress(originalInstanceConfig.isPreferIpAddress());

        ApplicationInfoManager manager =
                new ApplicationInfoManager(newInstanceConfig, (ApplicationInfoManager.OptionalArgs) null);

        return manager;
    }

    private EurekaClientConfigBean duplicateEurekaClientConfig() {
        EurekaClientConfigBean newConfig = new EurekaClientConfigBean();
        newConfig.setFetchRegistry(false);
        newConfig.setEurekaServerPort(originalClientConfig.getEurekaServerPort());
        newConfig.setAllowRedirects(originalClientConfig.isAllowRedirects());
        newConfig.setAvailabilityZones(originalClientConfig.getAvailabilityZones());
        newConfig.setBackupRegistryImpl(originalClientConfig.getBackupRegistryImpl());
        newConfig.setServiceUrl(originalClientConfig.getServiceUrl());
        return newConfig;
    }
}

And here is my feign client code:

@FeignClient(name = "workflow-service", configuration = FeignClientConfiguration.class)
public interface WorkflowService {
    @RequestMapping(value = "/job", method = RequestMethod.GET, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    ResponseEntity<List<WorkflowJobDTO>> listJobs();

Here is the feign client usage through which I am trying to access to another service:

@GetMapping(path = "/workflow-jobs", produces = "application/json")
    public ResponseEntity<List<WorkflowJobDTO>> getAllJobs() {
        return workflowService.listJobs();
    }
}

Solution

  • This has been fixed by just setting virtual host name.

    newInstanceConfig.setVirtualHostname(workflowEngineApplicationName);