Răsfoiți Sursa

init kubernetes native service discovery support

Jason Song 4 ani în urmă
părinte
comite
bf1323b036

+ 0 - 2
apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/AdminServiceApplication.java

@@ -5,14 +5,12 @@ import com.ctrip.framework.apollo.common.ApolloCommonConfig;
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.EnableAspectJAutoProxy;
 import org.springframework.context.annotation.PropertySource;
 import org.springframework.transaction.annotation.EnableTransactionManagement;
 @EnableAspectJAutoProxy
-@EnableEurekaClient
 @Configuration
 @PropertySource(value = {"classpath:adminservice.properties"})
 @EnableAutoConfiguration

+ 2 - 0
apollo-adminservice/src/main/resources/application-kubernetes.properties

@@ -0,0 +1,2 @@
+eureka.client.enabled=false
+spring.cloud.discovery.enabled=false

+ 0 - 4
apollo-configservice/src/main/config/application-github.properties

@@ -2,7 +2,3 @@
 spring.datasource.url = ${spring_datasource_url}
 spring.datasource.username = ${spring_datasource_username}
 spring.datasource.password = ${spring_datasource_password}
-
-
-#apollo.eureka.server.enabled=true
-#apollo.eureka.client.enabled=true

+ 0 - 16
apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/ConfigServerEurekaClientConfigure.java

@@ -1,16 +0,0 @@
-package com.ctrip.framework.apollo.configservice;
-
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * Start Eureka Client annotations according to configuration
- *
- * @author Zhiqiang Lin(linzhiqiang0514@163.com)
- */
-@Configuration
-@EnableEurekaClient
-@ConditionalOnProperty(name = "apollo.eureka.client.enabled", havingValue = "true", matchIfMissing = true)
-public class ConfigServerEurekaClientConfigure {
-}

+ 10 - 23
apollo-configservice/src/main/java/com/ctrip/framework/apollo/metaservice/controller/ServiceController.java

@@ -1,55 +1,42 @@
 package com.ctrip.framework.apollo.metaservice.controller;
 
+import com.ctrip.framework.apollo.core.ServiceNameConsts;
 import com.ctrip.framework.apollo.core.dto.ServiceDTO;
 import com.ctrip.framework.apollo.metaservice.service.DiscoveryService;
-import com.netflix.appinfo.InstanceInfo;
+import java.util.Collections;
+import java.util.List;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
-import java.util.List;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-
 @RestController
 @RequestMapping("/services")
 public class ServiceController {
 
   private final DiscoveryService discoveryService;
 
-  private static Function<InstanceInfo, ServiceDTO> instanceInfoToServiceDTOFunc = instance -> {
-    ServiceDTO service = new ServiceDTO();
-    service.setAppName(instance.getAppName());
-    service.setInstanceId(instance.getInstanceId());
-    service.setHomepageUrl(instance.getHomePageUrl());
-    return service;
-  };
-
   public ServiceController(final DiscoveryService discoveryService) {
     this.discoveryService = discoveryService;
   }
 
-
+  /**
+   * This method always return an empty list as meta service is not used at all
+   */
+  @Deprecated
   @RequestMapping("/meta")
   public List<ServiceDTO> getMetaService() {
-    List<InstanceInfo> instances = discoveryService.getMetaServiceInstances();
-    List<ServiceDTO> result = instances.stream().map(instanceInfoToServiceDTOFunc).collect(Collectors.toList());
-    return result;
+    return Collections.emptyList();
   }
 
   @RequestMapping("/config")
   public List<ServiceDTO> getConfigService(
       @RequestParam(value = "appId", defaultValue = "") String appId,
       @RequestParam(value = "ip", required = false) String clientIp) {
-    List<InstanceInfo> instances = discoveryService.getConfigServiceInstances();
-    List<ServiceDTO> result = instances.stream().map(instanceInfoToServiceDTOFunc).collect(Collectors.toList());
-    return result;
+    return discoveryService.getServiceInstances(ServiceNameConsts.APOLLO_CONFIGSERVICE);
   }
 
   @RequestMapping("/admin")
   public List<ServiceDTO> getAdminService() {
-    List<InstanceInfo> instances = discoveryService.getAdminServiceInstances();
-    List<ServiceDTO> result = instances.stream().map(instanceInfoToServiceDTOFunc).collect(Collectors.toList());
-    return result;
+    return discoveryService.getServiceInstances(ServiceNameConsts.APOLLO_ADMINSERVICE);
   }
 }

