org.apache.servicecomb.loadbalance.TestLoadbalanceHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.servicecomb.loadbalance.TestLoadbalanceHandler.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.servicecomb.loadbalance;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;

import javax.ws.rs.core.Response.Status;
import javax.xml.ws.Holder;

import org.apache.commons.configuration.AbstractConfiguration;
import org.apache.servicecomb.config.ConfigUtil;
import org.apache.servicecomb.core.CseContext;
import org.apache.servicecomb.core.Invocation;
import org.apache.servicecomb.core.Transport;
import org.apache.servicecomb.core.transport.TransportManager;
import org.apache.servicecomb.foundation.common.cache.VersionedCache;
import org.apache.servicecomb.foundation.common.utils.SPIServiceUtils;
import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils;
import org.apache.servicecomb.loadbalance.filter.SimpleTransactionControlFilter;
import org.apache.servicecomb.serviceregistry.RegistryUtils;
import org.apache.servicecomb.serviceregistry.ServiceRegistry;
import org.apache.servicecomb.serviceregistry.api.registry.MicroserviceInstance;
import org.apache.servicecomb.serviceregistry.cache.CacheEndpoint;
import org.apache.servicecomb.serviceregistry.cache.InstanceCacheManager;
import org.apache.servicecomb.serviceregistry.discovery.DiscoveryFilter;
import org.apache.servicecomb.swagger.invocation.AsyncResponse;
import org.apache.servicecomb.swagger.invocation.Response;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mockito;

import com.netflix.config.DynamicPropertyFactory;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.Server;

import mockit.Deencapsulation;
import mockit.Expectations;
import mockit.Injectable;
import mockit.Mock;
import mockit.MockUp;
import mockit.Mocked;

/**
 *
 *
 */
public class TestLoadbalanceHandler {
    String microserviceName = "ms";

    IRule rule = Mockito.mock(IRule.class);

    LoadbalanceHandler handler;

    Map<String, LoadBalancer> loadBalancerMap;

    private LoadBalancer loadBalancer = new LoadBalancer("loadBalancerName", rule, "test");

    @Injectable
    Invocation invocation;

    @Mocked
    ServiceRegistry serviceRegistry;

    @Mocked
    InstanceCacheManager instanceCacheManager;

    @Mocked
    TransportManager transportManager;

    @Mocked
    Transport restTransport;

    Response sendResponse;

    List<String> results = new ArrayList<>();

    @Rule
    public ExpectedException expectedException = ExpectedException.none();

    @Before
    public void setUp() {
        ConfigUtil.installDynamicConfig();
        AbstractConfiguration configuration = (AbstractConfiguration) DynamicPropertyFactory
                .getBackingConfigurationSource();
        configuration.addProperty("servicecomb.loadbalance.test.transactionControl.policy",
                "org.apache.servicecomb.loadbalance.filter.SimpleTransactionControlFilter");
        configuration.addProperty("servicecomb.loadbalance.test.transactionControl.options.tag0", "value0");
        configuration.addProperty("servicecomb.loadbalance.test.isolation.enabled", "true");
        configuration.addProperty("servicecomb.loadbalance.serverListFilters", "a");
        configuration.addProperty("servicecomb.loadbalance.serverListFilter.a.className",
                "org.apache.servicecomb.loadbalance.MyServerListFilterExt");

        new MockUp<Invocation>(invocation) {
            @Mock
            String getMicroserviceName() {
                return microserviceName;
            }

            @Mock
            void next(AsyncResponse asyncResp) throws Exception {
                asyncResp.handle(sendResponse);
            }
        };

        CseContext.getInstance().setTransportManager(transportManager);
        new MockUp<TransportManager>(transportManager) {
            @Mock
            Transport findTransport(String transportName) {
                return restTransport;
            }
        };

        RegistryUtils.setServiceRegistry(serviceRegistry);
        new MockUp<ServiceRegistry>(serviceRegistry) {
            @Mock
            InstanceCacheManager getInstanceCacheManager() {
                return instanceCacheManager;
            }
        };

        new Expectations(SPIServiceUtils.class) {
            {
                SPIServiceUtils.getSortedService(DiscoveryFilter.class);
                result = Collections.emptyList();
            }
        };

        BeansHolder holder = new BeansHolder();
        List<ExtensionsFactory> extensionsFactories = new ArrayList<>();
        extensionsFactories.add(new RuleClassNameExtentionsFactory());
        extensionsFactories.add(new RuleNameExtentionsFactory());
        extensionsFactories.add(new DefaultRetryExtensionsFactory());
        Deencapsulation.setField(holder, "extentionsFactories", extensionsFactories);
        holder.init();

        handler = new LoadbalanceHandler();
        loadBalancerMap = Deencapsulation.getField(handler, "loadBalancerMap");
    }

