diff --git oak-pojosr/src/test/groovy/org/apache/jackrabbit/oak/run/osgi/AbstractRepositoryFactoryTest.groovy oak-pojosr/src/test/groovy/org/apache/jackrabbit/oak/run/osgi/AbstractRepositoryFactoryTest.groovy index 2c97f5b2f7..0441fbfb85 100644 --- oak-pojosr/src/test/groovy/org/apache/jackrabbit/oak/run/osgi/AbstractRepositoryFactoryTest.groovy +++ oak-pojosr/src/test/groovy/org/apache/jackrabbit/oak/run/osgi/AbstractRepositoryFactoryTest.groovy @@ -23,19 +23,22 @@ import org.apache.felix.connect.launch.PojoServiceRegistry import org.apache.commons.io.FilenameUtils import org.apache.jackrabbit.api.JackrabbitRepository import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.rules.TemporaryFolder import org.osgi.framework.BundleContext +import org.osgi.framework.ServiceEvent +import org.osgi.framework.ServiceListener import org.osgi.framework.ServiceReference import org.osgi.service.cm.ConfigurationAdmin import org.osgi.util.tracker.ServiceTracker import javax.jcr.* +import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import static org.apache.jackrabbit.oak.run.osgi.OakOSGiRepositoryFactory.REPOSITORY_HOME import static org.apache.jackrabbit.oak.run.osgi.OakOSGiRepositoryFactory.REPOSITORY_TIMEOUT_IN_SECS import static org.junit.Assert.fail abstract class AbstractRepositoryFactoryTest{ @@ -150,8 +153,74 @@ abstract class AbstractRepositoryFactoryTest{ Thread.sleep(intervalBetweenTriesMsec); } catch (InterruptedException ignore) { } } fail("RetryLoop failed, condition is false after " + timeoutSeconds + " seconds" + (message ?: (":" + message))); } + + /** + * Convenience method to be used in conjunction with {@link #awaitServiceEvent}. It creates a filter that matches + * the given {@code className} to one of the following properties: {@code objectClass}, {@code service.pid} or + * {@code service.factoryPid}. + * + * @param className The class name to match. + * @return The filter expression. + */ + protected static String classNameFilter(String className) { + return "(|(objectClass=${className})(service.pid=${className})(service.factoryPid=${className}))" + } + + /** + * Execute a change in a closure and wait for a ServiceEvent to happen. The method only returns once + * an appropriate event is matched. If no event matches within the specified timeout, an AssertionError + * is thrown. The error message describes any non-matching events that may have happened for debugging + * purposes. + * + * @param closure The closure that effects a change that should cause the expected ServiceEvent. + * @param serviceFilter A filter expression following the syntax of {@link org.osgi.framework.Filter} (default: (objectClass=*)) + * @param eventTypes An integer bitmap of accepted ServiceEvent types (default: any). + * @param timeout A timeout value; the maximum time to wait for the service event. The unit depends on the {@code timeUnit} argument. + * @param timeUnit The unit for the timeout value. + */ + protected void awaitServiceEvent( + Closure closure, + String serviceFilter = "(objectClass=*)", + int eventTypes = ServiceEvent.MODIFIED | ServiceEvent.REGISTERED | ServiceEvent.UNREGISTERING | ServiceEvent.MODIFIED_ENDMATCH, + long timeout = 1000, + TimeUnit timeUnit = TimeUnit.MILLISECONDS) { + def filter = registry.bundleContext.createFilter(serviceFilter) + def latch = new CountDownLatch(1) + def events = [] + def listener = new ServiceListener() { + @Override + void serviceChanged(final ServiceEvent event) { + events.add([eventType: event.type, serviceProperties: asMap(event.serviceReference)]) + if ((eventTypes & event.type) > 0 && filter.match(event.serviceReference)) { + latch.countDown() + } + } + + private static asMap(final ServiceReference serviceReference) { + def map = new HashMap() + serviceReference.getPropertyKeys().each { key -> + map.put(key, serviceReference.getProperty(key)) + } + return map + } + } + + try { + registry.addServiceListener(listener) + closure.run() + def start = System.nanoTime() + if (!latch.await(timeout, timeUnit)) { + throw new AssertionError("Exceeded timeout waiting for service event matching " + + "[eventTypes: ${eventTypes}, filter: ${serviceFilter}], " + + "got ${events.size()} non matching events: [${events}]") + } + println "awaitServiceEvent completed after ${TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)}ms" + } finally { + registry.removeServiceListener(listener) + } + } } diff --git oak-pojosr/src/test/groovy/org/apache/jackrabbit/oak/run/osgi/DocumentNodeStoreConfigTest.groovy oak-pojosr/src/test/groovy/org/apache/jackrabbit/oak/run/osgi/DocumentNodeStoreConfigTest.groovy index a853fd4b5f..b53dad7781 100644 --- oak-pojosr/src/test/groovy/org/apache/jackrabbit/oak/run/osgi/DocumentNodeStoreConfigTest.groovy +++ oak-pojosr/src/test/groovy/org/apache/jackrabbit/oak/run/osgi/DocumentNodeStoreConfigTest.groovy @@ -36,14 +36,15 @@ import org.apache.jackrabbit.oak.spi.commit.CommitInfo import org.apache.jackrabbit.oak.spi.commit.EmptyHook import org.apache.jackrabbit.oak.spi.state.NodeBuilder import org.apache.jackrabbit.oak.spi.state.NodeStore import org.h2.jdbcx.JdbcDataSource import org.junit.After import org.junit.Ignore import org.junit.Test +import org.osgi.framework.ServiceEvent import org.osgi.framework.ServiceReference import org.osgi.framework.ServiceRegistration import javax.sql.DataSource import java.sql.Connection import java.sql.ResultSet import java.util.concurrent.TimeUnit @@ -124,24 +125,26 @@ class DocumentNodeStoreConfigTest extends AbstractRepositoryFactoryTest { documentStoreType: 'RDB' ] ]) DocumentNodeStore ns = getServiceWithWait(NodeStore.class) //3. Shut down ds - srds.unregister(); - - // Check for service to be unregistered after at most 5s, retrying every 500ms. + // Wait for service to be unregistered after at most 5s. // Previously, we waited only 500ms; this was extended due to // occasional test failures on Jenkins (see OAK-5612). If 5s // are not sufficient, we should investigate some more. - retry (5, 500, "NodeStore should be unregistered") { - ServiceReference sr = registry.getServiceReference(NodeStore.class.name) - return sr == null - } + awaitServiceEvent({ + srds.unregister(); + }, + classNameFilter(NodeStore.class.name), + ServiceEvent.UNREGISTERING, + 5, TimeUnit.SECONDS + ) + assert registry.getServiceReference(NodeStore.class.name) == null //4. Restart ds, service should still be down srds = registry.registerService(DataSource.class.name, ds, ['datasource.name': 'oak'] as Hashtable) assertNoService(NodeStore.class) } @Test diff --git oak-pojosr/src/test/groovy/org/apache/jackrabbit/oak/run/osgi/SecurityProviderRegistrationTest.groovy oak-pojosr/src/test/groovy/org/apache/jackrabbit/oak/run/osgi/SecurityProviderRegistrationTest.groovy index f02280adcb..b240dfa0bd 100644 --- oak-pojosr/src/test/groovy/org/apache/jackrabbit/oak/run/osgi/SecurityProviderRegistrationTest.groovy +++ oak-pojosr/src/test/groovy/org/apache/jackrabbit/oak/run/osgi/SecurityProviderRegistrationTest.groovy @@ -30,17 +30,21 @@ import org.apache.jackrabbit.oak.spi.security.user.AuthorizableNodeName import org.apache.jackrabbit.oak.spi.security.user.UserAuthenticationFactory import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration import org.apache.jackrabbit.oak.spi.security.user.action.AccessControlAction import org.apache.jackrabbit.oak.spi.security.user.action.AuthorizableAction import org.apache.jackrabbit.oak.spi.security.user.action.AuthorizableActionProvider import org.junit.Before import org.junit.Test +import org.osgi.framework.Filter +import org.osgi.framework.ServiceEvent +import org.osgi.framework.ServiceListener import org.osgi.framework.ServiceReference import org.osgi.service.cm.ConfigurationAdmin +import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import static org.mockito.Mockito.mock import static org.mockito.Mockito.when class SecurityProviderRegistrationTest extends AbstractRepositoryFactoryTest { @@ -143,21 +147,24 @@ class SecurityProviderRegistrationTest extends AbstractRepositoryFactoryTest { * A SecurityProvider should be registered only if every every prerequisite * is satisfied. */ @Test public void testMultipleRequiredServices() { // Set up the SecurityProvider to require 4 services - - setRequiredServicePids( - "test.RequiredAuthorizationConfiguration", - "test.RequiredPrincipalConfiguration", - "test.RequiredTokenConfiguration", - "test.RestrictionProvider") - TimeUnit.MILLISECONDS.sleep(500) + awaitServiceEvent({ + setRequiredServicePids( + "test.RequiredAuthorizationConfiguration", + "test.RequiredPrincipalConfiguration", + "test.RequiredTokenConfiguration", + "test.RestrictionProvider") + }, + "(objectClass=org.apache.jackrabbit.oak.spi.security.SecurityProvider)", + ServiceEvent.UNREGISTERING + ) assert securityProviderServiceReferences == null // Start the services and verify that only at the end the // SecurityProvider registers itself def ac = mock(AuthorizationConfiguration) when(ac.getParameters()).thenReturn(ConfigurationParameters.EMPTY) when(ac.getContext()).thenReturn(Context.DEFAULT) @@ -197,31 +204,42 @@ class SecurityProviderRegistrationTest extends AbstractRepositoryFactoryTest { ScrService scr = getServiceWithWait(ScrService.class) Component[] c = scr.getComponents('org.apache.jackrabbit.oak.security.authentication.AuthenticationConfigurationImpl') assert c // 1. Disable AuthenticationConfiguration such that SecurityProvider is unregistered - c[0].disable() - - TimeUnit.SECONDS.sleep(1) + awaitServiceEvent({ + c[0].disable() + }, + '(objectClass=org.apache.jackrabbit.oak.spi.security.SecurityProvider)', + ServiceEvent.UNREGISTERING + ) assert securityProviderServiceReferences == null // 2. Modify the config for AuthorizableActionProvider. It's expected that this config change is picked up - setConfiguration([ - "org.apache.jackrabbit.oak.spi.security.user.action.DefaultAuthorizableActionProvider": [ - "groupPrivilegeNames":"jcr:read" - ] - ]) - - TimeUnit.SECONDS.sleep(1) + awaitServiceEvent({ + setConfiguration([ + "org.apache.jackrabbit.oak.spi.security.user.action.DefaultAuthorizableActionProvider": [ + "groupPrivilegeNames":"jcr:read" + ] + ]) + }, + classNameFilter('org.apache.jackrabbit.oak.spi.security.user.action.DefaultAuthorizableActionProvider'), + ServiceEvent.MODIFIED | ServiceEvent.REGISTERED + ) // 3. Enable component again such that SecurityProvider gets reactivated - c[0].enable() + awaitServiceEvent({ + c[0].enable() + }, + '(objectClass=org.apache.jackrabbit.oak.spi.security.SecurityProvider)', + ServiceEvent.REGISTERED + ) securityProvider = getServiceWithWait(SecurityProvider.class) assertAuthorizationConfig(securityProvider) assertUserConfig(securityProvider, "jcr:read") } @Test @@ -233,52 +251,68 @@ class SecurityProviderRegistrationTest extends AbstractRepositoryFactoryTest { //Keep a dummy reference to UserConfiguration such that SCR does not deactivate it //once SecurityProviderRegistration gets deactivated. Otherwise if SecurityProviderRegistration is //the only component that refers it then upon its deactivation UserConfiguration would also be //deactivate and its internal state would be reset UserConfiguration userConfiguration = getServiceWithWait(UserConfiguration.class) //1. Modify the config for AuthorizableActionProvider. It's expected that this config change is picked up - setConfiguration([ - "org.apache.jackrabbit.oak.spi.security.user.action.DefaultAuthorizableActionProvider": [ - "groupPrivilegeNames":"jcr:read" + def servicePid = "org.apache.jackrabbit.oak.spi.security.user.action.DefaultAuthorizableActionProvider" + awaitServiceEvent({ + setConfiguration([ + (servicePid): [ + "groupPrivilegeNames":"jcr:read" + ] ] - ]) - - TimeUnit.SECONDS.sleep(1) + )}, + "(service.pid=${servicePid})", + ServiceEvent.MODIFIED | ServiceEvent.REGISTERED + ) securityProvider = getServiceWithWait(SecurityProvider.class) assertAuthorizationConfig(securityProvider) assertUserConfig(securityProvider, "jcr:read") ScrService scr = getServiceWithWait(ScrService.class) Component[] c = scr.getComponents('org.apache.jackrabbit.oak.security.authentication.AuthenticationConfigurationImpl') assert c // 2. Disable AuthenticationConfiguration such that SecurityProvider is unregistered - c[0].disable() - - TimeUnit.SECONDS.sleep(1) + awaitServiceEvent({ + c[0].disable() + }, + "(objectClass=org.apache.jackrabbit.oak.spi.security.SecurityProvider)", + ServiceEvent.UNREGISTERING + ) assert securityProviderServiceReferences == null // 3. Enable component again such that SecurityProvider gets reactivated - c[0].enable() + awaitServiceEvent({ + c[0].enable() + }, + "(objectClass=org.apache.jackrabbit.oak.spi.security.SecurityProvider)", + ServiceEvent.REGISTERED + ) securityProvider = getServiceWithWait(SecurityProvider.class) assertAuthorizationConfig(securityProvider) assertUserConfig(securityProvider, "jcr:read") } private void testRequiredService(Class serviceClass, T service) { // Adding a new precondition on a missing service PID forces the // SecurityProvider to unregister. - setRequiredServicePids("test.Required" + serviceClass.simpleName) - TimeUnit.MILLISECONDS.sleep(500) + awaitServiceEvent({ + setRequiredServicePids("test.Required" + serviceClass.simpleName) + }, + "(objectClass=org.apache.jackrabbit.oak.spi.security.SecurityProvider)", + ServiceEvent.UNREGISTERING + ) assert securityProviderServiceReferences == null // If a service is registered, and if the PID of the service matches the // precondition, the SecurityProvider is registered again. def registration = registry.registerService(serviceClass.name, service, dict("service.pid": "test.Required" + serviceClass.simpleName)) assert securityProviderServiceReferences != null @@ -286,17 +320,20 @@ class SecurityProviderRegistrationTest extends AbstractRepositoryFactoryTest { // If the service is unregistered, but the precondition is still in // place, the SecurityProvider unregisters again. registration.unregister() assert securityProviderServiceReferences == null // Removing the precondition allows the SecurityProvider to register. - - setRequiredServicePids() - TimeUnit.MILLISECONDS.sleep(500) + awaitServiceEvent({ + setRequiredServicePids() + }, + "(objectClass=org.apache.jackrabbit.oak.spi.security.SecurityProvider)", + ServiceEvent.REGISTERED + ) assert securityProviderServiceReferences != null } private ServiceReference[] getSecurityProviderServiceReferences() { return registry.getServiceReferences(SecurityProvider.class.name, "(type=default)") }