+ 48 - 0
apollo-configservice/src/main/java/com/ctrip/framework/apollo/metaservice/service/DefaultDiscoveryService.java

@@ -0,0 +1,48 @@
+package com.ctrip.framework.apollo.metaservice.service;
+
+import com.ctrip.framework.apollo.common.condition.ConditionalOnMissingProfile;
+import com.ctrip.framework.apollo.core.dto.ServiceDTO;
+import com.ctrip.framework.apollo.tracer.Tracer;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.discovery.DiscoveryClient;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+@Service
+@ConditionalOnMissingProfile({"kubernetes"})
+public class DefaultDiscoveryService implements DiscoveryService {
+
+  private final DiscoveryClient discoveryClient;
+
+  public DefaultDiscoveryService(final DiscoveryClient discoveryClient) {
+    this.discoveryClient = discoveryClient;
+  }
+
+  public List<ServiceDTO> getServiceInstances(String serviceId) {
+    List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
+    if (CollectionUtils.isEmpty(instances)) {
+      Tracer.logEvent("Apollo.Discovery.NotFound", serviceId);
+      return Collections.emptyList();
+    }
+    return instances.stream().map(instanceInfoToServiceDTOFunc)
+        .collect(Collectors.toList());
+  }
+
+  private static Function<ServiceInstance, ServiceDTO> instanceInfoToServiceDTOFunc = instance -> {
+    ServiceDTO service = new ServiceDTO();
+    service.setAppName(instance.getServiceId());
+    service.setInstanceId(
+        String.format("%s:%s:%s", instance.getHost(), instance.getServiceId(), instance.getPort()));
+    String uri = instance.getUri().toString();
+    if (!uri.endsWith("/")) {
+      uri += "/";
+    }
+    service.setHomepageUrl(uri);
+    return service;
+  };
+
+}

+ 8 - 39
apollo-configservice/src/main/java/com/ctrip/framework/apollo/metaservice/service/DiscoveryService.java

@@ -1,45 +1,14 @@
 package com.ctrip.framework.apollo.metaservice.service;
 
-import com.ctrip.framework.apollo.core.ServiceNameConsts;
-import com.ctrip.framework.apollo.tracer.Tracer;
-import com.netflix.appinfo.InstanceInfo;
-import com.netflix.discovery.EurekaClient;
-import com.netflix.discovery.shared.Application;
-import org.springframework.stereotype.Service;
-
-import java.util.Collections;
+import com.ctrip.framework.apollo.core.dto.ServiceDTO;
 import java.util.List;
 