    @After
    public void teardown() {
        CseContext.getInstance().setTransportManager(null);
        RegistryUtils.setServiceRegistry(null);
        ArchaiusUtils.resetConfig();
    }

    @Test
    public void handleClearLoadBalancer() throws Exception {
        new MockUp<LoadbalanceHandler>(handler) {
            @Mock
            LoadBalancer getOrCreateLoadBalancer(Invocation invocation) {
                return loadBalancer;
            }

            @Mock
            void send(Invocation invocation, AsyncResponse asyncResp, final LoadBalancer choosenLB)
                    throws Exception {
            }
        };

        loadBalancerMap.put("old", loadBalancer);
        Deencapsulation.setField(handler, "policy", "init");
        handler.handle(invocation, ar -> {
        });

        Assert.assertThat(loadBalancerMap.values(), Matchers.empty());
    }

    @Test
    public void handleSendNotRetry() throws Exception {
        new MockUp<LoadbalanceHandler>(handler) {
            @Mock
            LoadBalancer getOrCreateLoadBalancer(Invocation invocation) {
                return loadBalancer;
            }

            @Mock
            void send(Invocation invocation, AsyncResponse asyncResp, final LoadBalancer choosenLB)
                    throws Exception {
                results.add("sendNotRetry");
            }
        };

        handler.handle(invocation, ar -> {
        });

        Assert.assertThat(results, Matchers.contains("sendNotRetry"));
    }

    @Test
    public void handleSendWithRetry() throws Exception {
        new MockUp<LoadbalanceHandler>(handler) {
            @Mock
            LoadBalancer getOrCreateLoadBalancer(Invocation invocation) {
                return loadBalancer;
            }

            @Mock
            void sendWithRetry(Invocation invocation, AsyncResponse asyncResp, final LoadBalancer choosenLB)
                    throws Exception {
                results.add("sendWithRetry");
            }
        };

        new MockUp<Configuration>(Configuration.INSTANCE) {
            @Mock
            boolean isRetryEnabled(String microservice) {
                return true;
            }
        };

        handler.handle(invocation, ar -> {
        });

        Assert.assertThat(results, Matchers.contains("sendWithRetry"));
    }

    @Test
    public void testSetIsolationFilter() {
        Invocation invocation = Mockito.mock(Invocation.class);
        Mockito.when(invocation.getMicroserviceName()).thenReturn("test");
        LoadbalanceHandler lbHandler = new LoadbalanceHandler();
        LoadBalancer myLB = new LoadBalancer("loadBalancerName", rule, "test");
        lbHandler.setIsolationFilter(myLB, "abc");
        Assert.assertEquals(1, myLB.getFilterSize());

        Mockito.when(invocation.getMicroserviceName()).thenReturn("abc");
        myLB = new LoadBalancer("loadBalancerName", rule, "test");
        lbHandler.setIsolationFilter(myLB, "abc");
        myLB.setInvocation(invocation);

        Assert.assertEquals(1, myLB.getFilterSize());
        Map<String, ServerListFilterExt> filters = Deencapsulation.getField(myLB, "filters");
        List<Server> servers = new ArrayList<>();
        servers.add(new Server(null));
        Assert.assertEquals(servers.size(),
                filters.get("org.apache.servicecomb.loadbalance.filter.IsolationServerListFilter")
                        .getFilteredListOfServers(servers).size());
    }

