/*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2016 by Pentaho : http://www.pentaho.com * ******************************************************************************* * * 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.pentaho.di.concurrency; import static org.junit.Assert.assertEquals; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Before; import org.junit.Test; import org.pentaho.di.cluster.SlaveServer; import org.pentaho.di.repository.StringObjectId; /** * We have a {@link SlaveServer} with a bunch of attributes (userName, password, port and others). * * The goal of this test is to verify that attributes from one SlaveServer doesn't mix with attributes of * another SlaveServer in concurrent environment when performing replace and clone methods. * * We have a {@code sharedSlaveServer} that will be share across {@link Replacer} and {@link Getter} * * {@link Replacer} generates consistent slaveService's. By consistence the following is meant: * Each slaveServer has it's id (random int) and all his fields are named in pattern: FIELD_NAME + slaveServerId. * Then replacer replaces {@code sharedSlaveServer} with generated slaveServer. * * Note: it is expected that {@code sharedSlaveServer} will always be in consistent state (all his fields will end * with the same id). * * And that's exactly what {@link Getter} does: * * It takes a snapshot of {@code sharedSlaveServer} and verifies it's consistency. * We have to take a snapshot here because {@code sharedSlaveServer} can be updated in the middle of consistency * verification. It's absolutely fine, cause it will be updated with consistent values. * */ public class SlaveServerConcurrentTest { private static final String ID = "id"; private static final String NAME = "name"; private static final String HOST_NAME = "hostName"; private static final String PORT = "port"; private static final String WEB_APP_NAME = "webAppName"; private static final String USERNAME = "userName"; private static final String PASSWORD = "password"; private static final String PROXY_HOST_NAME = "proxyHostName"; private static final String PROXY_PORT = "proxyPort"; private static final String NON_PROXY_HOSTS = "nonProxyHosts"; private static final int NUMBER_OF_REPLACES = 200; private static final int NUMBER_OF_GETTERS = 100; private static final int REPLACE_CIRCLES = 100; private SlaveServer sharedSlaveServer; @Before public void setUp() throws Exception { sharedSlaveServer = generateSlaveServer(); } @Test public void getAndReplaceConcurrently() throws Exception { AtomicBoolean condition = new AtomicBoolean( true ); List<Getter> getters = generateGetters( condition ); List<Replacer> replacers = generateReplacers( condition ); ConcurrencyTestRunner.runAndCheckNoExceptionRaised( replacers, getters, condition ); } private List<Getter> generateGetters( AtomicBoolean condition ) { List<Getter> consistencyCheckers = new ArrayList<>(); for ( int i = 0; i < NUMBER_OF_GETTERS; i++ ) { consistencyCheckers.add( new Getter( condition ) ); } return consistencyCheckers; } private List<Replacer> generateReplacers( AtomicBoolean condition ) { List<Replacer> replacers = new ArrayList<>(); for ( int i = 0; i < NUMBER_OF_REPLACES; i++ ) { replacers.add( new Replacer( condition ) ); } return replacers; } private class Replacer extends StopOnErrorCallable<Object> { Replacer( AtomicBoolean condition ) { super( condition ); } @Override public Object doCall() { for ( int i = 0; i < REPLACE_CIRCLES; i++ ) { sharedSlaveServer.replaceMeta( generateSlaveServer() ); } return null; } } private class Getter extends StopOnErrorCallable<Object> { Getter( AtomicBoolean condition ) { super( condition ); } @Override public Object doCall() { while ( condition.get() ) { checkConsistency( (SlaveServer) sharedSlaveServer.clone() ); } return null; } private void checkConsistency( SlaveServer slaveServer ) { String id = extractId( slaveServer.getName() ); assertEquals( id, extractId( slaveServer.getHostname() ) ); assertEquals( id, extractId( slaveServer.getPort() ) ); assertEquals( id, extractId( slaveServer.getWebAppName() ) ); assertEquals( id, extractId( slaveServer.getUsername() ) ); assertEquals( id, extractId( slaveServer.getPassword() ) ); assertEquals( id, extractId( slaveServer.getProxyHostname() ) ); assertEquals( id, extractId( slaveServer.getProxyPort() ) ); assertEquals( id, extractId( slaveServer.getNonProxyHosts() ) ); assertEquals( id, extractId( slaveServer.getObjectId().getId() ) ); } private String extractId( String string ) { return string.replaceAll( "\\D+", "" ); } } private SlaveServer generateSlaveServer() { Random random = new Random(); int id = random.nextInt(); SlaveServer slaveServer = new SlaveServer(); slaveServer.setName( NAME + id ); slaveServer.setHostname( HOST_NAME + id ); slaveServer.setPort( PORT + id ); slaveServer.setWebAppName( WEB_APP_NAME + id ); slaveServer.setUsername( USERNAME + id ); slaveServer.setPassword( PASSWORD + id ); slaveServer.setProxyHostname( PROXY_HOST_NAME + id ); slaveServer.setProxyPort( PROXY_PORT + id ); slaveServer.setNonProxyHosts( NON_PROXY_HOSTS + id ); slaveServer.setObjectId( new StringObjectId( ID + id ) ); return slaveServer; } }