-@Service
-public class DiscoveryService {
-
-  private final EurekaClient eurekaClient;
-
-  public DiscoveryService(final EurekaClient eurekaClient) {
-    this.eurekaClient = eurekaClient;
-  }
-
-  public List<InstanceInfo> getConfigServiceInstances() {
-    Application application = eurekaClient.getApplication(ServiceNameConsts.APOLLO_CONFIGSERVICE);
-    if (application == null) {
-      Tracer.logEvent("Apollo.EurekaDiscovery.NotFound", ServiceNameConsts.APOLLO_CONFIGSERVICE);
-    }
-    return application != null ? application.getInstances() : Collections.emptyList();
-  }
-
-  public List<InstanceInfo> getMetaServiceInstances() {
-    Application application = eurekaClient.getApplication(ServiceNameConsts.APOLLO_METASERVICE);
-    if (application == null) {
-      Tracer.logEvent("Apollo.EurekaDiscovery.NotFound", ServiceNameConsts.APOLLO_METASERVICE);
-    }
-    return application != null ? application.getInstances() : Collections.emptyList();
-  }
+public interface DiscoveryService {
 
-  public List<InstanceInfo> getAdminServiceInstances() {
-    Application application = eurekaClient.getApplication(ServiceNameConsts.APOLLO_ADMINSERVICE);
-    if (application == null) {
-      Tracer.logEvent("Apollo.EurekaDiscovery.NotFound", ServiceNameConsts.APOLLO_ADMINSERVICE);
-    }
-    return application != null ? application.getInstances() : Collections.emptyList();
-  }
+  /**
+   * @param serviceId the service id
+   * @return the service instance list for the specified service id, or an empty list if no service
+   * instance available
+   */
+  List<ServiceDTO> getServiceInstances(String serviceId);
 }

+ 63 - 0
apollo-configservice/src/main/java/com/ctrip/framework/apollo/metaservice/service/KubernetesDiscoveryService.java

@@ -0,0 +1,63 @@
+package com.ctrip.framework.apollo.metaservice.service;
+
+import com.ctrip.framework.apollo.biz.config.BizConfig;
+import com.ctrip.framework.apollo.core.ServiceNameConsts;
+import com.ctrip.framework.apollo.core.dto.ServiceDTO;
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Service;
+
+/**
+ * This is a simple implementation that skips any service discovery and just return what is configured
+ *
+ * <ul>
+ *   <li>getServiceInstances("apollo-configservice") returns ${apollo.config-service.url}</li>
+ *   <li>getServiceInstances("apollo-adminservice") returns ${apollo.admin-service.url}</li>
+ * </ul>
+ */
+@Service
+@Profile({"kubernetes"})
+public class KubernetesDiscoveryService implements DiscoveryService {
+  private static final Splitter COMMA_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults();
+  private static final Map<String, String> SERVICE_ID_TO_CONFIG_NAME = ImmutableMap
+      .of(ServiceNameConsts.APOLLO_CONFIGSERVICE, "apollo.config-service.url",
+          ServiceNameConsts.APOLLO_ADMINSERVICE, "apollo.admin-service.url");
+
+  private final BizConfig bizConfig;
+
+  public KubernetesDiscoveryService(final BizConfig bizConfig) {
+    this.bizConfig = bizConfig;
+  }
+
+  @Override
+  public List<ServiceDTO> getServiceInstances(String serviceId) {
+    String configName = SERVICE_ID_TO_CONFIG_NAME.get(serviceId);
+    if (configName == null) {
+      return Collections.emptyList();
+    }
+
+    return assembleServiceDTO(serviceId, bizConfig.getValue(configName));
+  }
+
+  private List<ServiceDTO> assembleServiceDTO(String serviceId, String directUrl) {
+    if (Strings.isNullOrEmpty(directUrl)) {
+      return Collections.emptyList();
+    }
+    List<ServiceDTO> serviceDTOList = Lists.newLinkedList();
+    COMMA_SPLITTER.split(directUrl).forEach(url -> {
+      ServiceDTO service = new ServiceDTO();
+      service.setAppName(serviceId);
+      service.setInstanceId(String.format("%s:%s", serviceId, url));
+      service.setHomepageUrl(url);
+      serviceDTOList.add(service);
+    });
+
+    return serviceDTOList;
+  }
+}

+ 3 - 0
apollo-configservice/src/main/resources/application-kubernetes.properties

@@ -0,0 +1,3 @@
+apollo.eureka.server.enabled=false
+eureka.client.enabled=false
+spring.cloud.discovery.enabled=false

+ 56 - 0
apollo-configservice/src/test/java/com/ctrip/framework/apollo/metaservice/controller/ServiceControllerTest.java

@@ -0,0 +1,56 @@
+package com.ctrip.framework.apollo.metaservice.controller;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.when;
+
+import com.ctrip.framework.apollo.core.ServiceNameConsts;
+import com.ctrip.framework.apollo.core.dto.ServiceDTO;
+import com.ctrip.framework.apollo.metaservice.service.DiscoveryService;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ServiceControllerTest {
+
+  @Mock
+  private DiscoveryService discoveryService;
+
+  @Mock
+  private List<ServiceDTO> someServices;
+
+  private ServiceController serviceController;
+
+  @Before
+  public void setUp() throws Exception {
+    serviceController = new ServiceController(discoveryService);
+  }
+
+  @Test
+  public void testGetMetaService() {
+    assertTrue(serviceController.getMetaService().isEmpty());
+  }
+
+  @Test
+  public void testGetConfigService() {
+    String someAppId = "someAppId";
+    String someClientIp = "someClientIp";
+
+    when(discoveryService.getServiceInstances(ServiceNameConsts.APOLLO_CONFIGSERVICE))
+        .thenReturn(someServices);
+
+    assertEquals(someServices, serviceController.getConfigService(someAppId, someClientIp));
+  }
+
+  @Test
+  public void testGetAdminService() {
+    when(discoveryService.getServiceInstances(ServiceNameConsts.APOLLO_ADMINSERVICE))
+        .thenReturn(someServices);
+
+    assertEquals(someServices, serviceController.getAdminService());
+
+  }
+}

+ 98 - 0
apollo-configservice/src/test/java/com/ctrip/framework/apollo/metaservice/service/DefaultDiscoveryServiceTest.java

@@ -0,0 +1,98 @@
+package com.ctrip.framework.apollo.metaservice.service;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.ctrip.framework.apollo.core.dto.ServiceDTO;
+import com.google.common.collect.Lists;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.discovery.DiscoveryClient;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DefaultDiscoveryServiceTest {
+
+  @Mock
+  private DiscoveryClient discoveryClient;
+
+  private DefaultDiscoveryService defaultDiscoveryService;
+
+  private String someServiceId;
+
+  @Before
+  public void setUp() throws Exception {
+    defaultDiscoveryService = new DefaultDiscoveryService(discoveryClient);
+
+    someServiceId = "someServiceId";
+  }
+
+  @Test
+  public void testGetServiceInstancesWithNullInstances() {
+    when(discoveryClient.getInstances(someServiceId)).thenReturn(null);
+
+    assertTrue(defaultDiscoveryService.getServiceInstances(someServiceId).isEmpty());
+  }
+
+  @Test
+  public void testGetServiceInstancesWithEmptyInstances() {
+    when(discoveryClient.getInstances(someServiceId)).thenReturn(new ArrayList<>());
+
+    assertTrue(defaultDiscoveryService.getServiceInstances(someServiceId).isEmpty());
+  }
+
+  @Test
+  public void testGetServiceInstances() throws URISyntaxException {
+    String someHost = "1.2.3.4";
+    int somePort = 8080;
+    String someUri = String.format("http://%s:%s/some-path/", someHost, somePort);
+    ServiceInstance someServiceInstance = mockServiceInstance(someServiceId, someHost, somePort,
+        someUri);
+
+    String anotherHost = "2.3.4.5";
+    int anotherPort = 9090;
+    String anotherUri = String.format("http://%s:%s/some-path-with-no-slash", anotherHost, anotherPort);
+    ServiceInstance anotherServiceInstance = mockServiceInstance(someServiceId, anotherHost, anotherPort,
+        anotherUri);
+
+    when(discoveryClient.getInstances(someServiceId))
+        .thenReturn(Lists.newArrayList(someServiceInstance, anotherServiceInstance));
+
+    List<ServiceDTO> serviceDTOList = defaultDiscoveryService.getServiceInstances(someServiceId);
+
+    assertEquals(2, serviceDTOList.size());
+    check(someServiceInstance, serviceDTOList.get(0), false);
+    check(anotherServiceInstance, serviceDTOList.get(1), true);
+  }
+
+  private void check(ServiceInstance serviceInstance, ServiceDTO serviceDTO, boolean appendSlashToUri) {
+    assertEquals(serviceInstance.getServiceId(), serviceDTO.getAppName());
+    assertEquals(serviceDTO.getInstanceId(), String
+        .format("%s:%s:%s", serviceInstance.getHost(), serviceInstance.getServiceId(),
+            serviceInstance.getPort()));
+    if (appendSlashToUri) {
+      assertEquals(serviceInstance.getUri().toString() + "/", serviceDTO.getHomepageUrl());
+    } else {
+      assertEquals(serviceInstance.getUri().toString(), serviceDTO.getHomepageUrl());
+    }
+  }
+
+  private ServiceInstance mockServiceInstance(String serviceId, String host, int port, String uri)
+      throws URISyntaxException {
+    ServiceInstance serviceInstance = mock(ServiceInstance.class);
+    when(serviceInstance.getServiceId()).thenReturn(serviceId);
+    when(serviceInstance.getHost()).thenReturn(host);
+    when(serviceInstance.getPort()).thenReturn(port);
+    when(serviceInstance.getUri()).thenReturn(new URI(uri));
+
+    return serviceInstance;
+  }
+}

+ 97 - 0
apollo-configservice/src/test/java/com/ctrip/framework/apollo/metaservice/service/KubernetesDiscoveryServiceTest.java

@@ -0,0 +1,97 @@
+package com.ctrip.framework.apollo.metaservice.service;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.ctrip.framework.apollo.biz.config.BizConfig;
+import com.ctrip.framework.apollo.core.ServiceNameConsts;
+import com.ctrip.framework.apollo.core.dto.ServiceDTO;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class KubernetesDiscoveryServiceTest {
+
+  private String configServiceConfigName = "apollo.config-service.url";
+  private String adminServiceConfigName = "apollo.admin-service.url";
+
+  @Mock
+  private BizConfig bizConfig;
+
+  private KubernetesDiscoveryService kubernetesDiscoveryService;
+
+  @Before
+  public void setUp() throws Exception {
+    kubernetesDiscoveryService = new KubernetesDiscoveryService(bizConfig);
+  }
+
+  @Test
+  public void testGetServiceInstancesWithInvalidServiceId() {
+    String someInvalidServiceId = "someInvalidServiceId";
+
+    assertTrue(kubernetesDiscoveryService.getServiceInstances(someInvalidServiceId).isEmpty());
+  }
+
+  @Test
+  public void testGetServiceInstancesWithNullConfig() {
+    when(bizConfig.getValue(configServiceConfigName)).thenReturn(null);
+
+    assertTrue(
+        kubernetesDiscoveryService.getServiceInstances(ServiceNameConsts.APOLLO_CONFIGSERVICE)
+            .isEmpty());
+
+    verify(bizConfig, times(1)).getValue(configServiceConfigName);
+  }
+
+  @Test
+  public void testGetConfigServiceInstances() {
+    String someUrl = "http://some-host/some-path";
+    when(bizConfig.getValue(configServiceConfigName)).thenReturn(someUrl);
+
+    List<ServiceDTO> serviceDTOList = kubernetesDiscoveryService
+        .getServiceInstances(ServiceNameConsts.APOLLO_CONFIGSERVICE);
+
+    assertEquals(1, serviceDTOList.size());
+    ServiceDTO serviceDTO = serviceDTOList.get(0);
+
+    assertEquals(ServiceNameConsts.APOLLO_CONFIGSERVICE, serviceDTO.getAppName());
+    assertEquals(String.format("%s:%s", ServiceNameConsts.APOLLO_CONFIGSERVICE, someUrl),
+        serviceDTO.getInstanceId());
+    assertEquals(someUrl, serviceDTO.getHomepageUrl());
+  }
+
+  @Test
+  public void testGetAdminServiceInstances() {
+    String someUrl = "http://some-host/some-path";
+    String anotherUrl = "http://another-host/another-path";
+    when(bizConfig.getValue(adminServiceConfigName))
+        .thenReturn(String.format("%s,%s", someUrl, anotherUrl));
+
+    List<ServiceDTO> serviceDTOList = kubernetesDiscoveryService
+        .getServiceInstances(ServiceNameConsts.APOLLO_ADMINSERVICE);
+
+    assertEquals(2, serviceDTOList.size());
+    ServiceDTO serviceDTO = serviceDTOList.get(0);
+
+    assertEquals(ServiceNameConsts.APOLLO_ADMINSERVICE, serviceDTO.getAppName());
+    assertEquals(String.format("%s:%s", ServiceNameConsts.APOLLO_ADMINSERVICE, someUrl),
+        serviceDTO.getInstanceId());
+    assertEquals(someUrl, serviceDTO.getHomepageUrl());
+
+    ServiceDTO anotherServiceDTO = serviceDTOList.get(1);
+
+    assertEquals(ServiceNameConsts.APOLLO_ADMINSERVICE, anotherServiceDTO.getAppName());
+    assertEquals(String.format("%s:%s", ServiceNameConsts.APOLLO_ADMINSERVICE, anotherUrl),
+        anotherServiceDTO.getInstanceId());
+    assertEquals(anotherUrl, anotherServiceDTO.getHomepageUrl());
+
+  }
+
+}