package alma.acs.nc; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.junit.Assert.assertThat; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import org.omg.CORBA.portable.IDLEntity; import org.omg.CosNotifyChannelAdmin.AdminNotFound; import org.omg.CosNotifyChannelAdmin.ConsumerAdmin; import org.omg.CosNotifyChannelAdmin.ProxySupplier; import org.omg.CosNotifyChannelAdmin.ProxyType; import gov.sandia.NotifyMonitoringExt.EventChannel; import alma.acs.component.client.ComponentClientTestCase; import alma.acs.concurrent.ThreadBurstExecutorService; import alma.acs.nc.AcsEventSubscriber; import alma.acs.nc.NCSubscriber; import alma.acs.nc.Helper; import alma.acscommon.NC_KIND; public class NCSubscriberAdminReuseTest extends ComponentClientTestCase { private static String CHANNEL_NAME = "bob-dylan"; private EventChannel channel; public NCSubscriberAdminReuseTest() throws Exception { super(NCSubscriberAdminReuseTest.class.getSimpleName()); } public void setUp() throws Exception { super.setUp(); m_logger.info("------------ setUp " + getName() + " --------------"); Helper helper = new Helper(CHANNEL_NAME, getContainerServices(), Helper.getNamingServiceInitial(getContainerServices())); channel = helper.getNotificationChannel(helper.getNotificationFactoryNameForChannel()); assertNotNull(channel); assertEquals(0, channel.get_all_consumeradmins().length); } public void tearDown() throws Exception { m_logger.info("------------ tearDown " + getName() + " --------------"); // Make sure we don't have any admin for the next round destroyConsumers(); super.tearDown(); } private void destroyConsumers() throws AdminNotFound { int[] adminIDs = channel.get_all_consumeradmins(); if (adminIDs.length > 0) { m_logger.info("Will destroy all " + adminIDs.length + " consumer admin objects..."); for(int adminID : adminIDs) { channel.get_consumeradmin(adminID).destroy(); } m_logger.info("Done destroying all consumer admin objects."); } else { m_logger.info("There are no consumer admin objects, nothing to destroy."); } } public void testSharedAdminReuse() throws Exception { final int intendedNumberOfAdminObjects = 10; List<AcsEventSubscriber<IDLEntity>> subscriberList = new ArrayList<AcsEventSubscriber<IDLEntity>>(); for(int adminCount = 1; adminCount <= intendedNumberOfAdminObjects; adminCount++) { // Create the maximum number of proxies per admin for(int j=0; j!=NCSubscriber.PROXIES_PER_ADMIN; j++) { subscriberList.add(getContainerServices().createNotificationChannelSubscriber(CHANNEL_NAME, IDLEntity.class)); } // verify that all "j loop" subscribers caused only the automatic creation of one admin object assertEquals(adminCount, channel.get_all_consumeradmins().length); } m_logger.info("Created " + subscriberList.size() + " subscribers for channel '" + CHANNEL_NAME); // Now, all admins should be full of proxies (-1 because of the dummy proxy). // There should not be any fluctuation in the number of proxies per admin as we have it in #testConcurrentSubscribersCreation // because here we create all subscribers sequentially. for(int adminID : channel.get_all_consumeradmins()) { assertEquals(NCSubscriber.PROXIES_PER_ADMIN, channel.get_consumeradmin(adminID).push_suppliers().length - 1); } m_logger.info("Verified that each admin object has " + NCSubscriber.PROXIES_PER_ADMIN + " subscribers. Will now disconnect the subscibers..."); // disconnect all subscribers, which should remove the proxies from the admin objects int disconnectCount=1; for(AcsEventSubscriber<IDLEntity> subscriber : subscriberList) { subscriber.disconnect(); m_logger.info("Disconnected subscriber #" + disconnectCount++); } m_logger.info("All subscribers are disconnected. Will now verify this on the server-side admin objects..."); // Now, all consumer admins should have 0 proxies (+1, the dummy proxy) int[] adminIDs = channel.get_all_consumeradmins(); assertEquals("Currently we do not release admin objects, which means that there should still be " + intendedNumberOfAdminObjects + " of them, even when empty.", intendedNumberOfAdminObjects, adminIDs.length); int adminCount=1; for(int adminID : channel.get_all_consumeradmins()) { m_logger.info("About to check the supplier proxies on admin #" + adminCount); assertEquals(1, channel.get_consumeradmin(adminID).push_suppliers().length); adminCount++; } } /** * TODO: Write a similar test with an old-style C++ Consumer, * once we remove the deprecated Java NC Consumer. */ public void testNewAndOldNCsTogether() throws Exception { List<Consumer> consumers = new ArrayList<Consumer>(); for(int i=1; i<=10; i++) { // Create the maximum number of proxies per admin // Also, per every NCSubscriber, create an old Consumer AcsEventSubscriber[] subscribers = new AcsEventSubscriber[NCSubscriber.PROXIES_PER_ADMIN]; for(int j=0; j!=NCSubscriber.PROXIES_PER_ADMIN; j++) { subscribers[j] = getContainerServices().createNotificationChannelSubscriber(CHANNEL_NAME, IDLEntity.class); Consumer c = new Consumer(CHANNEL_NAME, getContainerServices()); consumers.add(c); } assertEquals(i*(1 + NCSubscriber.PROXIES_PER_ADMIN), channel.get_all_consumeradmins().length); } // Now, let's examine the consumer admins, and see whether they are shared or not int sharedAdmins = 0; int lonelyAdmins = 0; for(int adminID: channel.get_all_consumeradmins()) { ConsumerAdmin admin = channel.get_consumeradmin(adminID); boolean isSharedAdmin = false; for(int proxyID: admin.push_suppliers()) { ProxySupplier proxy = admin.get_proxy_supplier(proxyID); if(ProxyType.PUSH_ANY.equals(proxy.MyType())) { isSharedAdmin = true; break; } } if( isSharedAdmin ) { assertEquals(NCSubscriber.PROXIES_PER_ADMIN, admin.push_suppliers().length - 1); sharedAdmins++; } else lonelyAdmins++; } assertEquals(10, sharedAdmins); assertEquals(10*NCSubscriber.PROXIES_PER_ADMIN, lonelyAdmins); // Manually free these old filthy consumers for(Consumer c: consumers) c.disconnect(); } public void testConcurrentSubscribersCreation() throws Exception { // We test the concurrent creation of subscribers with different loads runConcurrentSubscribersCreation(10); runConcurrentSubscribersCreation(15); runConcurrentSubscribersCreation(53); runConcurrentSubscribersCreation(42); } private void runConcurrentSubscribersCreation(int numRealSubscribersDefinedTotal) throws Exception { m_logger.info("Setting up " + numRealSubscribersDefinedTotal + " concurrent subscriber creations..."); final List<AcsEventSubscriber<IDLEntity>> subscribers = Collections.synchronizedList( new ArrayList<AcsEventSubscriber<IDLEntity>>()); // Create all the tasks first ThreadBurstExecutorService executor = new ThreadBurstExecutorService(getContainerServices().getThreadFactory()); for (int i=0; i<numRealSubscribersDefinedTotal; i++) { Runnable r = new Runnable() { public void run() { try { // create subscriber, and add it to the list subscribers.add( getContainerServices().createNotificationChannelSubscriber(CHANNEL_NAME, IDLEntity.class) ); } catch (Exception e) { m_logger.log(Level.WARNING, "Failed to create a subscriber.", e); } } }; try { executor.submit(r, 100, TimeUnit.SECONDS); } catch (InterruptedException e1) { fail("Failed to submit the subscriber creator thread to the executor service"); } } // and now run'em all at the same time! (concurrently) m_logger.info("Will run " + numRealSubscribersDefinedTotal + " concurrent subscriber creations..."); try { boolean startOK = executor.executeAllAndWait(100, TimeUnit.SECONDS); assertTrue("Not all subscribers started within the alotted 100 seconds window.", startOK); } catch (InterruptedException e) { fail("Got InterruptedException while running all my threads"); } // After all the show, we should have all requested subscribers in the local list assertEquals(numRealSubscribersDefinedTotal, subscribers.size()); m_logger.info("Successfully created " + numRealSubscribersDefinedTotal + " subscribers semi-concurrently. Will now check their NC admin links..."); // Check if these subscribers are distributed correctly over several admin objects, // allowing for some overbooking due to concurrent requests (see comment about concurrency in c'tor of NCSubscriber) final int allowedAdminOverbooking = 2; int numRealSubscribersFoundTotal = 0; int[] adminIDs = channel.get_all_consumeradmins(); for(int i=0; i < adminIDs.length; i++) { int adminID = adminIDs[i]; String msgBase = "Subscriber admin #" + (i+1) + " (of " + adminIDs.length + ") "; try { int subs = channel.get_consumeradmin(adminID).push_suppliers().length; int numRealSubscribersThisAdmin = subs - 1; // minus 1 for the 'management' subscriber m_logger.info(msgBase + "has " + numRealSubscribersThisAdmin + " proxy objects (subscribers) attached."); if (i < adminIDs.length - 1) { // This is not the last of the admin objects. It should be filled to the brim with proxies. assertThat(msgBase + "should be full with " + NCSubscriber.PROXIES_PER_ADMIN + " proxies.", numRealSubscribersThisAdmin, greaterThanOrEqualTo(NCSubscriber.PROXIES_PER_ADMIN) ); assertThat(msgBase + "has more proxies than allowed by 'allowedAdminOverbooking'=" + allowedAdminOverbooking + ", which may be OK.", numRealSubscribersThisAdmin, lessThanOrEqualTo(NCSubscriber.PROXIES_PER_ADMIN + allowedAdminOverbooking) ); } else { // We should be at the last of the admin objects, which may be only partially filled assertThat(msgBase + "has more proxies than allowed by 'allowedAdminOverbooking'=" + allowedAdminOverbooking + ", which may be OK.", numRealSubscribersThisAdmin, lessThanOrEqualTo(NCSubscriber.PROXIES_PER_ADMIN + allowedAdminOverbooking) ); assertThat(msgBase + "should contain all remaining proxies.", numRealSubscribersThisAdmin, equalTo(numRealSubscribersDefinedTotal - numRealSubscribersFoundTotal) ); } numRealSubscribersFoundTotal += numRealSubscribersThisAdmin; } catch (AdminNotFound ex) { fail("Can't get information about consumer admin #" + (i+1) + " (ID=" + adminID + "): " + ex.toString()); } } destroyConsumers(); } }