/***************************************************************************
* Copyright (c) 2013 VMware, Inc. All Rights Reserved.
* 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 com.vmware.vhadoop.vhm;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import junit.framework.AssertionFailedError;
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.vmware.vhadoop.api.vhm.ClusterMap;
import com.vmware.vhadoop.api.vhm.ClusterMapReader;
public class ClusterMapAccessTest {
StandaloneSimpleClusterMap _clusterMap;
MultipleReaderSingleWriterClusterMapAccess _clusterMapAccess;
List<Thread> _liveThreads = new ArrayList<Thread>();
@Before
public void initialize() {
_clusterMap = new StandaloneSimpleClusterMap(false);
_clusterMapAccess = MultipleReaderSingleWriterClusterMapAccess.getClusterMapAccess(_clusterMap);
}
@After
public void destroy() {
MultipleReaderSingleWriterClusterMapAccess.destroy();
}
@Test
public void DoubleLockTest() {
ClusterMap cm = _clusterMapAccess.lockClusterMap();
assertNotNull(cm);
cm = _clusterMapAccess.lockClusterMap();
assertNull(cm);
_clusterMapAccess.unlockClusterMap(cm);
}
@Test
public void DoubleUnLockTest() {
ClusterMap cm = _clusterMapAccess.lockClusterMap();
assertNotNull(cm);
boolean result = _clusterMapAccess.unlockClusterMap(cm);
assertTrue(result);
result = _clusterMapAccess.unlockClusterMap(cm);
assertFalse(result);
}
class TestClusterMapReader extends AbstractClusterMapReader {
public int getNumPoweredOffVMs(long delayMillis) {
ClusterMap cm = null;
try {
cm = getAndReadLockClusterMap();
Thread.sleep(delayMillis);
Set<String> vmIds = cm.listComputeVMsForClusterAndPowerState("myCluster", false);
return (vmIds == null) ? 0 : vmIds.size();
} catch (InterruptedException e) {
} finally {
unlockClusterMap(cm);
}
return 0;
}
}
private AtomicInteger startReaders(final List<TestClusterMapReader> readers, final long readDelayMillis, final int assertResult) {
final AtomicInteger numStarted = new AtomicInteger();
for (final TestClusterMapReader reader : readers) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
numStarted.incrementAndGet();
System.out.println(System.currentTimeMillis()+": Reader trying to read");
assertEquals(assertResult, reader.getNumPoweredOffVMs(readDelayMillis));
System.out.println(System.currentTimeMillis()+": Reader done reading");
}});
_liveThreads.add(t);
t.start();
}
return numStarted;
}
private AtomicInteger startWriter(final long writeDelayMillis) {
final AtomicInteger writing = new AtomicInteger();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
_clusterMapAccess.runCodeInWriteLock(new Callable<Object>() {
@Override
public Object call() throws Exception {
writing.incrementAndGet();
System.out.println(System.currentTimeMillis()+": Writer started writing. New Readers should be blocked for "+writeDelayMillis+"ms");
Thread.sleep(writeDelayMillis);
_clusterMap.addVMToMap("myVm3", "myCluster", "myHost", false);
return false;
}
});
} catch (Exception e) {
throw new AssertionFailedError("Error updating ClusterMap");
}
}});
_liveThreads.add(t);
t.start();
return writing;
}
private void blockUntilCompletion() {
int aliveCount = _liveThreads.size();
while (aliveCount > 0) {
aliveCount = 0;
try {
for (Thread t : _liveThreads) {
if (t.isAlive()) aliveCount++;
}
Thread.sleep(10);
} catch (InterruptedException e) {}
}
}
private void blockUntilIntegerEquals(AtomicInteger value, int expected) {
while (value.get() != expected) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {}
}
}
@Test
public void testCorrectBehavior() {
ClusterMapReader parent = new AbstractClusterMapReader(_clusterMapAccess, null) {};
/* Create a clustermap with 2 powered off VMs */
_clusterMap.addVMToMap("myVm1", "myCluster", "myHost", false);
_clusterMap.addVMToMap("myVm2", "myCluster", "myHost", false);
/* Create 3 cluster map readers */
final List<TestClusterMapReader> readers = new ArrayList<TestClusterMapReader>();
for (int i=0; i<3; i++) {
TestClusterMapReader reader = new TestClusterMapReader();
readers.add(reader);
reader.initialize(parent);
}
long readDelayMillis = 200;
long writeDelayMillis = 1000;
/* Start the 3 readers in 3 threads. Once numStarted == 3, the reader threads have started and will then delay for the requested time */
AtomicInteger numStarted = startReaders(readers, readDelayMillis, 2);
blockUntilIntegerEquals(numStarted, 3);
System.out.println(System.currentTimeMillis()+": Readers started reading. Writer should be blocked for "+readDelayMillis+"ms");
/* The writer thread is started while the readers are still reading, so should be prevented from writing until the readers have completed */
AtomicInteger writing = startWriter(writeDelayMillis);
blockUntilIntegerEquals(writing, 1);
/* The line above blocks until the writer starts writing, at which point we start 3 new reader threads which all assert that they see the updated value */
startReaders(readers, 0, 3);
/* Wait until all threads have completed */
blockUntilCompletion();
}
}