diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/io/servicecomb/demo/springmvc/client/MicroserviceArray.java b/demo/demo-springmvc/springmvc-client/src/main/java/io/servicecomb/demo/springmvc/client/MicroserviceArray.java new file mode 100644 index 00000000000..b0c012ffd5e --- /dev/null +++ b/demo/demo-springmvc/springmvc-client/src/main/java/io/servicecomb/demo/springmvc/client/MicroserviceArray.java @@ -0,0 +1,32 @@ +/* + * Copyright 2017 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.servicecomb.demo.springmvc.client; + +import io.servicecomb.serviceregistry.api.registry.Microservice; +import java.util.List; + +public class MicroserviceArray { + + private List services; + + public List getServices() { + return services; + } + + public void setServices(List services) { + this.services = services; + } +} diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/io/servicecomb/demo/springmvc/client/ServiceCenterExample.java b/demo/demo-springmvc/springmvc-client/src/main/java/io/servicecomb/demo/springmvc/client/ServiceCenterExample.java new file mode 100644 index 00000000000..ef988979963 --- /dev/null +++ b/demo/demo-springmvc/springmvc-client/src/main/java/io/servicecomb/demo/springmvc/client/ServiceCenterExample.java @@ -0,0 +1,53 @@ +/* + * Copyright 2017 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.servicecomb.demo.springmvc.client; + +import java.net.URI; +import org.springframework.http.HttpMethod; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +public class ServiceCenterExample { + + public static void main(String[] args) throws Exception { + RestTemplate template = new RestTemplate(); + template.getMessageConverters().add(new MappingJackson2HttpMessageConverter()); + MultiValueMap headers = new LinkedMultiValueMap<>(); + headers.add("X-Tenant-Name", "default"); + + RequestEntity requestEntity = new RequestEntity(headers, HttpMethod.GET, + new URI("http://127.0.0.1:9980/registry/v3/microservices")); + ResponseEntity stringResponseEntity = template.exchange(requestEntity, String.class); + System.out.println(stringResponseEntity.getBody()); + ResponseEntity microseriveResponseEntity = template + .exchange(requestEntity, MicroserviceArray.class); + MicroserviceArray microserives = microseriveResponseEntity.getBody(); + System.out.println(microserives.getServices().get(1).getServiceId()); + + // instance + headers.add("X-ConsumerId", microserives.getServices().get(1).getServiceId()); + requestEntity = new RequestEntity(headers, HttpMethod.GET, + new URI("http://127.0.0.1:9980/registry/v3/microservices/" + microserives.getServices().get(1).getServiceId() + + "/instances")); + ResponseEntity microserviceInstanceResponseEntity = template.exchange(requestEntity, String.class); + System.out.println(microserviceInstanceResponseEntity.getBody()); + } +} diff --git a/demo/demo-springmvc/springmvc-client/src/main/resources/microservice.yaml b/demo/demo-springmvc/springmvc-client/src/main/resources/microservice.yaml index 011534b8e61..636dfb6a474 100644 --- a/demo/demo-springmvc/springmvc-client/src/main/resources/microservice.yaml +++ b/demo/demo-springmvc/springmvc-client/src/main/resources/microservice.yaml @@ -17,7 +17,15 @@ cse: retryEnabled: true retryOnSame: 1 retryOnNext: 1 - + serverListFilters: zoneaware + serverListFilter: + zoneaware: + className: io.servicecomb.loadbalance.filter.ZoneAwareServerListFilterExt + datacenter: + name: myDC + region: my-Region + availableZone: my-Zone + #########SSL options ssl.protocols: TLSv1.2 ssl.authPeer: true diff --git a/demo/demo-springmvc/springmvc-server/src/main/resources/microservice.yaml b/demo/demo-springmvc/springmvc-server/src/main/resources/microservice.yaml index 4464454dbf4..733c3c505f3 100644 --- a/demo/demo-springmvc/springmvc-server/src/main/resources/microservice.yaml +++ b/demo/demo-springmvc/springmvc-server/src/main/resources/microservice.yaml @@ -17,7 +17,10 @@ cse: tracing: enabled: true samplingRate: 0.5 - + datacenter: + name: myDC + region: my-Region + availableZone: my-Zone #########SSL options ssl.protocols: TLSv1.2 ssl.authPeer: true diff --git a/foundations/foundation-common/src/test/java/io/servicecomb/foundation/common/base/RetryableRunnableTest.java b/foundations/foundation-common/src/test/java/io/servicecomb/foundation/common/base/RetryableRunnableTest.java index 60426228f44..ada6f7d3e5b 100644 --- a/foundations/foundation-common/src/test/java/io/servicecomb/foundation/common/base/RetryableRunnableTest.java +++ b/foundations/foundation-common/src/test/java/io/servicecomb/foundation/common/base/RetryableRunnableTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -62,20 +63,35 @@ public void retriesUnderlyingRunnableUntilSuccess() { @Test public void exitsWhenInterrupted() throws InterruptedException { ExecutorService executorService = Executors.newSingleThreadExecutor(); - doThrow(exception).when(runnable).run(); + CountDownLatch countDownLatch = new CountDownLatch(1); + DescriptiveRunnable runnable = new DescriptiveRunnable() { + @Override + public String description() { + return "runnable"; + } + + @Override + public void run() { + if(countDownLatch.getCount() == 1) { + countDownLatch.countDown(); + } + throw new RuntimeException("oops"); + } + }; + + RetryableRunnable retryableRunnable = new RetryableRunnable(runnable, 50); Future retryable = executorService.submit(retryableRunnable); executorService.submit(blockedRunnable); - TimeUnit.MILLISECONDS.sleep(100); + countDownLatch.await(); retryable.cancel(true); - TimeUnit.MILLISECONDS.sleep(100); - + // this test have some pitfalls that can't shutdown the execution of retryableRunnable assertThat(retryable.isCancelled(), is(true)); verify(blockedRunnable).run(); - executorService.shutdown(); + executorService.shutdownNow(); } } \ No newline at end of file diff --git a/handlers/handler-loadbalance/src/main/java/io/servicecomb/loadbalance/filter/ZoneAwareServerListFilterExt.java b/handlers/handler-loadbalance/src/main/java/io/servicecomb/loadbalance/filter/ZoneAwareServerListFilterExt.java new file mode 100644 index 00000000000..03f3287893a --- /dev/null +++ b/handlers/handler-loadbalance/src/main/java/io/servicecomb/loadbalance/filter/ZoneAwareServerListFilterExt.java @@ -0,0 +1,72 @@ +/* + * Copyright 2017 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.servicecomb.loadbalance.filter; + +import com.netflix.loadbalancer.Server; +import io.servicecomb.loadbalance.CseServer; +import io.servicecomb.loadbalance.ServerListFilterExt; +import io.servicecomb.serviceregistry.RegistryUtils; +import io.servicecomb.serviceregistry.api.registry.MicroserviceInstance; +import java.util.ArrayList; +import java.util.List; + +public class ZoneAwareServerListFilterExt implements ServerListFilterExt { + + @Override + public List getFilteredListOfServers(List list) { + List result = new ArrayList<>(); + MicroserviceInstance myself = RegistryUtils.getMicroserviceInstance(); + boolean find = false; + for (Server server : list) { + CseServer cseServer = (CseServer) server; + if (regionAndAZMatch(myself, cseServer.getInstance())) { + result.add(cseServer); + find = true; + } + } + + if (!find) { + for (Server server : list) { + CseServer cseServer = (CseServer) server; + if (regionMatch(myself, cseServer.getInstance())) { + result.add(cseServer); + find = true; + } + } + } + + if (!find) { + result = list; + } + return result; + } + + private boolean regionAndAZMatch(MicroserviceInstance myself, MicroserviceInstance target) { + if (myself.getDataCenterInfo() != null && target.getDataCenterInfo() != null) { + return myself.getDataCenterInfo().getRegion().equals(target.getDataCenterInfo().getRegion()) && + myself.getDataCenterInfo().getAvailableZone().equals(target.getDataCenterInfo().getAvailableZone()); + } + return false; + } + + private boolean regionMatch(MicroserviceInstance myself, MicroserviceInstance target) { + if (myself.getDataCenterInfo() != null && target.getDataCenterInfo() != null) { + return myself.getDataCenterInfo().getRegion().equals(target.getDataCenterInfo().getRegion()); + } + return false; + } +} diff --git a/handlers/handler-loadbalance/src/test/java/io/servicecomb/loadbalance/filter/TestZoneAwareServerListFilterExt.java b/handlers/handler-loadbalance/src/test/java/io/servicecomb/loadbalance/filter/TestZoneAwareServerListFilterExt.java new file mode 100644 index 00000000000..8c77c91f12a --- /dev/null +++ b/handlers/handler-loadbalance/src/test/java/io/servicecomb/loadbalance/filter/TestZoneAwareServerListFilterExt.java @@ -0,0 +1,137 @@ +/* + * Copyright 2017 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.servicecomb.loadbalance.filter; + +import com.netflix.loadbalancer.Server; +import io.servicecomb.loadbalance.CseServer; +import io.servicecomb.serviceregistry.RegistryUtils; +import io.servicecomb.serviceregistry.api.registry.DataCenterInfo; +import io.servicecomb.serviceregistry.api.registry.MicroserviceInstance; +import java.util.ArrayList; +import java.util.List; +import mockit.Expectations; +import mockit.Mock; +import mockit.MockUp; +import mockit.Mocked; +import org.junit.Assert; +import org.junit.Test; + +public class TestZoneAwareServerListFilterExt { + + @Test + public void testZoneAwareServerListFilterExt(@Mocked RegistryUtils registryUtils) { + MicroserviceInstance myself = new MicroserviceInstance(); + DataCenterInfo info = new DataCenterInfo(); + info.setName("test"); + info.setRegion("test-Region"); + info.setAvailableZone("test-zone"); + myself.setDataCenterInfo(info); + + MicroserviceInstance allmatchInstance = new MicroserviceInstance(); + info = new DataCenterInfo(); + info.setName("test"); + info.setRegion("test-Region"); + info.setAvailableZone("test-zone"); + allmatchInstance.setDataCenterInfo(info); + + MicroserviceInstance regionMatchInstance = new MicroserviceInstance(); + info = new DataCenterInfo(); + info.setName("test"); + info.setRegion("test-Region"); + info.setAvailableZone("test-zone2"); + regionMatchInstance.setDataCenterInfo(info); + + MicroserviceInstance noneMatchInstance = new MicroserviceInstance(); + info = new DataCenterInfo(); + info.setName("test"); + info.setRegion("test-Region2"); + info.setAvailableZone("test-zone2"); + noneMatchInstance.setDataCenterInfo(info); + + new Expectations() { + { + RegistryUtils.getMicroserviceInstance(); + result = myself; + } + }; + ZoneAwareServerListFilterExt filter = new ZoneAwareServerListFilterExt(); + List servers = new ArrayList<>(); + CseServer noneMatchServer = new MockUp() { + @Mock + public String toString() { + return "noneMatchServer"; + } + + @Mock + public String getHost() { + return "noneMatchServer"; + } + + @Mock + public MicroserviceInstance getInstance() { + return noneMatchInstance; + } + }.getMockInstance(); + CseServer regionMatchregionMatchServer = new MockUp() { + @Mock + public String toString() { + return "regionMatchregionMatchServer"; + } + + @Mock + public String getHost() { + return "regionMatchregionMatchServer"; + } + + @Mock + public MicroserviceInstance getInstance() { + return regionMatchInstance; + } + }.getMockInstance(); + + CseServer allmatchServer = new MockUp() { + @Mock + public String toString() { + return "allmatchServer"; + } + + @Mock + public String getHost() { + return "allmatchServer"; + } + + @Mock + public MicroserviceInstance getInstance() { + return allmatchInstance; + } + }.getMockInstance(); + + servers.add(noneMatchServer); + List result = filter.getFilteredListOfServers(servers); + Assert.assertEquals(result.size(), 1); + Assert.assertEquals(result.get(0), noneMatchServer); + + servers.add(regionMatchregionMatchServer); + result = filter.getFilteredListOfServers(servers); + Assert.assertEquals(result.size(), 1); + Assert.assertEquals(result.get(0), regionMatchregionMatchServer); + + servers.add(allmatchServer); + result = filter.getFilteredListOfServers(servers); + Assert.assertEquals(result.size(), 1); + Assert.assertEquals(result.get(0), allmatchServer); + } +} diff --git a/service-registry/src/main/java/io/servicecomb/serviceregistry/api/registry/DataCenterInfo.java b/service-registry/src/main/java/io/servicecomb/serviceregistry/api/registry/DataCenterInfo.java new file mode 100644 index 00000000000..a880878611c --- /dev/null +++ b/service-registry/src/main/java/io/servicecomb/serviceregistry/api/registry/DataCenterInfo.java @@ -0,0 +1,47 @@ +/* + * Copyright 2017 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.servicecomb.serviceregistry.api.registry; + +public class DataCenterInfo { + + private String name; + private String region; + private String availableZone; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getRegion() { + return region; + } + + public void setRegion(String region) { + this.region = region; + } + + public String getAvailableZone() { + return availableZone; + } + + public void setAvailableZone(String availableZone) { + this.availableZone = availableZone; + } +} diff --git a/service-registry/src/main/java/io/servicecomb/serviceregistry/api/registry/MicroserviceFactory.java b/service-registry/src/main/java/io/servicecomb/serviceregistry/api/registry/MicroserviceFactory.java index 11e55e29978..bd4ce7ddc74 100644 --- a/service-registry/src/main/java/io/servicecomb/serviceregistry/api/registry/MicroserviceFactory.java +++ b/service-registry/src/main/java/io/servicecomb/serviceregistry/api/registry/MicroserviceFactory.java @@ -33,23 +33,10 @@ public Microservice create(String appId, String microserviceName) { public Microservice create(MicroserviceDefinition microserviceDefinition) { Configuration configuration = microserviceDefinition.getConfiguration(); Microservice microservice = createMicroserviceFromDefinition(configuration); - microservice.setIntance(createMicroserviceInstance(configuration)); + microservice.setIntance(MicroserviceInstance.createFromDefinition(configuration)); return microservice; } - private MicroserviceInstance createMicroserviceInstance(Configuration configuration) { - MicroserviceInstance microserviceInstance = new MicroserviceInstance(); - microserviceInstance.setStage(DefinitionConst.defaultStage); - Map propertiesMap = InstancePropertiesLoader.INSTANCE.loadProperties(configuration); - microserviceInstance.setProperties(propertiesMap); - - HealthCheck healthCheck = new HealthCheck(); - healthCheck.setMode(HealthCheckMode.HEARTBEAT); - microserviceInstance.setHealthCheck(healthCheck); - - return microserviceInstance; - } - private Microservice createMicroserviceFromDefinition(Configuration configuration) { Microservice microservice = new Microservice(); microservice.setServiceName(configuration.getString(DefinitionConst.qulifiedServiceNameKey, diff --git a/service-registry/src/main/java/io/servicecomb/serviceregistry/api/registry/MicroserviceInstance.java b/service-registry/src/main/java/io/servicecomb/serviceregistry/api/registry/MicroserviceInstance.java index 6d789b03594..26a2e01d872 100644 --- a/service-registry/src/main/java/io/servicecomb/serviceregistry/api/registry/MicroserviceInstance.java +++ b/service-registry/src/main/java/io/servicecomb/serviceregistry/api/registry/MicroserviceInstance.java @@ -22,6 +22,11 @@ import java.util.Map; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.netflix.config.DynamicPropertyFactory; +import io.servicecomb.serviceregistry.config.InstancePropertiesLoader; +import io.servicecomb.serviceregistry.definition.DefinitionConst; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.lang.StringUtils; /** * Created by on 2016/12/5. @@ -47,6 +52,8 @@ public class MicroserviceInstance { private String stage; + private DataCenterInfo dataCenterInfo; + public String getInstanceId() { return instanceId; } @@ -110,4 +117,44 @@ public String getStage() { public void setStage(String stage) { this.stage = stage; } + + public DataCenterInfo getDataCenterInfo() { + return dataCenterInfo; + } + + public void setDataCenterInfo(DataCenterInfo dataCenterInfo) { + this.dataCenterInfo = dataCenterInfo; + } + + // Some properties of microservice instance are dynamic changed, not cover them all now. + public static MicroserviceInstance createFromDefinition(Configuration configuration) { + MicroserviceInstance microserviceInstance = new MicroserviceInstance(); + // default hard coded values + microserviceInstance.setStage(DefinitionConst.defaultStage); + HealthCheck healthCheck = new HealthCheck(); + healthCheck.setMode(HealthCheckMode.HEARTBEAT); + microserviceInstance.setHealthCheck(healthCheck); + + // load properties + Map propertiesMap = InstancePropertiesLoader.INSTANCE.loadProperties(configuration); + microserviceInstance.setProperties(propertiesMap); + + // load data center information + loadDatacenterInfo(microserviceInstance); + return microserviceInstance; + } + + private static void loadDatacenterInfo(MicroserviceInstance microserviceInstance) { + String dataCenterName = DynamicPropertyFactory.getInstance().getStringProperty("cse.datacenter.name", null) + .get(); + if (StringUtils.isNotEmpty(dataCenterName)) { + DataCenterInfo dataCenterInfo = new DataCenterInfo(); + dataCenterInfo.setName(dataCenterName); + dataCenterInfo + .setRegion(DynamicPropertyFactory.getInstance().getStringProperty("cse.datacenter.region", null).get()); + dataCenterInfo.setAvailableZone( + DynamicPropertyFactory.getInstance().getStringProperty("cse.datacenter.availableZone", null).get()); + microserviceInstance.setDataCenterInfo(dataCenterInfo); + } + } } diff --git a/service-registry/src/test/java/io/servicecomb/serviceregistry/api/registry/TestMicroServiceInstance.java b/service-registry/src/test/java/io/servicecomb/serviceregistry/api/registry/TestMicroServiceInstance.java index 440c3d90a53..8e5a10b8be2 100644 --- a/service-registry/src/test/java/io/servicecomb/serviceregistry/api/registry/TestMicroServiceInstance.java +++ b/service-registry/src/test/java/io/servicecomb/serviceregistry/api/registry/TestMicroServiceInstance.java @@ -16,14 +16,20 @@ package io.servicecomb.serviceregistry.api.registry; -import org.junit.Assert; - +import com.netflix.config.ConcurrentCompositeConfiguration; +import com.netflix.config.ConfigurationManager; +import com.netflix.config.DynamicPropertyFactory; +import io.servicecomb.config.ConfigUtil; +import io.servicecomb.serviceregistry.RegistryUtils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; - +import mockit.Deencapsulation; +import org.apache.commons.configuration.AbstractConfiguration; import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; @@ -31,7 +37,7 @@ /** * * @since Mar 13, 2017 - * @see + * @see */ public class TestMicroServiceInstance { @@ -43,6 +49,13 @@ public class TestMicroServiceInstance { HealthCheck oMockHealthCheck = null; + @AfterClass + public static void classTeardown() { + Deencapsulation.setField(ConfigurationManager.class, "instance", null); + Deencapsulation.setField(ConfigurationManager.class, "customConfigurationInstalled", false); + Deencapsulation.setField(DynamicPropertyFactory.class, "config", null); + RegistryUtils.setServiceRegistry(null); + } @Before public void setUp() throws Exception { oMicroserviceInstance = new MicroserviceInstance(); @@ -99,4 +112,15 @@ private void initMicroserviceInstance() { oMicroserviceInstance.setHealthCheck(oMockHealthCheck); } + @Test + public void testCreateMicroserviceInstanceFromFile() { + AbstractConfiguration config = ConfigUtil.createDynamicConfig(); + ConcurrentCompositeConfiguration configuration = new ConcurrentCompositeConfiguration(); + configuration.addConfiguration(config); + ConfigurationManager.install(configuration); + MicroserviceInstance instance = MicroserviceInstance.createFromDefinition(config); + Assert.assertEquals(instance.getDataCenterInfo().getName(), "myDC"); + Assert.assertEquals(instance.getDataCenterInfo().getRegion(), "my-Region"); + Assert.assertEquals(instance.getDataCenterInfo().getAvailableZone(), "my-Zone"); + } } diff --git a/service-registry/src/test/resources/microservice.yaml b/service-registry/src/test/resources/microservice.yaml index 98cf37ce386..ff48223f1f9 100644 --- a/service-registry/src/test/resources/microservice.yaml +++ b/service-registry/src/test/resources/microservice.yaml @@ -26,6 +26,10 @@ cse: healthCheck: interval: 1 times: 5 + datacenter: + name: myDC + region: my-Region + availableZone: my-Zone #ssl.keystore.path= #ssl.keystore.pass= #ssl.truststore.path=