/** * Copyright (C) 2010-14 diirt developers. See COPYRIGHT.TXT * All rights reserved. Use is subject to license terms. See LICENSE.TXT */ package org.diirt.support.pva.rpcservice; import java.io.InputStream; import java.util.logging.Level; import java.util.logging.Logger; import org.diirt.service.Service; import org.diirt.support.pva.rpcservice.rpcclient.PoolConfiguration; import org.diirt.support.pva.rpcservice.rpcclient.PoolStatistics; import org.diirt.support.pva.rpcservice.rpcclient.PooledRPCClientFactory; import org.epics.pvaccess.PVAException; import org.epics.pvaccess.client.rpc.RPCClient; import org.epics.pvaccess.server.rpc.RPCRequestException; import org.epics.pvaccess.server.rpc.RPCServer; import org.epics.pvdata.factory.FieldFactory; import org.epics.pvdata.factory.PVDataFactory; import org.epics.pvdata.pv.*; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import org.junit.After; import static org.junit.Assert.*; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; /** * Testing a RPC client pooling * * @author dkumar */ // TODO @Ignore public class PooledRPCClientTest implements Runnable { //testing channel name private static final String TEST_CHANNEL = "testChannel"; //a testing rpc server that implements a simple sum method functionality private RPCServer rpcServer; //a utility member for pv request creation private final static FieldCreate fieldCreate = FieldFactory.getFieldCreate(); @Test public void testServiceWithAPoolConfiguration() { InputStream stream = getClass().getResourceAsStream("RPCSumServiceWithPoolConfiguration.xml"); //the rpcservice will read the pool configuration from the rpcservice description xml and set it on the PooledRPCClientFactory Service service = RPCServices.createFromXml(stream); PoolConfiguration poolConfiguration = PooledRPCClientFactory.getPoolConfiguration(); //asserting the values that are different from the default configuration assertThat(poolConfiguration.getAbandonWhenPercentageFull(), equalTo(11)); assertThat(poolConfiguration.getInitialSize(), equalTo(5)); assertThat(poolConfiguration.isLogAbandoned(), equalTo(true)); assertThat(poolConfiguration.getMaxActive(), equalTo(6)); assertThat(poolConfiguration.getMaxIdle(), equalTo(3)); assertThat(poolConfiguration.getMinIdle(), equalTo(1)); assertThat(poolConfiguration.getMaxWait(), equalTo(5000)); assertThat(poolConfiguration.getMinEvictableIdleTimeMillis(), equalTo(35000)); assertThat(poolConfiguration.isRemoveAbandoned(), equalTo(true)); assertThat(poolConfiguration.getRemoveAbandonedTimeout(), equalTo(1234)); assertThat(poolConfiguration.isTestOnBorrow(), equalTo(true)); assertThat(poolConfiguration.isTestOnReturn(), equalTo(true)); assertThat(poolConfiguration.isTestWhileIdle(), equalTo(true)); assertThat(poolConfiguration.getTimeBetweenEvictionRunsMillis(), equalTo(2345)); assertThat(poolConfiguration.getValidationInterval(), equalTo(60000l)); assertThat(poolConfiguration.getMaxAge(), equalTo(123l)); assertThat(poolConfiguration.getUseLock(), equalTo(true)); assertThat(poolConfiguration.getSuspectTimeout(), equalTo(100)); } @Test public void testConfigurePoolWithWrongMaxActive() throws Exception { PoolConfiguration poolConfig = PooledRPCClientFactory.getPoolConfiguration(); poolConfig.setMaxActive(-1); RPCClient rpcClient = PooledRPCClientFactory.getRPCClient(null, TEST_CHANNEL); assertTrue("pool size should be DEFAULT_MAX_ACTIVE, despite the wrong configuration", poolConfig.getMaxActive() == PoolConfiguration.DEFAULT_MAX_ACTIVE ); rpcClient.destroy(); } @Test public void testConfigurePoolWithWrongMaxActiveAndInitialSize() throws Exception { PoolConfiguration poolConfig = PooledRPCClientFactory.getPoolConfiguration(); poolConfig.setMaxActive(10); poolConfig.setInitialSize(100); RPCClient rpcClient = PooledRPCClientFactory.getRPCClient(null, TEST_CHANNEL); assertTrue("pool initial size should be equal to pool max active, despite the wrong configuration", poolConfig.getInitialSize() == poolConfig.getMaxActive() ); rpcClient.destroy(); } @Test public void testConfigurePoolWithWrongMinIdleAndMaxActive() throws Exception { PoolConfiguration poolConfig = PooledRPCClientFactory.getPoolConfiguration(); poolConfig.setMaxActive(10); poolConfig.setMinIdle(100); RPCClient rpcClient = PooledRPCClientFactory.getRPCClient(null, TEST_CHANNEL); assertTrue("pool min idle size should be equal to pool max active, despite the wrong configuration", poolConfig.getMinIdle() == poolConfig.getMaxActive() ); rpcClient.destroy(); } @Test public void testConfigurePoolWithWrongMinIdleAndMaxIdle() throws Exception { PoolConfiguration poolConfig = PooledRPCClientFactory.getPoolConfiguration(); poolConfig.setMaxIdle(10); poolConfig.setMinIdle(100); RPCClient rpcClient = PooledRPCClientFactory.getRPCClient(null, TEST_CHANNEL); assertTrue("pool min idle size should be equal to max idle, despite the wrong configuration", poolConfig.getMinIdle() == poolConfig.getMaxIdle() ); rpcClient.destroy(); } @Test public void testGetRpcClientsFromOnePool() throws Exception { PoolConfiguration poolConfig = PooledRPCClientFactory.getPoolConfiguration(); poolConfig.setInitialSize(2); RPCClient rpcClient = PooledRPCClientFactory.getRPCClient(null, TEST_CHANNEL); PoolStatistics stat = PooledRPCClientFactory.getPoolStatistics(null, TEST_CHANNEL); assertNotNull("statistics for pool not obtained", stat); assertThat(stat.getChannelName(), equalTo(TEST_CHANNEL)); assertNull("rpc client's hostname should be null", stat.getHostName()); assertThat(stat.getActive(), equalTo(1)); assertThat(stat.getSize(), equalTo(2)); assertThat(stat.getName(), containsString("RPCClientPool")); assertThat(stat.getName(), containsString(TEST_CHANNEL)); assertThat(stat.getWaitCount(), equalTo(0)); RPCClient anotherRpcClient = PooledRPCClientFactory.getRPCClient(null, TEST_CHANNEL); assertThat(stat.getActive(), equalTo(2)); rpcClient.destroy(); anotherRpcClient.destroy(); assertThat(stat.getActive(), equalTo(0)); assertThat(stat.getIdle(), equalTo(2)); } @Test public void testGetRpcClientsFromTwoPools() throws Exception { PoolConfiguration poolConfig = PooledRPCClientFactory.getPoolConfiguration(); poolConfig.setMaxActive(2); RPCClient rpcClient = PooledRPCClientFactory.getRPCClient("localhost", TEST_CHANNEL); PoolStatistics stat = PooledRPCClientFactory.getPoolStatistics("localhost", TEST_CHANNEL); assertNotNull("statistics for a pool not obtained", stat); assertThat(stat.getChannelName(), equalTo(TEST_CHANNEL)); assertThat(stat.getHostName(), equalTo("localhost")); assertThat(stat.getActive(), equalTo(1)); assertThat(stat.getSize(), equalTo(2)); //get a rpc client from a different pool RPCClient anotherRpcClient = PooledRPCClientFactory.getRPCClient(null, TEST_CHANNEL); PoolStatistics anotherStat = PooledRPCClientFactory.getPoolStatistics(null, TEST_CHANNEL); assertNotNull("statistics for another pool not obtained", anotherStat); assertTrue("statistics for two different pools should not be the same", !(anotherStat == stat)); assertNull("another's pool hostname should be null", anotherStat.getHostName()); rpcClient.destroy(); anotherRpcClient.destroy(); } @Test(expected = RPCRequestException.class) public void testGetRpcClientFromPoolWithoutIdleSlots() throws Exception { PoolConfiguration poolConfig = PooledRPCClientFactory.getPoolConfiguration(); poolConfig.setInitialSize(1); poolConfig.setMaxActive(1); //wait for 2 seconds for a pooled rpc client poolConfig.setMaxWait(2000); RPCClient rpcClient = PooledRPCClientFactory.getRPCClient(null, TEST_CHANNEL); RPCClient anotherRpcClient = PooledRPCClientFactory.getRPCClient(null, TEST_CHANNEL); assertTrue("we should not obtain the second rpc client since the pool should have no idle slots free", false); } @Test public void testValidationOnIdleRpcClients() throws Exception { PoolConfiguration poolConfig = PooledRPCClientFactory.getPoolConfiguration(); poolConfig.setInitialSize(1); poolConfig.setMaxActive(1); poolConfig.setTestWhileIdle(true); poolConfig.setMinEvictableIdleTimeMillis(1000); poolConfig.setTimeBetweenEvictionRunsMillis(250); poolConfig.setValidationInterval(250); poolConfig.setLogAbandoned(true); //get a rpc client and return it immediately to the pool RPCClient rpcClient = PooledRPCClientFactory.getRPCClient(null, TEST_CHANNEL); rpcClient.destroy(); //wait for eviction idle time * 2 and check that the rpc client we get back is different then the old one Thread.sleep(2000); RPCClient anotherRpcClient = PooledRPCClientFactory.getRPCClient(null, TEST_CHANNEL); assertTrue("the first rpc client should have been evicted from the pool", rpcClient != anotherRpcClient); } @Test public void testResizePoolOnIdleRpcClients() throws Exception { PoolConfiguration poolConfig = PooledRPCClientFactory.getPoolConfiguration(); poolConfig.setInitialSize(5); poolConfig.setMaxActive(10); poolConfig.setMaxIdle(2); poolConfig.setTestWhileIdle(false); poolConfig.setMinIdle(2); poolConfig.setTimeBetweenEvictionRunsMillis(1000); poolConfig.setMinEvictableIdleTimeMillis(250); System.out.println(poolConfig.toString()); //get a rpc client and return it immediately to the pool RPCClient rpcClient = PooledRPCClientFactory.getRPCClient(null, TEST_CHANNEL); rpcClient.destroy(); //check that the initial pool size is bigger then min idle PoolStatistics stat = PooledRPCClientFactory.getPoolStatistics(null, TEST_CHANNEL); assertNotNull("statistics for a pool not obtained", stat); assertTrue("there should be more then min idle slots in the pool", stat.getIdle() > 2); //wait for eviction idle time * 2 and check that the pool scaled back to min idle slots Thread.sleep(4000); assertThat("there should exactly min idle slots in the pool", stat.getIdle(), equalTo(2)); } @Test public void testCheckAbandonRpcClient() throws Exception { PooledRPCClientFactory.resetConfiguration(); PoolConfiguration poolConfig = PooledRPCClientFactory.getPoolConfiguration(); poolConfig.setTimeBetweenEvictionRunsMillis(1000); poolConfig.setMinEvictableIdleTimeMillis(250); //busy rpc clients are abandoned after 1s poolConfig.setRemoveAbandonedTimeout(1); poolConfig.setRemoveAbandoned(true); poolConfig.setTestWhileIdle(false); poolConfig.setValidationInterval(250); System.out.println(poolConfig.toString()); //get a rpc client RPCClient rpcClient = PooledRPCClientFactory.getRPCClient(null, TEST_CHANNEL); PoolStatistics stat = PooledRPCClientFactory.getPoolStatistics(null, TEST_CHANNEL); assertNotNull("statistics for a pool not obtained", stat); assertTrue("there should be 1 active rpc client", stat.getActive() == 1); //allow the time so that an active rpc client is abandoned Thread.sleep(4000); assertThat("there should be no active rpc client since one was abandoned", stat.getActive(), equalTo(0)); } @Test(expected = IllegalArgumentException.class) public void testGetRpcClientWithNullChannelName() throws Exception { RPCClient rpcClient = PooledRPCClientFactory.getRPCClient(null, null); } @Override public void run() { try { this.rpcServer.run(0); } catch (PVAException ex) { Logger.getLogger(PooledRPCClientTest.class.getName()).log(Level.SEVERE, null, ex); } } static class TestRPCServiceImpl implements org.epics.pvaccess.server.rpc.RPCService { @Override public PVStructure request(PVStructure args) throws RPCRequestException { String methodName = args.getStringField("op").get(); if ("sum".equals(methodName)) { return new SumServiceImpl().request(args); } return null; } } static class SumServiceImpl implements org.epics.pvaccess.server.rpc.RPCService { private final static Structure resultStructure = fieldCreate.createStructure( new String[]{"c"}, new Field[]{fieldCreate.createScalar(ScalarType.pvDouble)}); @Override public PVStructure request(PVStructure args) throws RPCRequestException { double a = args.getDoubleField("a").get(); double b = args.getDoubleField("b").get(); PVStructure result = PVDataFactory.getPVDataCreate().createPVStructure(resultStructure); result.getDoubleField("c").put(a + b); return result; } } static class NopServiceImpl implements org.epics.pvaccess.server.rpc.RPCService { private final static Structure resultStructure = fieldCreate.createStructure( new String[0], new Field[0]); @Override public PVStructure request(PVStructure args) throws RPCRequestException { return PVDataFactory.getPVDataCreate().createPVStructure(resultStructure); } } @Before public void onSetUp() throws PVAException { this.rpcServer = new RPCServer(); this.rpcServer.registerService(TEST_CHANNEL, new TestRPCServiceImpl()); this.rpcServer.printInfo(); Thread thread = new Thread(this); thread.start(); } @After public void onTearDown() throws PVAException { PooledRPCClientFactory.close(); this.rpcServer.destroy(); } }