/** * 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.hadoop.hdfs.notifier.server; import java.util.HashSet; import java.util.LinkedList; import java.util.Queue; import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; import junit.framework.Assert; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hdfs.MiniDFSCluster; import org.apache.hadoop.hdfs.notifier.EventType; import org.apache.hadoop.hdfs.notifier.InvalidServerIdException; import org.apache.hadoop.hdfs.notifier.NamespaceEvent; import org.apache.hadoop.hdfs.notifier.NamespaceEventKey; import org.apache.hadoop.hdfs.notifier.NamespaceNotification; import org.apache.thrift.TException; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TestServerDispatcher { static private Logger LOG = LoggerFactory.getLogger(TestServerDispatcher.class); static Configuration conf; @BeforeClass public static void initConf() { conf = NotifierTestUtil.initGenericConf(); } @Test public void testBasicDispatch() throws Exception { DummyServerCore core = new DummyServerCore(); ServerDispatcher dispatcher = new ServerDispatcher(core); long clientId = 1000; DummyClientHandler handler = new DummyClientHandler(); NamespaceEvent event = new NamespaceEvent("/a", EventType.FILE_ADDED.getByteValue()); Set<Long> subscriptions = new HashSet<Long>(); Thread dispatcherThread = new Thread(dispatcher); // Add only one client with one subscription subscriptions.add(clientId); core.clientQueues.put(clientId, new ConcurrentLinkedQueue<NamespaceNotification>()); core.subscriptions.put(new NamespaceEventKey(event), subscriptions); core.clients.put(clientId, new ClientData(clientId, handler, "host", 3000)); dispatcher.loopSleepTime = 20; dispatcher.assignClient(clientId); // Add a notification and wait for it to be delivered dispatcherThread.start(); core.clientQueues.get(clientId).add(new NamespaceNotification("/a/b", EventType.FILE_ADDED.getByteValue(), 10)); Thread.sleep(250); core.shutdown(); dispatcherThread.join(); // Check what was received NamespaceNotification n; Assert.assertEquals(1, handler.notificationQueue.size()); n = handler.notificationQueue.element(); Assert.assertEquals("/a/b", n.path); Assert.assertEquals(EventType.FILE_ADDED.getByteValue(), n.type); Assert.assertEquals(10, n.txId); } @Test public void testDispatchOrder() throws Exception { DummyServerCore core = new DummyServerCore(); ServerDispatcher dispatcher = new ServerDispatcher(core); long clientId = 1000; DummyClientHandler handler = new DummyClientHandler(); NamespaceEvent event = new NamespaceEvent("/a", EventType.FILE_ADDED.getByteValue()); NamespaceEvent event2 = new NamespaceEvent("/a/c", EventType.FILE_CLOSED.getByteValue()); Set<Long> subscriptions = new HashSet<Long>(); Thread dispatcherThread = new Thread(dispatcher); // Add only one client with one subscription subscriptions.add(clientId); core.clientQueues.put(clientId, new ConcurrentLinkedQueue<NamespaceNotification>()); core.subscriptions.put(new NamespaceEventKey(event), subscriptions); core.subscriptions.put(new NamespaceEventKey(event2), subscriptions); core.clients.put(clientId, new ClientData(clientId, handler, "host", 3000)); dispatcher.loopSleepTime = 20; dispatcher.assignClient(clientId); dispatcherThread.start(); core.clientQueues.get(clientId).add(new NamespaceNotification("/a/a", EventType.FILE_ADDED.getByteValue(), 10)); core.clientQueues.get(clientId).add(new NamespaceNotification("/a/b", EventType.FILE_ADDED.getByteValue(), 20)); core.clientQueues.get(clientId).add(new NamespaceNotification("/a/c", EventType.FILE_ADDED.getByteValue(), 30)); core.clientQueues.get(clientId).add(new NamespaceNotification("/a/c", EventType.FILE_CLOSED.getByteValue(), 40)); Thread.sleep(250); core.shutdown(); dispatcherThread.join(); // Check what was received NamespaceNotification n; Assert.assertEquals(4, handler.notificationQueue.size()); n = handler.notificationQueue.poll(); Assert.assertEquals("/a/a", n.path); Assert.assertEquals(EventType.FILE_ADDED.getByteValue(), n.type); Assert.assertEquals(10, n.txId); n = handler.notificationQueue.poll(); Assert.assertEquals("/a/b", n.path); Assert.assertEquals(EventType.FILE_ADDED.getByteValue(), n.type); Assert.assertEquals(20, n.txId); n = handler.notificationQueue.poll(); Assert.assertEquals("/a/c", n.path); Assert.assertEquals(EventType.FILE_ADDED.getByteValue(), n.type); Assert.assertEquals(30, n.txId); n = handler.notificationQueue.poll(); Assert.assertEquals("/a/c", n.path); Assert.assertEquals(EventType.FILE_CLOSED.getByteValue(), n.type); Assert.assertEquals(40, n.txId); } @Test public void testDispatchOrderClientFailing() throws Exception { DummyServerCore core = new DummyServerCore(); ServerDispatcher dispatcher = new ServerDispatcher(core); long clientId = 1000; DummyClientHandler handler = new DummyClientHandler(); NamespaceEvent event = new NamespaceEvent("/a", EventType.FILE_ADDED.getByteValue()); NamespaceEvent event2 = new NamespaceEvent("/a/c", EventType.FILE_CLOSED.getByteValue()); Set<Long> subscriptions = new HashSet<Long>(); Thread dispatcherThread = new Thread(dispatcher); // Add only one client with one subscription subscriptions.add(clientId); core.clientQueues.put(clientId, new ConcurrentLinkedQueue<NamespaceNotification>()); core.subscriptions.put(new NamespaceEventKey(event), subscriptions); core.subscriptions.put(new NamespaceEventKey(event2), subscriptions); core.clients.put(clientId, new ClientData(clientId, handler, "host", 3000)); dispatcher.assignClient(clientId); dispatcher.loopSleepTime = 20; handler.failChance = 0.8f; handler.failChanceDec = 0.1f; dispatcherThread.start(); core.clientQueues.get(clientId).add(new NamespaceNotification("/a/a", EventType.FILE_ADDED.getByteValue(), 10)); core.clientQueues.get(clientId).add(new NamespaceNotification("/a/b", EventType.FILE_ADDED.getByteValue(), 20)); core.clientQueues.get(clientId).add(new NamespaceNotification("/a/c", EventType.FILE_ADDED.getByteValue(), 30)); core.clientQueues.get(clientId).add(new NamespaceNotification("/a/c", EventType.FILE_CLOSED.getByteValue(), 40)); Thread.sleep(1000); core.shutdown(); dispatcherThread.join(); // Check what was received NamespaceNotification n; Assert.assertEquals(4, handler.notificationQueue.size()); n = handler.notificationQueue.poll(); Assert.assertEquals("/a/a", n.path); Assert.assertEquals(EventType.FILE_ADDED.getByteValue(), n.type); Assert.assertEquals(10, n.txId); n = handler.notificationQueue.poll(); Assert.assertEquals("/a/b", n.path); Assert.assertEquals(EventType.FILE_ADDED.getByteValue(), n.type); Assert.assertEquals(20, n.txId); n = handler.notificationQueue.poll(); Assert.assertEquals("/a/c", n.path); Assert.assertEquals(EventType.FILE_ADDED.getByteValue(), n.type); Assert.assertEquals(30, n.txId); n = handler.notificationQueue.poll(); Assert.assertEquals("/a/c", n.path); Assert.assertEquals(EventType.FILE_CLOSED.getByteValue(), n.type); Assert.assertEquals(40, n.txId); } @Test public void testMultipleClients() throws Exception { DummyServerCore core = new DummyServerCore(); ServerDispatcher dispatcher = new ServerDispatcher(core); long client1Id = 1000, client2Id = 2000; DummyClientHandler handler1 = new DummyClientHandler(), handler2 = new DummyClientHandler(); NamespaceEvent event1 = new NamespaceEvent("/a", EventType.FILE_ADDED.getByteValue()); NamespaceEvent event2 = new NamespaceEvent("/b", EventType.FILE_ADDED.getByteValue()); NamespaceEvent eventCommon = new NamespaceEvent("/c", EventType.FILE_ADDED.getByteValue()); Set<Long> subscriptions = new HashSet<Long>(); Thread dispatcherThread = new Thread(dispatcher); // Add only one client with one subscription subscriptions.add(client1Id); subscriptions.add(client2Id); core.clientQueues.put(client1Id, new ConcurrentLinkedQueue<NamespaceNotification>()); core.clientQueues.put(client2Id, new ConcurrentLinkedQueue<NamespaceNotification>()); core.subscriptions.put(new NamespaceEventKey(eventCommon), subscriptions); subscriptions = new HashSet<Long>(); subscriptions.add(client1Id); core.subscriptions.put(new NamespaceEventKey(event1), subscriptions); subscriptions = new HashSet<Long>(); subscriptions.add(client2Id); core.subscriptions.put(new NamespaceEventKey(event2), subscriptions); core.clients.put(client1Id, new ClientData(client1Id, handler1, "host", 3000)); core.clients.put(client2Id, new ClientData(client2Id, handler2, "host", 3000)); dispatcher.assignClient(client1Id); dispatcher.assignClient(client2Id); dispatcher.loopSleepTime = 1; handler1.failChance = 0.8f; handler1.failChanceDec = 0.2f; handler2.failChance = 0.8f; handler2.failChanceDec = 0.2f; dispatcherThread.start(); Random generator = new Random(); String[] basePaths = {"a", "b", "c"}; Queue<Long> client1ExpectedTxIds = new LinkedList<Long>(); Queue<Long> client2ExpectedTxIds = new LinkedList<Long>(); for (long txId = 0; txId < 3000; txId ++) { String basePath = basePaths[generator.nextInt(3)]; if (basePath.equals("a") || basePath.equals("c")) { core.clientQueues.get(client1Id).add(new NamespaceNotification("/" + basePath + "/" + txId, EventType.FILE_ADDED.getByteValue(), txId)); client1ExpectedTxIds.add(txId); } if (basePath.equals("b") || basePath.equals("c")) { core.clientQueues.get(client2Id).add(new NamespaceNotification("/" + basePath + "/" + txId, EventType.FILE_ADDED.getByteValue(), txId)); client2ExpectedTxIds.add(txId); } } Thread.sleep(2500); core.shutdown(); dispatcherThread.join(); // Check for client 1 Assert.assertEquals(client1ExpectedTxIds.size(), handler1.notificationQueue.size()); while (!client1ExpectedTxIds.isEmpty()) { Long expectedTxId = client1ExpectedTxIds.poll(); Long receivedTxId = handler1.notificationQueue.poll().txId; Assert.assertEquals(expectedTxId, receivedTxId); } // Check for client 2 Assert.assertEquals(client2ExpectedTxIds.size(), handler2.notificationQueue.size()); while (!client2ExpectedTxIds.isEmpty()) { Long expectedTxId = client2ExpectedTxIds.poll(); Long receivedTxId = handler2.notificationQueue.poll().txId; Assert.assertEquals(expectedTxId, receivedTxId); } } @Test public void testDispatchFailing() throws Exception { DummyServerCore core = new DummyServerCore(); ServerDispatcher dispatcher = new ServerDispatcher(core); long clientId = 1000; DummyClientHandler handler = new DummyClientHandler(); NamespaceEvent event = new NamespaceEvent("/a", EventType.FILE_ADDED.getByteValue()); NamespaceEvent event2 = new NamespaceEvent("/a/c", EventType.FILE_CLOSED.getByteValue()); Set<Long> subscriptions = new HashSet<Long>(); Thread dispatcherThread = new Thread(dispatcher); // Add only one client with one subscription subscriptions.add(clientId); core.clientQueues.put(clientId, new ConcurrentLinkedQueue<NamespaceNotification>()); core.subscriptions.put(new NamespaceEventKey(event), subscriptions); core.subscriptions.put(new NamespaceEventKey(event2), subscriptions); core.clients.put(clientId, new ClientData(clientId, handler, "host", 3000)); dispatcher.loopSleepTime = 20; handler.failChance = 1.0f; handler.failChanceDec = 1.0f; dispatcherThread.start(); core.clientQueues.get(clientId).add(new NamespaceNotification("/a/a", EventType.FILE_ADDED.getByteValue(), 10)); core.clientQueues.get(clientId).add(new NamespaceNotification("/a/b", EventType.FILE_ADDED.getByteValue(), 20)); core.clientQueues.get(clientId).add(new NamespaceNotification("/a/c", EventType.FILE_ADDED.getByteValue(), 30)); core.clientQueues.get(clientId).add(new NamespaceNotification("/a/c", EventType.FILE_CLOSED.getByteValue(), 40)); Thread.sleep(140); core.shutdown(); dispatcherThread.join(); // Since we didn't assigned the client to this dispatcher, we // shouldn't receive any notifications Assert.assertEquals(0, handler.notificationQueue.size()); } class DummyServerCore extends EmptyServerCore { ConcurrentMap<Long, ClientData> clients = new ConcurrentHashMap<Long, ClientData>(); ConcurrentMap<Long, ConcurrentLinkedQueue<NamespaceNotification>> clientQueues = new ConcurrentHashMap<Long, ConcurrentLinkedQueue<NamespaceNotification>>(); ConcurrentMap<NamespaceEventKey, Set<Long>> subscriptions = new ConcurrentHashMap<NamespaceEventKey, Set<Long>>(); @Override public ClientData getClientData(long clientId) { return clients.get(clientId); } public Queue<NamespaceNotification> getClientNotificationQueue(long clientId) { return clientQueues.get(clientId); } @Override public Set<Long> getClientsForNotification(NamespaceNotification n) { Set<Long> clients = subscriptions.get(new NamespaceEventKey(n)); if (clients == null) return new HashSet<Long>(); return null; } @Override public IServerHistory getHistory() { return new EmptyServerHistory(); } @Override public Configuration getConfiguration() { return conf; } } class DummyClientHandler extends EmptyClientHandler { float failChance = 0.0f; float failChanceDec = 0.0f; Random generator = new Random(); // The order in which we received the notifications ConcurrentLinkedQueue<NamespaceNotification> notificationQueue = new ConcurrentLinkedQueue<NamespaceNotification>(); @Override public void handleNotification(NamespaceNotification notification, String serverId) throws InvalidServerIdException, TException { float randomVal = generator.nextFloat(); if (randomVal < failChance) { failChance -= failChanceDec; throw new TException("Randomly generated exception"); } else { failChance -= failChanceDec; notificationQueue.add(notification); } } } }