/* * Hibernate Search, full-text search for your domain model * * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. */ package org.hibernate.search.test.jgroups.common; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; import org.apache.lucene.queryparser.classic.ParseException; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.Query; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.search.FullTextQuery; import org.hibernate.search.FullTextSession; import org.hibernate.search.Search; import org.hibernate.search.backend.jgroups.impl.DispatchMessageSender; import org.hibernate.search.backend.jgroups.impl.NodeSelectorService; import org.hibernate.search.backend.jgroups.impl.NodeSelectorStrategy; import org.hibernate.search.engine.integration.impl.ExtendedSearchIntegrator; import org.hibernate.search.engine.service.spi.ServiceReference; import org.hibernate.search.test.DefaultTestResourceManager; import org.hibernate.search.test.TestResourceManager; import org.hibernate.search.test.jgroups.master.TShirt; import org.hibernate.search.testsupport.TestConstants; import org.hibernate.search.testsupport.TestForIssue; import org.hibernate.search.testsupport.concurrency.Poller; import org.junit.Assert; import org.junit.Test; /** * @author Lukasz Moren * @author Sanne Grinovero */ @TestForIssue(jiraKey = "HSEARCH-2675") public class JGroupsDynamicMasterElectionTest extends DynamicMasterSlaveSearchTestCase { public static final String TESTING_JGROUPS_CONFIGURATION_FILE = "testing-flush-loopback.xml"; public static final Poller POLLER = Poller.milliseconds( 10_000, 100 ); /* * Must be at least 3 so as to highlight the bug mentioned in HSEARCH-2675: * for some reason, the master will change automatically when we spawn the second * node, so the first two nodes will always be able to handle master works. * The third one and later will not, however. */ private static final int DEFAULT_NUMBER_OF_NODES = 10; /** * Name of the JGroups channel used in test */ public static final String CHANNEL_NAME = UUID.randomUUID().toString(); private final QueryParser parser = new QueryParser( "id", TestConstants.stopAnalyzer ); @Override protected int getExpectedNumberOfNodes() { return DEFAULT_NUMBER_OF_NODES; } @Test public void masterElection() throws Exception { TestResourceManager masterResourceManager = determineJGroupsMaster().get(); List<DefaultTestResourceManager> slaveResourceManagers = determineJGroupsSlaves(); Assert.assertEquals( getExpectedNumberOfNodes() - 1, slaveResourceManagers.size() ); // Check that the first master works fine TShirt ts = new TShirt(); ts.setLogo( "Boston" ); ts.setSize( "XXL" ); ts.setLength( 23.4d ); testAdd( masterResourceManager, slaveResourceManagers, ts, 1 ); // Kill the master masterResourceManager.getSessionFactory().close(); // ... check that a new master is elected POLLER.pollAssertion( () -> { Assert.assertTrue( "Lots of time waited and still no new master has been elected!", determineJGroupsMaster().isPresent() ); } ); masterResourceManager = determineJGroupsMaster().get(); slaveResourceManagers = determineJGroupsSlaves(); Assert.assertEquals( getExpectedNumberOfNodes() - 2, slaveResourceManagers.size() ); // ... and check that the new master actually performs work TShirt ts2 = new TShirt(); ts2.setLogo( "Mapple leaves" ); ts2.setSize( "L" ); ts2.setLength( 23.42d ); testAdd( masterResourceManager, slaveResourceManagers, ts2, 2 ); } private void testAdd(TestResourceManager masterResourceManager, List<DefaultTestResourceManager> slaveResourceManagers, TShirt ts, int expectedResults) throws ParseException { try ( Session slaveSession = slaveResourceManagers.get( 0 ).openSession() ) { Transaction tx = slaveSession.beginTransaction(); slaveSession.persist( ts ); tx.commit(); try ( Session masterSession = masterResourceManager.openSession() ) { // since this is an async backend, we expect to see // the values in the index *eventually*. POLLER.pollAssertion( () -> { List<?> result = doQuery( masterSession ); Assert.assertEquals( "Lots of time waited and still the document is not indexed on master yet!", expectedResults, result.size() ); } ); } } // Wait for the changes to be visible from the slaves POLLER.pollAssertion( () -> { for ( TestResourceManager resourceManager : slaveResourceManagers ) { try ( Session slaveSession = resourceManager.openSession() ) { List<?> result = doQuery( slaveSession ); Assert.assertEquals( "Lots of time waited and still the document is not visible from the slave yet!", expectedResults, result.size() ); } } } ); } private List<?> doQuery(Session slaveSession) throws ParseException { FullTextSession ftSession = Search.getFullTextSession( slaveSession ); Query luceneQuery = parser.parse( "logo:Boston or logo:Mapple leaves" ); slaveSession.getTransaction().begin(); FullTextQuery query = ftSession.createFullTextQuery( luceneQuery ); List<?> result = query.list(); slaveSession.getTransaction().commit(); return result; } @Override public void configure(Map<String,Object> cfg) { //master jgroups configuration super.configure( cfg ); cfg.put( "hibernate.search.default.retry_initialize_period", "1" ); cfg.put( "hibernate.search.default." + DispatchMessageSender.CLUSTER_NAME, CHANNEL_NAME ); cfg.put( DispatchMessageSender.CONFIGURATION_FILE, TESTING_JGROUPS_CONFIGURATION_FILE ); /* * Do *not* drop the schema upon factory closing, or the slave won't be able to use it. */ cfg.put( org.hibernate.cfg.Environment.HBM2DDL_AUTO, "drop-and-create" ); } @Override public Class<?>[] getAnnotatedClasses() { return new Class[] { TShirt.class }; } private boolean isActive(TestResourceManager manager) { return !manager.getSessionFactory().isClosed(); } private boolean isJGroupsMaster(TestResourceManager manager) { ExtendedSearchIntegrator integrator = manager.getExtendedSearchIntegrator(); try ( ServiceReference<NodeSelectorService> service = integrator.getServiceManager().requestReference( NodeSelectorService.class ) ) { NodeSelectorStrategy nodeSelector = service.get().getMasterNodeSelector( TShirt.INDEX_NAME ); return nodeSelector.isIndexOwnerLocal(); } } private Optional<DefaultTestResourceManager> determineJGroupsMaster() { return getResourceManagers().stream() .filter( this::isActive ) .filter( this::isJGroupsMaster ) .findFirst(); } private List<DefaultTestResourceManager> determineJGroupsSlaves() { return getResourceManagers().stream() .filter( this::isActive ) .filter( (manager) -> !isJGroupsMaster(manager) ) .collect( Collectors.toList() ); } }