/*
* RHQ Management Platform
* Copyright (C) 2005-2013 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.core.pc.inventory;
import static org.rhq.core.domain.measurement.AvailabilityType.UP;
import java.util.Date;
import java.util.Hashtable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.rhq.core.domain.measurement.Availability;
import org.rhq.core.domain.measurement.AvailabilityType;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.pluginapi.availability.AvailabilityFacet;
import org.rhq.core.pluginapi.inventory.ResourceComponent;
@Test
public class AvailabilityProxyConcurrencyTest implements AvailabilityFacet {
@BeforeMethod
@AfterMethod
public void clearInterrupts() {
// make sure we don't leave interrupt flag set, if somehow the test set it
Thread.interrupted();
}
private AtomicInteger numberOfFacetCalls = new AtomicInteger(-1);
public void testConcurrentAvailChecks() throws Exception {
Thread.interrupted(); // clear any hanging around interrupt status
final ExecutorService executor = Executors.newCachedThreadPool();
try {
// mock out the resource container so calls to the component get our impl of getAvailability()
ResourceComponent<?> resourceComponent = Mockito.mock(ResourceComponent.class);
Mockito.when(resourceComponent.getAvailability()).thenAnswer(new Answer<AvailabilityType>() {
public AvailabilityType answer(InvocationOnMock invocation) throws Throwable {
return AvailabilityProxyConcurrencyTest.this.getAvailability();
}
});
final ResourceContainer resourceContainer = Mockito.mock(ResourceContainer.class);
Mockito.when(resourceContainer.getResourceClassLoader()).thenReturn(getClass().getClassLoader());
Mockito.when(resourceContainer.getResourceComponent()).thenReturn(resourceComponent);
// our one proxy we want to call concurrently
final AvailabilityProxy ap = new TestAvailabilityProxy(resourceContainer);
// make sure our mock object uses our own thread pool when submitting the task
Mockito.when(resourceContainer.submitAvailabilityCheck(ap)).thenAnswer(
new Answer<Future<AvailabilityType>>() {
public Future<AvailabilityType> answer(InvocationOnMock invocation) throws Throwable {
return executor.submit(ap);
}
});
// prime the pump by getting the first avail synchronously
System.out.println("~~~AVAILABILITY PROXY CALL #" + 0 + " at " + new Date());
AvailabilityType firstAvail = ap.getAvailability();
assert UP.equals(firstAvail) : "Can't even get our first avail correctly: " + firstAvail;
Mockito.when(resourceContainer.getAvailability()).thenReturn(
new Availability(new Resource(1), AvailabilityType.UP)); // last avail is UP and will stay as UP
// make several calls to availProxy.getAvailability() in quick succession
final int numCalls = 15;
final Hashtable<String, AvailabilityType> availResults = new Hashtable<String, AvailabilityType>(numCalls);
final Hashtable<String, Date> dateResults = new Hashtable<String, Date>(numCalls);
final Hashtable<String, Throwable> throwableResults = new Hashtable<String, Throwable>(numCalls);
// this will count how many times the proxy actually calls the facet (i.e. component's) getAvail method
numberOfFacetCalls.set(0);
// release the hounds!
for (int i = 1; i <= numCalls; i++) {
try {
// space out the calls slightly to allow some async invocations to complete, giving us a mix of
// sync and async completions
try {
Thread.sleep(25);
} catch (InterruptedException e) {
//
}
System.out.println("~~~AVAILABILITY PROXY CALL #" + i + " at " + new Date());
AvailabilityType availCheck = ap.getAvailability();
// if the avail check is in progress, defer to our last known avail (which should be UP, due to
// our first call, and the simulating mock)
availCheck = (availCheck == AvailabilityType.UNKNOWN) ? resourceContainer.getAvailability()
.getAvailabilityType() : availCheck;
availResults.put("Call-" + i, availCheck);
} catch (Exception e) {
throwableResults.put(Thread.currentThread().getName(), e);
} finally {
dateResults.put("Call-" + i, new Date());
}
}
System.out.println("~~~THREADS FINISHED AT: " + new Date());
System.out.println("~~~THREAD FINISH TIMES: " + dateResults);
System.out.println("~~~THREADS WITH EXCEPTIONS: " + throwableResults);
// now make sure all of them returns UP
assert availResults.size() == numCalls : "Failed, bad threads: availResults = " + availResults;
for (AvailabilityType availtype : availResults.values()) {
assert availtype.equals(UP) : "Failed, bad avail: availResults = " + availResults;
}
// make sure we actually tested the code we need to test - we should not be making
// individual facet calls for each request because of the quick succession of calls
// and the facet sleeps. The proxy should return the last avail rather
// than requiring a new facet call, in some cases. The first 3 are always fast (see below
// impl of facet's getAvailability().
assert (numberOfFacetCalls.get()) > 3 : numberOfFacetCalls;
assert (numberOfFacetCalls.get()) < numCalls : numberOfFacetCalls;
} finally {
executor.shutdownNow();
}
}
@Override
public synchronized AvailabilityType getAvailability() {
final int facetCall = numberOfFacetCalls.incrementAndGet();
try {
System.out.println("~~~AVAILABILITY FACET CALL #" + facetCall + " at " + new Date());
// return quickly for the first request, we want it to establish a lastKnownAvail of UP
if (facetCall > 0) {
// make a few fast enough to complete synchronously and other need to finish async
Thread.sleep((0 == (facetCall % 3)) ? 400 : 10);
}
} catch (Exception e) {
System.out.println("~~~AVAILABILITY SLEEP WAS ABORTED FOR FACET CALL # " + facetCall + ": " + e);
}
return UP;
}
// for our test we want the sync avail check to occasionally time out - so set the timeout limit to a
// value that works with the call interval and the facet sleep values
private class TestAvailabilityProxy extends AvailabilityProxy {
public TestAvailabilityProxy(ResourceContainer rc) {
super(rc);
}
@Override
protected long getSyncTimeout() {
return 250L;
}
}
}