提交 bf1323b0 编写于 作者: J Jason Song

init kubernetes native service discovery support

上级 df6cb396
......@@ -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
......
eureka.client.enabled=false
spring.cloud.discovery.enabled=false
\ No newline at end of file
......@@ -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
\ No newline at end of file
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 {
}
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);
}
}
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;
};
}
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);
}
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;
}
}
apollo.eureka.server.enabled=false
eureka.client.enabled=false
spring.cloud.discovery.enabled=false
\ No newline at end of file
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());
}
}
\ No newline at end of file
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;
}
}
\ No newline at end of file
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());
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册