/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.activemq.artemis.core.server.group.impl;
import javax.management.ObjectName;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.activemq.artemis.api.core.BroadcastGroupConfiguration;
import org.apache.activemq.artemis.api.core.ICoreMessage;
import org.apache.activemq.artemis.api.core.Message;
import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.api.core.TransportConfiguration;
import org.apache.activemq.artemis.api.core.management.ManagementHelper;
import org.apache.activemq.artemis.api.core.management.ObjectNameBuilder;
import org.apache.activemq.artemis.core.config.BridgeConfiguration;
import org.apache.activemq.artemis.core.config.ClusterConnectionConfiguration;
import org.apache.activemq.artemis.core.config.Configuration;
import org.apache.activemq.artemis.core.config.DivertConfiguration;
import org.apache.activemq.artemis.core.management.impl.ActiveMQServerControlImpl;
import org.apache.activemq.artemis.core.messagecounter.MessageCounterManager;
import org.apache.activemq.artemis.core.paging.PagingManager;
import org.apache.activemq.artemis.core.persistence.StorageManager;
import org.apache.activemq.artemis.core.postoffice.PostOffice;
import org.apache.activemq.artemis.core.remoting.server.RemotingService;
import org.apache.activemq.artemis.core.security.Role;
import org.apache.activemq.artemis.core.security.SecurityStore;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.Divert;
import org.apache.activemq.artemis.core.server.Queue;
import org.apache.activemq.artemis.core.server.QueueFactory;
import org.apache.activemq.artemis.core.server.cluster.Bridge;
import org.apache.activemq.artemis.core.server.cluster.BroadcastGroup;
import org.apache.activemq.artemis.core.server.cluster.ClusterConnection;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
import org.apache.activemq.artemis.core.server.management.ManagementService;
import org.apache.activemq.artemis.core.server.management.Notification;
import org.apache.activemq.artemis.core.server.management.NotificationListener;
import org.apache.activemq.artemis.core.settings.HierarchicalRepository;
import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
import org.apache.activemq.artemis.core.transaction.ResourceManager;
import org.apache.activemq.artemis.spi.core.remoting.Acceptor;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.apache.activemq.artemis.utils.ReusableLatch;
import org.apache.activemq.artemis.utils.collections.ConcurrentHashSet;
import org.junit.Assert;
import org.junit.Test;
/**
* this is testing the case for resending notifications from RemotingGroupHandler
* There is a small window where you could receive notifications wrongly
* this test will make sure the component would play well with that notification
*/
public class ClusteredResetMockTest extends ActiveMQTestBase {
public static final SimpleString ANYCLUSTER = SimpleString.toSimpleString("anycluster");
@Test
public void testMultipleSenders() throws Throwable {
int NUMBER_OF_SENDERS = 100;
ReusableLatch latchSends = new ReusableLatch(NUMBER_OF_SENDERS);
FakeManagement fake = new FakeManagement(latchSends);
RemoteGroupingHandler handler = new RemoteGroupingHandler(fake, SimpleString.toSimpleString("tst1"), SimpleString.toSimpleString("tst2"), 50000, 499);
handler.start();
Sender[] sn = new Sender[NUMBER_OF_SENDERS];
for (int i = 0; i < sn.length; i++) {
sn[i] = new Sender("grp" + i, handler);
sn[i].start();
}
try {
// Waiting two requests to arrive
Assert.assertTrue(latchSends.await(1, TimeUnit.MINUTES));
// we will ask a resend.. need to add 2 back
for (int i = 0; i < NUMBER_OF_SENDERS; i++) {
// There is no countUp(NUMBER_OF_SENDERS); adding two back on the reusable latch
latchSends.countUp();
}
fake.pendingNotifications.clear();
handler.resendPending();
assertTrue(latchSends.await(10, TimeUnit.SECONDS));
HashSet<SimpleString> codesAsked = new HashSet<>();
for (Notification notification : fake.pendingNotifications) {
codesAsked.add(notification.getProperties().getSimpleStringProperty(ManagementHelper.HDR_PROPOSAL_GROUP_ID));
}
for (Sender snItem : sn) {
assertTrue(codesAsked.contains(snItem.code));
}
for (int i = NUMBER_OF_SENDERS - 1; i >= 0; i--) {
// Sending back the response as Notifications would be doing
Response response = new Response(sn[i].code, ANYCLUSTER);
handler.proposed(response);
}
for (Sender sni : sn) {
sni.join();
if (sni.ex != null) {
throw sni.ex;
}
}
} finally {
for (Sender sni : sn) {
sni.interrupt();
}
}
}
class Sender extends Thread {
SimpleString code;
public RemoteGroupingHandler handler;
Throwable ex;
Sender(String code, RemoteGroupingHandler handler) {
super("Sender::" + code);
this.code = SimpleString.toSimpleString(code);
this.handler = handler;
}
@Override
public void run() {
Proposal proposal = new Proposal(code, ANYCLUSTER);
try {
Response response = handler.propose(proposal);
if (response == null) {
ex = new NullPointerException("expected value on " + getName());
} else if (!response.getGroupId().equals(code)) {
ex = new IllegalStateException("expected code=" + code + " but it was " + response.getGroupId());
}
} catch (Throwable ex) {
ex.printStackTrace();
this.ex = ex;
}
}
}
class FakeManagement implements ManagementService {
public ConcurrentHashSet<Notification> pendingNotifications = new ConcurrentHashSet<>();
final ReusableLatch latch;
FakeManagement(ReusableLatch latch) {
this.latch = latch;
}
@Override
public MessageCounterManager getMessageCounterManager() {
return null;
}
@Override
public SimpleString getManagementAddress() {
return null;
}
@Override
public SimpleString getManagementNotificationAddress() {
return null;
}
@Override
public ObjectNameBuilder getObjectNameBuilder() {
return null;
}
@Override
public void setStorageManager(StorageManager storageManager) {
}
@Override
public ActiveMQServerControlImpl registerServer(PostOffice postOffice,
SecurityStore securityStore,
StorageManager storageManager,
Configuration configuration,
HierarchicalRepository<AddressSettings> addressSettingsRepository,
HierarchicalRepository<Set<Role>> securityRepository,
ResourceManager resourceManager,
RemotingService remotingService,
ActiveMQServer messagingServer,
QueueFactory queueFactory,
ScheduledExecutorService scheduledThreadPool,
PagingManager pagingManager,
boolean backup) throws Exception {
return null;
}
@Override
public void unregisterServer() throws Exception {
}
@Override
public void registerInJMX(ObjectName objectName, Object managedResource) throws Exception {
}
@Override
public void unregisterFromJMX(ObjectName objectName) throws Exception {
}
@Override
public void registerInRegistry(String resourceName, Object managedResource) {
}
@Override
public void unregisterFromRegistry(String resourceName) {
}
@Override
public void registerAddress(AddressInfo addressInfo) throws Exception {
}
@Override
public void unregisterAddress(SimpleString address) throws Exception {
}
@Override
public void registerQueue(Queue queue, SimpleString address, StorageManager storageManager) throws Exception {
}
@Override
public void unregisterQueue(SimpleString name, SimpleString address, RoutingType routingType) throws Exception {
}
@Override
public void registerAcceptor(Acceptor acceptor, TransportConfiguration configuration) throws Exception {
}
@Override
public void unregisterAcceptors() {
}
@Override
public void registerDivert(Divert divert, DivertConfiguration config) throws Exception {
}
@Override
public void unregisterDivert(SimpleString name, SimpleString address) throws Exception {
}
@Override
public void registerBroadcastGroup(BroadcastGroup broadcastGroup,
BroadcastGroupConfiguration configuration) throws Exception {
}
@Override
public void unregisterBroadcastGroup(String name) throws Exception {
}
@Override
public void registerBridge(Bridge bridge, BridgeConfiguration configuration) throws Exception {
}
@Override
public void unregisterBridge(String name) throws Exception {
}
@Override
public void registerCluster(ClusterConnection cluster,
ClusterConnectionConfiguration configuration) throws Exception {
}
@Override
public void unregisterCluster(String name) throws Exception {
}
@Override
public Object getResource(String resourceName) {
return null;
}
@Override
public Object[] getResources(Class<?> resourceType) {
return new Object[0];
}
@Override
public ICoreMessage handleMessage(Message message) throws Exception {
return null;
}
@Override
public void start() throws Exception {
}
@Override
public void stop() throws Exception {
}
@Override
public boolean isStarted() {
return false;
}
@Override
public void sendNotification(Notification notification) throws Exception {
pendingNotifications.add(notification);
latch.countDown();
}
@Override
public void enableNotifications(boolean enable) {
}
@Override
public void addNotificationListener(NotificationListener listener) {
}
@Override
public void removeNotificationListener(NotificationListener listener) {
}
}
}