    @Test
    public void setTransactionControlFilter_NoPolicy() {
        new MockUp<Configuration>(Configuration.INSTANCE) {
            @Mock
            String getFlowsplitFilterPolicy(String microservice) {
                return "";
            }
        };

        handler.setTransactionControlFilter(loadBalancer, microserviceName);
        Assert.assertEquals(0, loadBalancer.getFilterSize());
    }

    @Test
    public void setTransactionControlFilter_InvalidPolicy() {
        ArchaiusUtils.setProperty("servicecomb.loadbalance.ms.transactionControl.policy", "InvalidPolicy");

        expectedException.expect(Error.class);
        expectedException.expectMessage(Matchers.is("Fail to create instance of class: InvalidPolicy"));

        handler.setTransactionControlFilter(loadBalancer, microserviceName);
        Assert.assertEquals(0, loadBalancer.getFilterSize());
    }

    @Test
    public void setTransactionControlFilter_PolicyNotAssignable() {
        new MockUp<Configuration>(Configuration.INSTANCE) {
            @Mock
            String getFlowsplitFilterPolicy(String microservice) {
                return String.class.getName();
            }
        };

        expectedException.expect(Error.class);
        expectedException.expectMessage(Matchers.is("Fail to create instance of class: java.lang.String"));

        handler.setTransactionControlFilter(loadBalancer, microserviceName);
        Assert.assertEquals(0, loadBalancer.getFilterSize());
    }

    @Test
    public void setTransactionControlFilter_Normal() {
        new MockUp<Configuration>(Configuration.INSTANCE) {
            @Mock
            String getFlowsplitFilterPolicy(String microservice) {
                return SimpleTransactionControlFilter.class.getName();
            }
        };

        handler.setTransactionControlFilter(loadBalancer, microserviceName);
        Assert.assertEquals(1, loadBalancer.getFilterSize());
    }

    @Test
    public void getOrCreateLoadBalancer() throws Exception {
        LoadbalanceHandler handler = new LoadbalanceHandler();

        MicroserviceInstance instance = new MicroserviceInstance();
        instance.setInstanceId("id");
        instance.getEndpoints().add("rest://localhost:8080");

        Map<String, MicroserviceInstance> instanceMap = new HashMap<>();
        instanceMap.put(instance.getInstanceId(), instance);

        VersionedCache instanceVersionedCache = new VersionedCache().autoCacheVersion().name("instanceCache")
                .data(instanceMap);

        new Expectations() {
            {
                invocation.getConfigTransportName();
                result = "rest";
                instanceCacheManager.getOrCreateVersionedCache(anyString, anyString, anyString);
                result = instanceVersionedCache;
            }
        };

        LoadBalancer lb = handler.getOrCreateLoadBalancer(invocation);

        Assert.assertEquals(2, lb.getFilterSize());
        Assert.assertEquals("instanceCache/rest", lb.getName());
        Assert.assertEquals("[rest://localhost:8080]", Deencapsulation.getField(lb, "serverList").toString());
    }

    @Test
    public void send_noEndPoint() {
        new Expectations(loadBalancer) {
            {
                loadBalancer.chooseServer(invocation);
                result = null;
            }
        };

        Holder<Throwable> result = new Holder<>();
        Deencapsulation.invoke(handler, "send", invocation, (AsyncResponse) resp -> {
            result.value = (Throwable) resp.getResult();
        }, loadBalancer);

        Assert.assertEquals(
                "InvocationException: code=490;msg=CommonExceptionData [message=Cse Internal Bad Request]",
                result.value.getMessage());
        Assert.assertEquals(
                "No available address found. microserviceName=ms, version=null, discoveryGroupName=null",
                result.value.getCause().getMessage());
    }

