/* * Copyright 2013-2015 the original author or authors. * * 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 org.springframework.cloud.netflix.ribbon; import java.io.IOException; import java.net.URI; import java.net.URL; import java.util.Collections; import java.util.Map; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest; import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.RibbonServer; import org.springframework.web.util.DefaultUriTemplateHandler; import com.netflix.client.config.CommonClientConfigKey; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.LoadBalancerStats; import com.netflix.loadbalancer.Server; import com.netflix.loadbalancer.ServerStats; import lombok.SneakyThrows; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.anyDouble; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * @author Spencer Gibb */ public class RibbonLoadBalancerClientTests { @Mock private SpringClientFactory clientFactory; @Mock private BaseLoadBalancer loadBalancer; @Mock private LoadBalancerStats loadBalancerStats; @Mock private ServerStats serverStats; @Before public void init() { MockitoAnnotations.initMocks(this); given(this.clientFactory.getLoadBalancerContext(anyString())).willReturn( new RibbonLoadBalancerContext(this.loadBalancer)); given(this.clientFactory.getInstance(anyString(), eq(ServerIntrospector.class))) .willReturn(new DefaultServerIntrospector() { @Override public Map<String, String> getMetadata(Server server) { return Collections.singletonMap("mykey", "myvalue"); } }); } @Test public void reconstructURI() { testReconstructURI("http"); } @Test public void reconstructSecureURI() throws Exception { testReconstructURI("https"); } @SneakyThrows private void testReconstructURI(String scheme) { RibbonServer server = getRibbonServer(); RibbonLoadBalancerClient client = getRibbonLoadBalancerClient(server); ServiceInstance serviceInstance = client.choose(server.getServiceId()); URI uri = client.reconstructURI(serviceInstance, new URL(scheme + "://" + server.getServiceId()).toURI()); assertEquals(server.getHost(), uri.getHost()); assertEquals(server.getPort(), uri.getPort()); } @Test public void testReconstructSecureUriWithSpecialCharsPath() { testReconstructUriWithPath("https", "/foo=|"); } @Test public void testReconstructUnsecureUriWithSpecialCharsPath() { testReconstructUriWithPath("http", "/foo=|"); } private void testReconstructUriWithPath(String scheme, String path) { RibbonServer server = getRibbonServer(); IClientConfig config = mock(IClientConfig.class); when(config.get(CommonClientConfigKey.IsSecure)).thenReturn(true); when(clientFactory.getClientConfig(server.getServiceId())).thenReturn(config); RibbonLoadBalancerClient client = getRibbonLoadBalancerClient(server); ServiceInstance serviceInstance = client.choose(server.getServiceId()); URI expanded = new DefaultUriTemplateHandler() .expand(scheme + "://" + server.getServiceId() + path); URI reconstructed = client.reconstructURI(serviceInstance, expanded); assertEquals(expanded.getPath(), reconstructed.getPath()); } @Test @SneakyThrows public void testReconstructUriWithSecureClientConfig() { RibbonServer server = getRibbonServer(); IClientConfig config = mock(IClientConfig.class); when(config.get(CommonClientConfigKey.IsSecure)).thenReturn(true); when(clientFactory.getClientConfig(server.getServiceId())).thenReturn(config); RibbonLoadBalancerClient client = getRibbonLoadBalancerClient(server); ServiceInstance serviceInstance = client.choose(server.getServiceId()); URI uri = client.reconstructURI(serviceInstance, new URL("http://" + server.getServiceId()).toURI()); assertEquals(server.getHost(), uri.getHost()); assertEquals(server.getPort(), uri.getPort()); assertEquals("https", uri.getScheme()); } @Test @SneakyThrows public void testReconstructSecureUriWithoutScheme() { testReconstructSchemelessUriWithoutClientConfig(getSecureRibbonServer(), "https"); } @Test @SneakyThrows public void testReconstructUnsecureSchemelessUri() { testReconstructSchemelessUriWithoutClientConfig(getRibbonServer(), "http"); } @SneakyThrows public void testReconstructSchemelessUriWithoutClientConfig(RibbonServer server, String expectedScheme) { IClientConfig config = mock(IClientConfig.class); when(config.get(CommonClientConfigKey.IsSecure)).thenReturn(null); when(clientFactory.getClientConfig(server.getServiceId())).thenReturn(config); RibbonLoadBalancerClient client = getRibbonLoadBalancerClient(server); ServiceInstance serviceInstance = client.choose(server.getServiceId()); URI uri = client.reconstructURI(serviceInstance, new URI("//" + server.getServiceId())); assertEquals(server.getHost(), uri.getHost()); assertEquals(server.getPort(), uri.getPort()); assertEquals(expectedScheme, uri.getScheme()); } @Test public void testChoose() { RibbonServer server = getRibbonServer(); RibbonLoadBalancerClient client = getRibbonLoadBalancerClient(server); ServiceInstance serviceInstance = client.choose(server.getServiceId()); assertServiceInstance(server, serviceInstance); } @Test public void testChooseMissing() { given(this.clientFactory.getLoadBalancer(this.loadBalancer.getName())) .willReturn(null); given(this.loadBalancer.getName()).willReturn("missingservice"); RibbonLoadBalancerClient client = new RibbonLoadBalancerClient(this.clientFactory); ServiceInstance instance = client.choose("missingservice"); assertNull("instance wasn't null", instance); } @Test public void testExecute() throws IOException { final RibbonServer server = getRibbonServer(); RibbonLoadBalancerClient client = getRibbonLoadBalancerClient(server); final String returnVal = "myval"; Object actualReturn = client.execute(server.getServiceId(), new LoadBalancerRequest<Object>() { @Override public Object apply(ServiceInstance instance) throws Exception { assertServiceInstance(server, instance); return returnVal; } }); verifyServerStats(); assertEquals("retVal was wrong", returnVal, actualReturn); } @Test public void testExecuteException() { final RibbonServer ribbonServer = getRibbonServer(); RibbonLoadBalancerClient client = getRibbonLoadBalancerClient(ribbonServer); try { client.execute(ribbonServer.getServiceId(), new LoadBalancerRequest<Object>() { @Override public Object apply(ServiceInstance instance) throws Exception { assertServiceInstance(ribbonServer, instance); throw new RuntimeException(); } }); fail("Should have thrown exception"); } catch (Exception ex) { assertNotNull(ex); } verifyServerStats(); } @Test public void testExecuteIOException() { final RibbonServer ribbonServer = getRibbonServer(); RibbonLoadBalancerClient client = getRibbonLoadBalancerClient(ribbonServer); try { client.execute(ribbonServer.getServiceId(), new LoadBalancerRequest<Object>() { @Override public Object apply(ServiceInstance instance) throws Exception { assertServiceInstance(ribbonServer, instance); throw new IOException(); } }); fail("Should have thrown exception"); } catch (Exception ex) { assertThat("wrong exception type", ex, is(instanceOf(IOException.class))); } verifyServerStats(); } protected RibbonServer getRibbonServer() { return new RibbonServer("testService", new Server("myhost", 9080), false, Collections.singletonMap("mykey", "myvalue")); } protected RibbonServer getSecureRibbonServer() { return new RibbonServer("testService", new Server("myhost", 8443), false, Collections.singletonMap("mykey", "myvalue")); } protected void verifyServerStats() { verify(this.serverStats).incrementActiveRequestsCount(); verify(this.serverStats).decrementActiveRequestsCount(); verify(this.serverStats).incrementNumRequests(); verify(this.serverStats).noteResponseTime(anyDouble()); } protected void assertServiceInstance(RibbonServer ribbonServer, ServiceInstance instance) { assertNotNull("instance was null", instance); assertEquals("serviceId was wrong", ribbonServer.getServiceId(), instance.getServiceId()); assertEquals("host was wrong", ribbonServer.getHost(), instance.getHost()); assertEquals("port was wrong", ribbonServer.getPort(), instance.getPort()); assertEquals("missing metadata", ribbonServer.getMetadata().get("mykey"), instance.getMetadata().get("mykey")); } protected RibbonLoadBalancerClient getRibbonLoadBalancerClient( RibbonServer ribbonServer) { given(this.loadBalancer.getName()).willReturn(ribbonServer.getServiceId()); given(this.loadBalancer.chooseServer(anyObject())).willReturn( ribbonServer.getServer()); given(this.loadBalancer.getLoadBalancerStats()) .willReturn(this.loadBalancerStats); given(this.loadBalancerStats.getSingleServerStat(ribbonServer.getServer())) .willReturn(this.serverStats); given(this.clientFactory.getLoadBalancer(this.loadBalancer.getName())) .willReturn(this.loadBalancer); return new RibbonLoadBalancerClient(this.clientFactory); } }