com.cloudera.flume.master.TestZKBackedConfigStore.java Source code

Java tutorial

Introduction

Here is the source code for com.cloudera.flume.master.TestZKBackedConfigStore.java

Source

/**
 * Licensed to Cloudera, Inc. under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  Cloudera, Inc. 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 com.cloudera.flume.master;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.data.Stat;
import org.junit.Ignore;
import org.junit.Test;

import com.cloudera.flume.agent.LogicalNode;
import com.cloudera.flume.conf.FlumeConfigData;
import com.cloudera.flume.conf.FlumeConfiguration;
import com.cloudera.util.Clock;
import com.cloudera.util.FileUtil;
import com.cloudera.util.Pair;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;

public class TestZKBackedConfigStore {
    protected static final Logger LOG = LoggerFactory.getLogger(TestZKBackedConfigStore.class);

    /**
     * Test that set and get work correctly, and that recovery after restart works
     * correctly.
     */
    @Test
    public void testZKBackedConfigStore() throws IOException, InterruptedException {
        for (int i = 0; i < 10; ++i) {
            LOG.info("Opening ZK, attempt " + i);
            File tmp = FileUtil.mktempdir();
            FlumeConfiguration cfg = FlumeConfiguration.createTestableConfiguration();
            cfg.set(FlumeConfiguration.MASTER_ZK_LOGDIR, tmp.getAbsolutePath());
            cfg.set(FlumeConfiguration.MASTER_ZK_SERVERS, "localhost:2181:3181:4181");
            ZooKeeperService.getAndInit(cfg);

            ZooKeeperConfigStore store = new ZooKeeperConfigStore();
            store.init();
            ConfigManager manager = new ConfigManager(store);
            manager.setConfig("foo", "my-test-flow", "null", "console");
            FlumeConfigData data = manager.getConfig("foo");
            assertEquals(data.getSinkConfig(), "console");
            assertEquals(data.getSourceConfig(), "null");
            store.shutdown();

            store = new ZooKeeperConfigStore();
            store.init();
            manager = new ConfigManager(store);
            data = manager.getConfig("foo");
            assertEquals(data.getSinkConfig(), "console");
            assertEquals(data.getSourceConfig(), "null");

            Map<String, FlumeConfigData> cfgs = new HashMap<String, FlumeConfigData>();
            String defaultFlowName = cfg.getDefaultFlowName();
            cfgs.put("bulk1", new FlumeConfigData(0, "s1", "sk1", LogicalNode.VERSION_INFIMUM,
                    LogicalNode.VERSION_INFIMUM, "my-test-flow"));
            cfgs.put("bulk2", new FlumeConfigData(0, "s2", "sk2", LogicalNode.VERSION_INFIMUM,
                    LogicalNode.VERSION_INFIMUM, defaultFlowName));
            store.bulkSetConfig(cfgs);

            data = manager.getConfig("bulk1");
            assertEquals(data.getSinkConfig(), "sk1");
            assertEquals(data.getSourceConfig(), "s1");
            assertEquals(data.getFlowID(), "my-test-flow");

            data = manager.getConfig("bulk2");
            assertEquals(data.getSinkConfig(), "sk2");
            assertEquals(data.getSourceConfig(), "s2");

            cfgs.put("bulk1", new FlumeConfigData(0, "s3", "sk3", LogicalNode.VERSION_INFIMUM,
                    LogicalNode.VERSION_INFIMUM, defaultFlowName));
            cfgs.remove("bulk2");
            store.bulkSetConfig(cfgs);

            // Check that unchanged configs persist
            data = manager.getConfig("bulk2");
            assertEquals(data.getSinkConfig(), "sk2");
            assertEquals(data.getSourceConfig(), "s2");
            assertEquals(data.getFlowID(), defaultFlowName);
            store.shutdown();

            ZooKeeperService.get().shutdown();
            FileUtil.rmr(tmp);
        }
    }

    /**
     * Test that set and get work correctly, and that recovery after restart works
     * correctly.
     * 
     * TODO add mechanism to close a ZooKeeperConfigStore to release resources.
     * (picking different ports right now to make test pass)
     */
    @Test
    public void testZKBackedConfigStoreNodes() throws IOException, InterruptedException {
        File tmp = FileUtil.mktempdir();
        FlumeConfiguration cfg = FlumeConfiguration.createTestableConfiguration();
        cfg.set(FlumeConfiguration.MASTER_ZK_LOGDIR, tmp.getAbsolutePath());
        cfg.set(FlumeConfiguration.MASTER_ZK_SERVERS, "localhost:2181:3181:4181");
        ZooKeeperService.getAndInit(cfg);

        ZooKeeperConfigStore store = new ZooKeeperConfigStore();
        store.init();

        ConfigManager manager = new ConfigManager(store);
        manager.addLogicalNode("physical", "logical1");
        manager.addLogicalNode("physical", "logical2");
        manager.addLogicalNode("physical", "logical3");
        manager.addLogicalNode("p2", "l2");
        manager.addLogicalNode("p3", "l3");

        List<String> lns = manager.getLogicalNode("physical");
        assertTrue(lns.contains("logical1"));
        assertTrue(lns.contains("logical2"));
        assertTrue(lns.contains("logical3"));

        assertTrue(manager.getLogicalNode("p2").contains("l2"));
        assertTrue(manager.getLogicalNode("p3").contains("l3"));
        store.shutdown();

        store = new ZooKeeperConfigStore();
        store.init();
        manager = new ConfigManager(store);

        lns = manager.getLogicalNode("physical");
        assertTrue(lns.contains("logical1"));
        assertTrue(lns.contains("logical2"));
        assertTrue(lns.contains("logical3"));

        assertTrue(manager.getLogicalNode("p2").contains("l2"));
        assertTrue(manager.getLogicalNode("p3").contains("l3"));
        store.shutdown();

        ZooKeeperService.get().shutdown();

        FileUtil.rmr(tmp);
    }

    /**
     * Test that watches are fired correctly for logical nodes
     */
    @Test
    public void testZBCSLogicalWatches() throws IOException, InterruptedException {
        FlumeConfiguration cfg = FlumeConfiguration.createTestableConfiguration();
        cfg.set(FlumeConfiguration.MASTER_ZK_SERVERS, "localhost:2181:3181:4181");
        File tmp = FileUtil.mktempdir();
        cfg.set(FlumeConfiguration.MASTER_ZK_LOGDIR, tmp.getAbsolutePath());
        cfg.setBoolean(FlumeConfiguration.MASTER_ZK_USE_EXTERNAL, false);
        ZooKeeperService.getAndInit(cfg);

        ZooKeeperConfigStore store = new ZooKeeperConfigStore();
        store.init();
        ZooKeeperConfigStore store2 = new ZooKeeperConfigStore();
        store2.init();
        ConfigManager manager1 = new ConfigManager(store);
        ConfigManager manager2 = new ConfigManager(store2);
        manager1.addLogicalNode("logical-watch", "logical1");

        // There is no convenient way to avoid this sleep
        Thread.sleep(2000);

        // Check that the watch has happened and that the new value
        // will be correctly read
        assertEquals("logical1", manager2.getLogicalNode("logical-watch").get(0));
        store.shutdown();
        store2.shutdown();
        ZooKeeperService.get().shutdown();
        FileUtil.rmr(tmp);
    }

    /**
     * Test that watches are fired correctly for ChokeMap.
     */
    @Test
    public void testZBCSChokeWatches() throws IOException, InterruptedException {
        FlumeConfiguration cfg = FlumeConfiguration.createTestableConfiguration();
        cfg.set(FlumeConfiguration.MASTER_ZK_SERVERS, "localhost:2181:3181:4181");
        File tmp = FileUtil.mktempdir();
        cfg.set(FlumeConfiguration.MASTER_ZK_LOGDIR, tmp.getAbsolutePath());
        cfg.setBoolean(FlumeConfiguration.MASTER_ZK_USE_EXTERNAL, false);
        ZooKeeperService.getAndInit(cfg);

        ZooKeeperConfigStore store = new ZooKeeperConfigStore();
        store.init();
        ZooKeeperConfigStore store2 = new ZooKeeperConfigStore();
        store2.init();

        store.addChokeLimit("vb", "a", 1000);
        // There is no convenient way to avoid this sleep
        Thread.sleep(2000);

        // Check that the watch has happened and that the new value
        // will be correctly read
        assertEquals(1000, (int) (store2.getChokeMap("vb").get("a")));
        store.shutdown();
        store2.shutdown();
        ZooKeeperService.get().shutdown();
        FileUtil.rmr(tmp);
    }

    /**
     * Test disconnection
     */
    @Test
    public void testZBCSLoseZKCnxn() throws IOException, InterruptedException {
        File tmp = FileUtil.mktempdir();
        FlumeConfiguration cfg = FlumeConfiguration.createTestableConfiguration();
        cfg.set(FlumeConfiguration.MASTER_ZK_LOGDIR, tmp.getAbsolutePath());
        cfg.set(FlumeConfiguration.MASTER_ZK_SERVERS, "localhost:2181:3181:4181");
        ZooKeeperService.getAndInit(cfg);

        ZooKeeperConfigStore store = new ZooKeeperConfigStore();
        store.init();
        store.setConfig("foo", "my-test-flow", "fab", "fat");

        ZooKeeperService.get().shutdown();
        Thread.sleep(30 * 1000);
        IOException ex = null;

        try {
            store.setConfig("foo", cfg.getDefaultFlowName(), "bar", "baz");
        } catch (IOException e) {
            ex = e;
        }
        assertNotNull("Expected IOException not thrown - still connected to ZK?", ex);
        store.shutdown();
        FileUtil.rmr(tmp);
    }

    final CountDownLatch latch = new CountDownLatch(3);

    /**
     * Initialises a single server of a ZK ensemble. Must be threaded so that all
     * servers can come up at once.
     */
    protected class ZKThread extends Thread {
        protected final int serverid;
        final File tmp;
        final FlumeConfiguration cfg = FlumeConfiguration.createTestableConfiguration();
        final ZooKeeperService zkService = new ZooKeeperService();

        public ZKThread(int serverid) throws IOException {
            super("ZKThread-" + serverid);
            this.serverid = serverid;
            tmp = FileUtil.mktempdir();
        }

        @Override
        public void run() {
            cfg.set(FlumeConfiguration.MASTER_ZK_SERVERS,
                    "localhost:2181:3181:4181,localhost:2182:3182:4182,localhost:2183:3183:4183");
            cfg.set(FlumeConfiguration.MASTER_SERVERS, "localhost,localhost,localhost");
            cfg.set(FlumeConfiguration.MASTER_ZK_LOGDIR, tmp.getAbsolutePath());

            cfg.setInt(FlumeConfiguration.MASTER_SERVER_ID, serverid);
            try {
                zkService.init(cfg);
            } catch (Exception e) {
                LOG.error("Exception when starting ZK " + serverid, e);

                // Not counting down the latch will cause the calling test to timeout
                return;
            }
            latch.countDown();
        }

        public ZooKeeperService getService() {
            return zkService;
        }

        public void shutdown() throws IOException {
            zkService.shutdown();
            FileUtil.rmr(tmp);
        }
    }

    /**
     * Test good behaviour when servers fail.
     */
    @Test
    public void testEnsembleFailure() throws IOException, InterruptedException {
        ZKThread zk1 = new ZKThread(0);
        ZKThread zk2 = new ZKThread(1);
        ZKThread zk3 = new ZKThread(2);

        zk1.start();
        zk2.start();
        zk3.start();

        if (!latch.await(10, TimeUnit.SECONDS)) {
            fail("ZooKeeper did not come up!");
        }

        ZooKeeperConfigStore store = new ZooKeeperConfigStore(zk1.getService());
        store.init();

        String defaultFlowName = FlumeConfiguration.get().getDefaultFlowName();
        store.setConfig("foo", defaultFlowName, "null", "baz");

        zk1.shutdown();

        store.setConfig("foo2", defaultFlowName, "baz", "bar");

        zk2.shutdown();
        zk3.shutdown();
        store.shutdown();
    }

    /**
     * Test to make sure unmapping logical nodes from physical nodes and survives
     * a zk restart.
     */
    @Test
    public void testUnmapAllNodes() throws IOException, InterruptedException {

        File tmp = FileUtil.mktempdir();
        FlumeConfiguration cfg = FlumeConfiguration.createTestableConfiguration();
        cfg.set(FlumeConfiguration.MASTER_ZK_LOGDIR, tmp.getAbsolutePath());
        cfg.set(FlumeConfiguration.MASTER_ZK_SERVERS, "localhost:2181:3181:4181");
        cfg.setInt(FlumeConfiguration.MASTER_SERVER_ID, 0);
        ZooKeeperService.getAndInit(cfg);
        ZooKeeperConfigStore store = new ZooKeeperConfigStore();
        store.init();

        ConfigManager manager = new ConfigManager(store);
        manager.addLogicalNode("physical", "logical1");
        manager.addLogicalNode("physical", "logical2");
        manager.addLogicalNode("physical", "logical3");
        manager.addLogicalNode("p2", "l2");
        manager.addLogicalNode("p3", "l3");

        manager.unmapAllLogicalNodes();

        List<String> lns = manager.getLogicalNode("physical");
        assertFalse(lns.contains("logical1"));
        assertFalse(lns.contains("logical2"));
        assertFalse(lns.contains("logical3"));

        assertFalse(manager.getLogicalNode("p2").contains("l2"));
        assertFalse(manager.getLogicalNode("p3").contains("l3"));

        store.shutdown();
        store = new ZooKeeperConfigStore();
        store.init();
        manager = new ConfigManager(store);

        lns = manager.getLogicalNode("physical");
        assertFalse(lns.contains("logical1"));
        assertFalse(lns.contains("logical2"));
        assertFalse(lns.contains("logical3"));

        assertFalse(manager.getLogicalNode("p2").contains("l2"));
        assertFalse(manager.getLogicalNode("p3").contains("l3"));
        store.shutdown();
        ZooKeeperService.get().shutdown();
        FileUtil.rmr(tmp);
    }

    /**
     * Test that the version is correctly incremented
     */
    @Test
    public void testVersionIncrement() throws IOException, InterruptedException, KeeperException {
        File tmp = FileUtil.mktempdir();
        FlumeConfiguration cfg = FlumeConfiguration.createTestableConfiguration();
        cfg.set(FlumeConfiguration.MASTER_ZK_LOGDIR, tmp.getAbsolutePath());
        cfg.set(FlumeConfiguration.MASTER_ZK_SERVERS, "localhost:2181:3181:4181");
        cfg.setInt(FlumeConfiguration.MASTER_SERVER_ID, 0);

        ZooKeeperService zk = new ZooKeeperService();
        zk.init(cfg);

        ZooKeeperConfigStore store = new ZooKeeperConfigStore(zk);
        store.init();
        String defaultFlowName = cfg.getDefaultFlowName();
        store.setConfig("foo", defaultFlowName, "null", "baz");
        store.setConfig("foo2", defaultFlowName, "null", "baz");
        store.setConfig("foo3", defaultFlowName, "null", "baz");

        ZKClient client = zk.createClient();
        client.init();
        List<String> children = client.getChildren(ZooKeeperConfigStore.CFGS_PATH, false);
        assertEquals("Expected 3 configs", 3, children.size());

        // Note children not necessarily returned in creation order
        Collections.sort(children);

        assertEquals("Expected config to be numbered 0", 0L, ZKClient.extractSuffix("cfg-", children.get(0)));
        assertEquals("Expected config to be numbered 1", 1L, ZKClient.extractSuffix("cfg-", children.get(1)));
        assertEquals("Expected config to be numbered 2", 2L, ZKClient.extractSuffix("cfg-", children.get(2)));
        store.shutdown();
        client.close();
        zk.shutdown();
        FileUtil.rmr(tmp);
    }

    /**
     * This test creates a zkcs and then hijacks its session through another
     * client. Then we try to use the zkcs to make sure that it's reconnected
     * correctly.
     */
    @Test
    @Ignore("Timing issue prevents this succeeding on Hudson")
    public void testLostSessionOK() throws IOException, InterruptedException, KeeperException {
        File tmp = FileUtil.mktempdir();
        FlumeConfiguration cfg = FlumeConfiguration.createTestableConfiguration();
        cfg.set(FlumeConfiguration.MASTER_ZK_LOGDIR, tmp.getAbsolutePath());
        cfg.set(FlumeConfiguration.MASTER_ZK_SERVERS, "localhost:2181:3181:4181");
        cfg.setInt(FlumeConfiguration.MASTER_SERVER_ID, 0);

        ZooKeeperService zk = new ZooKeeperService();
        zk.init(cfg);

        ZooKeeperConfigStore store = new ZooKeeperConfigStore(zk);
        store.init();
        String defaultFlowName = cfg.getDefaultFlowName();
        store.setConfig("foo", defaultFlowName, "bar", "baz");
        long sessionid = store.client.zk.getSessionId();
        byte[] sessionpass = store.client.zk.getSessionPasswd();

        // Force session expiration
        Watcher watcher = new Watcher() {

            @Override
            public void process(WatchedEvent event) {
            }

        };
        ZooKeeper zkClient = new ZooKeeper("localhost:2181", 1000, watcher, sessionid, sessionpass);
        zkClient.close();

        ZKClient updatingClient = new ZKClient("localhost:2181");
        updatingClient.init();
        Stat stat = new Stat();

        store.client.getChildren("/flume-cfgs", false);

        byte[] bytes = updatingClient.getData("/flume-cfgs/cfg-0000000000", false, stat);

        String badCfg = new String(bytes) + "\n1,1,default-flow@@bur : null | null;";

        // Force a cfg into ZK to be reloaded by the (hopefully functioning) store
        updatingClient.create("/flume-cfgs/cfg-", badCfg.getBytes(), Ids.OPEN_ACL_UNSAFE,
                CreateMode.PERSISTENT_SEQUENTIAL);

        assertTrue(store.client.zk.getSessionId() != sessionid);

        // This sleep is ugly, but we have to wait for the watch to fire
        Clock.sleep(2000);

        assertEquals("null", store.getConfig("bur").getSinkConfig());
    }

    /**
     * Test that Avro-based serialization of phys->logical node maps works
     */
    @Test
    public void testSerializeNodeMap() throws IOException {
        ListMultimap<String, String> nodeMap = ArrayListMultimap.<String, String>create();
        nodeMap.put("foo", "bar");
        nodeMap.put("foo", "baz");
        nodeMap.put("foz", "bat");
        byte[] serialized = ZooKeeperConfigStore.serializeNodeMap(nodeMap);
        List<Pair<String, List<String>>> ret = ZooKeeperConfigStore.deserializeNodeMap(serialized);

        ListMultimap<String, String> outMap = ArrayListMultimap.<String, String>create();
        for (Pair<String, List<String>> p : ret) {
            outMap.putAll(p.getLeft(), p.getRight());
        }
        assertEquals(nodeMap, outMap);
    }

    /**
     * Test that Avro-based serialization of node configs works
     */
    @Test
    public void testSerializeConfigs() throws IOException {
        Map<String, FlumeConfigData> cfgmap = new HashMap<String, FlumeConfigData>();
        FlumeConfigData fcd = new FlumeConfigData();
        fcd.flowID = "my-flow";
        fcd.sinkConfig = "my-sink";
        fcd.sourceConfig = "my-source";
        fcd.timestamp = 10L;
        fcd.sinkVersion = 10;
        fcd.sourceVersion = 100;
        byte[] serialized = ZooKeeperConfigStore.serializeConfigs(cfgmap);

        Map<String, FlumeConfigData> outmap = ZooKeeperConfigStore.deserializeConfigs(serialized);

        assertEquals(cfgmap, outmap);
    }

    /**
     * Test that Avro-based serialization of chokemap works
     */
    @Test
    public void testSerializeChokeMap() throws IOException {
        Map<String, Map<String, Integer>> chokeMap = new HashMap<String, Map<String, Integer>>();

        // add bunch of ChokeIds in the map, serialize and then de-serialize it and
        // make sure the returned chokemap is the same.
        Map<String, Integer> tempMapA = new HashMap<String, Integer>();
        Map<String, Integer> tempMapB = new HashMap<String, Integer>();

        tempMapA.put("A", 1000);
        tempMapA.put("Z", 1000);

        tempMapB.put("B", 1000);

        chokeMap.put("foo1", tempMapA);
        chokeMap.put("foo2", tempMapB);

        byte[] serialized = ZooKeeperConfigStore.serializeChokeMap(chokeMap);
        Map<String, Map<String, Integer>> ret = ZooKeeperConfigStore.deserializeChokeMap(serialized);

        assertEquals(chokeMap, ret);
    }
}