    @Test
    public void send_failed() {
        CacheEndpoint cacheEndpoint = new CacheEndpoint("rest://localhost:8080", null);
        CseServer server = new CseServer(restTransport, cacheEndpoint);
        new MockUp<System>() {
            @Mock
            long currentTimeMillis() {
                return 123;
            }
        };
        new Expectations(loadBalancer) {
            {
                loadBalancer.chooseServer(invocation);
                result = server;
            }
        };
        int continuousFailureCount = server.getCountinuousFailureCount();

        sendResponse = Response.create(Status.BAD_REQUEST, "send failed");

        Holder<Throwable> result = new Holder<>();
        Deencapsulation.invoke(handler, "send", invocation, (AsyncResponse) resp -> {
            result.value = (Throwable) resp.getResult();
        }, loadBalancer);

        Assert.assertEquals(123, server.getLastVisitTime());
        Assert.assertEquals(1, loadBalancer.getLoadBalancerStats().getSingleServerStat(server)
                .getSuccessiveConnectionFailureCount());
        Assert.assertEquals("InvocationException: code=400;msg=send failed", result.value.getMessage());
        Assert.assertEquals(continuousFailureCount + 1, server.getCountinuousFailureCount());
    }

    @Test
    public void send_success() {
        CacheEndpoint cacheEndpoint = new CacheEndpoint("rest://localhost:8080", null);
        CseServer server = new CseServer(restTransport, cacheEndpoint);
        new MockUp<System>() {
            @Mock
            long currentTimeMillis() {
                return 123;
            }
        };
        new Expectations(loadBalancer) {
            {
                loadBalancer.chooseServer(invocation);
                result = server;
            }
        };
        server.incrementContinuousFailureCount();

        sendResponse = Response.ok("success");

        Holder<String> result = new Holder<>();
        Deencapsulation.invoke(handler, "send", invocation, (AsyncResponse) resp -> {
            result.value = resp.getResult();
        }, loadBalancer);

        Assert.assertEquals(123, server.getLastVisitTime());
        Assert.assertEquals(1,
                loadBalancer.getLoadBalancerStats().getSingleServerStat(server).getActiveRequestsCount());
        Assert.assertEquals("success", result.value);
        Assert.assertEquals(0, server.getCountinuousFailureCount());
    }

    @Test
    public void sendWithRetry() {
        Holder<String> result = new Holder<>();
        Deencapsulation.invoke(handler, "sendWithRetry", invocation, (AsyncResponse) resp -> {
            result.value = resp.getResult();
        }, loadBalancer);

        // no exception
    }

    @Test
    public void testIsEqual() {
        boolean nullResult = handler.isEqual(null, null);
        Assert.assertEquals(true, nullResult);
        boolean bothNotNullResult = handler.isEqual("com.netflix.loadbalancer.RandomRule",
                "com.netflix.loadbalancer.RandomRule");
        Assert.assertEquals(true, bothNotNullResult);
        boolean globalNotNull = handler.isEqual(null, "com.netflix.loadbalancer.RandomRule");
        Assert.assertEquals(false, globalNotNull);
        boolean localNotNull = handler.isEqual("com.netflix.loadbalancer.RandomRule", null);
        Assert.assertEquals(false, localNotNull);
    }

    @Test
    public void testIsFailedResponse() {
        Assert.assertFalse(handler.isFailedResponse(Response.create(400, "", "")));
        Assert.assertFalse(handler.isFailedResponse(Response.create(500, "", "")));
        Assert.assertTrue(handler.isFailedResponse(Response.create(490, "", "")));
        Assert.assertTrue(handler.isFailedResponse(Response.consumerFailResp(new NullPointerException())));
    }

    @Test
    public void retryPoolDaemon() throws ExecutionException, InterruptedException {
        ExecutorService RETRY_POOL = Deencapsulation.getField(handler, "RETRY_POOL");

        Holder<Thread> nameHolder = new Holder<>();

        RETRY_POOL.submit(() -> {
            nameHolder.value = Thread.currentThread();
        }).get();

        Assert.assertThat(nameHolder.value.getName(), Matchers.startsWith("retry-pool-thread-"));
        Assert.assertTrue(nameHolder.value.isDaemon());
    }
}