Package org.sdnplatform.sync.internal

Source Code of org.sdnplatform.sync.internal.SyncManagerTest$Update

package org.sdnplatform.sync.internal;

import static org.junit.Assert.*;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;

import net.floodlightcontroller.core.module.FloodlightModuleContext;
import net.floodlightcontroller.debugcounter.IDebugCounterService;
import net.floodlightcontroller.debugcounter.NullDebugCounter;
import net.floodlightcontroller.threadpool.IThreadPoolService;
import net.floodlightcontroller.threadpool.ThreadPool;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.sdnplatform.sync.IClosableIterator;
import org.sdnplatform.sync.IInconsistencyResolver;
import org.sdnplatform.sync.IStoreClient;
import org.sdnplatform.sync.IStoreListener;
import org.sdnplatform.sync.IStoreListener.UpdateType;
import org.sdnplatform.sync.ISyncService;
import org.sdnplatform.sync.Versioned;
import org.sdnplatform.sync.ISyncService.Scope;
import org.sdnplatform.sync.error.ObsoleteVersionException;
import org.sdnplatform.sync.internal.AbstractSyncManager;
import org.sdnplatform.sync.internal.SyncManager;
import org.sdnplatform.sync.internal.SyncTorture;
import org.sdnplatform.sync.internal.config.Node;
import org.sdnplatform.sync.internal.config.PropertyCCProvider;
import org.sdnplatform.sync.internal.store.Key;
import org.sdnplatform.sync.internal.store.TBean;
import org.sdnplatform.sync.internal.util.CryptoUtil;
import org.sdnplatform.sync.internal.version.VectorClock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class SyncManagerTest {
    protected static Logger logger =
            LoggerFactory.getLogger(SyncManagerTest.class);
   
    protected FloodlightModuleContext[] moduleContexts;
    protected SyncManager[] syncManagers;
    protected final static ObjectMapper mapper = new ObjectMapper();
    protected String nodeString;
    ArrayList<Node> nodes;

    ThreadPool tp;

    @Rule
    public TemporaryFolder keyStoreFolder = new TemporaryFolder();

    protected File keyStoreFile;
    protected String keyStorePassword = "verysecurepassword";
   
    protected void setupSyncManager(FloodlightModuleContext fmc,
                                    SyncManager syncManager, Node thisNode)
            throws Exception {       
        fmc.addService(IThreadPoolService.class, tp);
        fmc.addService(IDebugCounterService.class, new NullDebugCounter());
        fmc.addConfigParam(syncManager, "configProviders",
                           PropertyCCProvider.class.getName());
        fmc.addConfigParam(syncManager, "nodes", nodeString);
        fmc.addConfigParam(syncManager, "thisNode", ""+thisNode.getNodeId());
        fmc.addConfigParam(syncManager, "persistenceEnabled", "false");
        fmc.addConfigParam(syncManager, "authScheme", "CHALLENGE_RESPONSE");
        fmc.addConfigParam(syncManager, "keyStorePath",
                           keyStoreFile.getAbsolutePath());
        fmc.addConfigParam(syncManager, "keyStorePassword", keyStorePassword);
        tp.init(fmc);
        syncManager.init(fmc);

        tp.startUp(fmc);
        syncManager.startUp(fmc);

        syncManager.registerStore("global", Scope.GLOBAL);
        syncManager.registerStore("local", Scope.LOCAL);
    }
   
    @Before
    public void setUp() throws Exception {
        keyStoreFile = new File(keyStoreFolder.getRoot(),
                "keystore.jceks");
        CryptoUtil.writeSharedSecret(keyStoreFile.getAbsolutePath(),
                                     keyStorePassword,
                                     CryptoUtil.secureRandom(16));

        tp = new ThreadPool();
       
        syncManagers = new SyncManager[4];
        moduleContexts = new FloodlightModuleContext[4];

        nodes = new ArrayList<Node>();
        nodes.add(new Node("localhost", 40101, (short)1, (short)1));
        nodes.add(new Node("localhost", 40102, (short)2, (short)2));
        nodes.add(new Node("localhost", 40103, (short)3, (short)1));
        nodes.add(new Node("localhost", 40104, (short)4, (short)2));
        nodeString = mapper.writeValueAsString(nodes);

        for(int i = 0; i < 4; i++) {
            moduleContexts[i] = new FloodlightModuleContext();
            syncManagers[i] = new SyncManager();
            setupSyncManager(moduleContexts[i], syncManagers[i], nodes.get(i));
        }
    }

    @After
    public void tearDown() {
        tp.getScheduledExecutor().shutdownNow();
        tp = null;

        if (syncManagers != null) {
            for(int i = 0; i < syncManagers.length; i++) {
                if (null != syncManagers[i])
                    syncManagers[i].shutdown();
            }
        }
        syncManagers = null;
    }

    @Test
    public void testBasicOneNode() throws Exception {
        AbstractSyncManager sync = syncManagers[0];
        IStoreClient<Key, TBean> testClient =
                sync.getStoreClient("global", Key.class, TBean.class);
        Key k = new Key("com.bigswitch.bigsync.internal", "test");
        TBean tb = new TBean("hello", 42);
        TBean tb2 = new TBean("hello", 84);
        TBean tb3 = new TBean("hello", 126);
       
        assertNotNull(testClient.get(k));
        assertNull(testClient.get(k).getValue());
       
        testClient.put(k, tb);
        Versioned<TBean> result = testClient.get(k);
        assertEquals(result.getValue(), tb);

        result.setValue(tb2);
        testClient.put(k, result);

        try {
            result.setValue(tb3);
            testClient.put(k, result);
            fail("Should get ObsoleteVersionException");
        } catch (ObsoleteVersionException e) {
            // happy town
        }

        result = testClient.get(k);
        assertEquals(tb2, result.getValue());
       
    }

    @Test
    public void testIterator() throws Exception {
        AbstractSyncManager sync = syncManagers[0];
        IStoreClient<Key, TBean> testClient =
                sync.getStoreClient("local", Key.class, TBean.class);
       
        HashMap<Key, TBean> testMap = new HashMap<Key, TBean>();
        for (int i = 0; i < 100; i++) {
            Key k = new Key("com.bigswitch.bigsync.internal", "test" + i);
            TBean tb = new TBean("value", i);
            testMap.put(k, tb);
            testClient.put(k, tb);
        }
       
        IClosableIterator<Entry<Key, Versioned<TBean>>> iter =
                testClient.entries();
        int size = 0;
        try {
            while (iter.hasNext()) {
                Entry<Key, Versioned<TBean>> e = iter.next();
                assertEquals(testMap.get(e.getKey()), e.getValue().getValue());
                size += 1;
            }
        } finally {
            iter.close();
        }
        assertEquals(testMap.size(), size);
    }

    protected static <K, V> Versioned<V> waitForValue(IStoreClient<K, V> client,
                                             K key, V value,
                                             int maxTime,
                                             String clientName)
                                                     throws Exception {
        Versioned<V> v = null;
        long then = System.currentTimeMillis();
        while (true) {
            v = client.get(key);
            if (value != null) {
                if (v.getValue() != null && v.getValue().equals(value)) break;
            } else {
                if (v.getValue() != null) break;
            }
            if (v.getValue() != null)
                logger.info("{}: Value for key {} not yet right: " +
                            "expected: {}; actual: {}",
                            new Object[]{clientName, key, value, v.getValue()});
            else
                logger.info("{}: Value for key {} is null: expected {}",
                            new Object[]{clientName, key, value});
               

            Thread.sleep(100);
            assertTrue(then + maxTime > System.currentTimeMillis());
        }
        return v;
    }

    private void waitForFullMesh(int maxTime) throws Exception {
        waitForFullMesh(syncManagers, maxTime);
    }

    protected static void waitForFullMesh(SyncManager[] syncManagers,
                                          int maxTime) throws Exception {
        long then = System.currentTimeMillis();

        while (true) {
            boolean full = true;
            for(int i = 0; i < syncManagers.length; i++) {
                if (!syncManagers[i].rpcService.isFullyConnected())
                    full = false;
            }
            if (full) break;
            Thread.sleep(100);
            assertTrue(then + maxTime > System.currentTimeMillis());
        }
    }
    private void waitForConnection(SyncManager sm,
                                   short nodeId,
                                   boolean connected,
                                   int maxTime) throws Exception {
        long then = System.currentTimeMillis();

        while (true) {
            if (connected == sm.rpcService.isConnected(nodeId)) break;
            Thread.sleep(100);
            assertTrue(then + maxTime > System.currentTimeMillis());
        }
    }

    @Test
    public void testBasicGlobalSync() throws Exception {
        waitForFullMesh(2000);

        ArrayList<IStoreClient<String, String>> clients =
                new ArrayList<IStoreClient<String, String>>(syncManagers.length);
        // write one value to each node's local interface
        for (int i = 0; i < syncManagers.length; i++) {
            IStoreClient<String, String> client =
                    syncManagers[i].getStoreClient("global",
                                                   String.class, String.class);
            clients.add(client);
            client.put("key" + i, ""+i);
        }

        // verify that we see all the values everywhere
        for (int j = 0; j < clients.size(); j++) {
            for (int i = 0; i < syncManagers.length; i++) {
                waitForValue(clients.get(j), "key" + i, ""+i, 2000, "client"+j);
            }
        }
    }

    @Test
    public void testBasicLocalSync() throws Exception {
        waitForFullMesh(2000);

        ArrayList<IStoreClient<String, String>> clients =
                new ArrayList<IStoreClient<String, String>>(syncManagers.length);
        // write one value to each node's local interface
        for (int i = 0; i < syncManagers.length; i++) {
            IStoreClient<String, String> client =
                    syncManagers[i].getStoreClient("local",
                                                   String.class, String.class);
            clients.add(client);
            client.put("key" + i, ""+i);
        }

        // verify that we see all the values from each local group at all the
        // nodes of that local group
        for (int j = 0; j < clients.size(); j++) {
            IStoreClient<String, String> client = clients.get(j);
            for (int i = 0; i < syncManagers.length; i++) {
                if (i % 2 == j % 2)
                    waitForValue(client, "key" + i, ""+i, 2000, "client"+j);
                else {
                    Versioned<String> v = client.get("key" + i);
                    if (v.getValue() != null) {
                        fail("Node " + j + " reading key" + i +
                             ": " + v.getValue());
                    }
                }
            }
        }
    }
   
    @Test
    public void testConcurrentWrite() throws Exception {
        waitForFullMesh(2000);
       
        // Here we generate concurrent writes and then resolve them using
        // a custom inconsistency resolver
        IInconsistencyResolver<Versioned<List<String>>> ir =
                new IInconsistencyResolver<Versioned<List<String>>>() {
            @Override
            public List<Versioned<List<String>>>
                    resolveConflicts(List<Versioned<List<String>>> items) {
                VectorClock vc = null;
                List<String> strings = new ArrayList<String>();
                for (Versioned<List<String>> item : items) {
                    if (vc == null)
                        vc = (VectorClock)item.getVersion();
                    else
                        vc = vc.merge((VectorClock)item.getVersion());
                   
                    strings.addAll(item.getValue());
                }
                Versioned<List<String>> v =
                        new Versioned<List<String>>(strings, vc);
                return Collections.singletonList(v);
            }
        };
       
        TypeReference<List<String>> tr = new TypeReference<List<String>>() {};
        TypeReference<String> ktr = new TypeReference<String>() {};
        IStoreClient<String, List<String>> client0 =
                syncManagers[0].getStoreClient("local", ktr, tr, ir);
        IStoreClient<String, List<String>> client2 =
                syncManagers[2].getStoreClient("local", ktr, tr, ir);
       
        client0.put("key", Collections.singletonList("value"));
        Versioned<List<String>> v = client0.get("key");
        assertNotNull(v);
       
        // now we generate two writes that are concurrent to each other
        // but are both locally after the first write.  The result should be
        // two non-obsolete lists each containing a single element.
        // The inconsistency resolver above will resolve these by merging
        // the lists
        List<String> comp = new ArrayList<String>();
        v.setValue(Collections.singletonList("newvalue0"));
        comp.add("newvalue0");
        client0.put("key", v);
        v.setValue(Collections.singletonList("newvalue1"));
        comp.add("newvalue1");
        client2.put("key", v);
       
        v = waitForValue(client0, "key", comp, 1000, "client0");

        // add one more value to the array.  Now there will be exactly one
        // non-obsolete value
        List<String> newlist = new ArrayList<String>(v.getValue());
        assertEquals(2, newlist.size());
        newlist.add("finalvalue");
        v.setValue(newlist);
        client0.put("key", v);
       
        v = waitForValue(client2, "key", newlist, 2000, "client2");
        assertEquals(3, newlist.size());
     
    }

    @Test
    public void testReconnect() throws Exception {
        IStoreClient<String, String> client0 =
                syncManagers[0].getStoreClient("global",
                                               String.class,
                                               String.class);
        IStoreClient<String, String> client1 =
                syncManagers[1].getStoreClient("global",
                                               String.class, String.class);
        IStoreClient<String, String> client2 =
                syncManagers[2].getStoreClient("global",
                                               String.class, String.class);

        client0.put("key0", "value0");
        waitForValue(client2, "key0", "value0", 1000, "client0");

        logger.info("Shutting down server ID 1");
        syncManagers[0].shutdown();
       
        client1.put("newkey1", "newvalue1");
        client2.put("newkey2", "newvalue2");
        client1.put("key0", "newvalue0");
        client2.put("key2", "newvalue2");
       
        for (int i = 0; i < 500; i++) {
            client2.put("largetest" + i, "largetestvalue");
        }
       
        logger.info("Initializing server ID 1");
        syncManagers[0] = new SyncManager();
        setupSyncManager(moduleContexts[0], syncManagers[0], nodes.get(0));

        waitForFullMesh(2000);

        client0 = syncManagers[0].getStoreClient("global",
                                                 String.class, String.class);
        waitForValue(client0, "newkey1", "newvalue1", 1000, "client0");
        waitForValue(client0, "newkey2", "newvalue2", 1000, "client0");
        waitForValue(client0, "key0", "newvalue0", 1000, "client0");
        waitForValue(client0, "key2", "newvalue2", 1000, "client0");

        for (int i = 0; i < 500; i++) {
            waitForValue(client0, "largetest" + i,
                         "largetestvalue", 1000, "client0");
        }
    }
   
    protected class Update {
        String key;
        UpdateType type;

        public Update(String key, UpdateType type) {
            super();
            this.key = key;
            this.type = type;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + getOuterType().hashCode();
            result = prime * result + ((key == null) ? 0 : key.hashCode());
            result = prime * result + ((type == null) ? 0 : type.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null) return false;
            if (getClass() != obj.getClass()) return false;
            Update other = (Update) obj;
            if (!getOuterType().equals(other.getOuterType())) return false;
            if (key == null) {
                if (other.key != null) return false;
            } else if (!key.equals(other.key)) return false;
            if (type != other.type) return false;
            return true;
        }

        private SyncManagerTest getOuterType() {
            return SyncManagerTest.this;
        }
    }
   
    protected class TestListener implements IStoreListener<String> {
        HashSet<Update> notified = new HashSet<Update>();
       
        @Override
        public void keysModified(Iterator<String> keys,
                                 UpdateType type) {
            while (keys.hasNext())
                notified.add(new Update(keys.next(), type));
        }
       
    }
   
    @SuppressWarnings("rawtypes")
    private void waitForNotify(TestListener tl,
                               HashSet comp,
                               int maxTime) throws Exception {
        long then = System.currentTimeMillis();

        while (true) {
            if (tl.notified.containsAll(comp)) break;
            Thread.sleep(100);
            assertTrue(then + maxTime > System.currentTimeMillis());
        }
    }

    @Test
    public void testNotify() throws Exception {
        IStoreClient<String, String> client0 =
                syncManagers[0].getStoreClient("local",
                                               String.class, String.class);
        IStoreClient<String, String> client2 =
                syncManagers[2].getStoreClient("local",
                                               new TypeReference<String>() {},
                                               new TypeReference<String>() {});
       
        TestListener t0 = new TestListener();
        TestListener t2 = new TestListener();
        client0.addStoreListener(t0);
        client2.addStoreListener(t2);
       
        client0.put("test0", "value");
        client2.put("test2", "value");

        HashSet<Update> c0 = new HashSet<Update>();
        c0.add(new Update("test0", UpdateType.LOCAL));
        c0.add(new Update("test2", UpdateType.REMOTE));
        HashSet<Update> c2 = new HashSet<Update>();
        c2.add(new Update("test0", UpdateType.REMOTE));
        c2.add(new Update("test2", UpdateType.LOCAL));
       
        waitForNotify(t0, c0, 2000);
        waitForNotify(t2, c2, 2000);
        assertEquals(2, t0.notified.size());
        assertEquals(2, t2.notified.size());
       
        t0.notified.clear();
        t2.notified.clear();
       
        Versioned<String> v0 = client0.get("test0");
        v0.setValue("newvalue");
        client0.put("test0", v0);

        Versioned<String> v2 = client0.get("test2");
        v2.setValue("newvalue");
        client2.put("test2", v2);

        waitForNotify(t0, c0, 2000);
        waitForNotify(t2, c2, 2000);
        assertEquals(2, t0.notified.size());
        assertEquals(2, t2.notified.size());

        t0.notified.clear();
        t2.notified.clear();
       
        client0.delete("test0");
        client2.delete("test2");

        waitForNotify(t0, c0, 2000);
        waitForNotify(t2, c2, 2000);
        assertEquals(2, t0.notified.size());
        assertEquals(2, t2.notified.size());
    }
   
    @Test
    public void testAddNode() throws Exception {
        waitForFullMesh(2000);
        IStoreClient<String, String> client0 =
                syncManagers[0].getStoreClient("global",
                                               String.class, String.class);
        IStoreClient<String, String> client1 =
                syncManagers[1].getStoreClient("global",
                                               String.class, String.class);
        client0.put("key", "value");
        waitForValue(client1, "key", "value", 2000, "client1");
       
        nodes.add(new Node("localhost", 40105, (short)5, (short)5));
        SyncManager[] sms = Arrays.copyOf(syncManagers,
                                          syncManagers.length + 1);
        FloodlightModuleContext[] fmcs =
                Arrays.copyOf(moduleContexts,
                              moduleContexts.length + 1);
        sms[syncManagers.length] = new SyncManager();
        fmcs[moduleContexts.length] = new FloodlightModuleContext();
        nodeString = mapper.writeValueAsString(nodes);

        setupSyncManager(fmcs[moduleContexts.length],
                         sms[syncManagers.length],
                         nodes.get(syncManagers.length));
        syncManagers = sms;
        moduleContexts = fmcs;

        for(int i = 0; i < 4; i++) {
            moduleContexts[i].addConfigParam(syncManagers[i],
                                             "nodes", nodeString);
            syncManagers[i].doUpdateConfiguration();
        }
        waitForFullMesh(2000);

        IStoreClient<String, String> client4 =
                syncManagers[4].getStoreClient("global",
                                               String.class, String.class);
        client4.put("newkey", "newvalue");
        waitForValue(client4, "key", "value", 2000, "client4");
        waitForValue(client0, "newkey", "newvalue", 2000, "client0");
    }
   
    @Test
    public void testRemoveNode() throws Exception {
        waitForFullMesh(2000);
        IStoreClient<String, String> client0 =
                syncManagers[0].getStoreClient("global",
                                               String.class, String.class);
        IStoreClient<String, String> client1 =
                syncManagers[1].getStoreClient("global",
                                               String.class, String.class);
        IStoreClient<String, String> client2 =
                syncManagers[2].getStoreClient("global",
                                               String.class, String.class);

        client0.put("key", "value");
        waitForValue(client1, "key", "value", 2000, "client1");
       
        nodes.remove(0);
        nodeString = mapper.writeValueAsString(nodes);

        SyncManager oldNode = syncManagers[0];
        syncManagers = Arrays.copyOfRange(syncManagers, 1, 4);
        moduleContexts = Arrays.copyOfRange(moduleContexts, 1, 4);

        try {
            for(int i = 0; i < syncManagers.length; i++) {
                moduleContexts[i].addConfigParam(syncManagers[i],
                                                 "nodes", nodeString);
                syncManagers[i].doUpdateConfiguration();
                waitForConnection(syncManagers[i], (short)1, false, 2000);
            }
        } finally {
            oldNode.shutdown();
        }
        waitForFullMesh(2000);

        client1.put("newkey", "newvalue");
        waitForValue(client2, "key", "value", 2000, "client4");
        waitForValue(client2, "newkey", "newvalue", 2000, "client0");
    }

    @Test
    public void testChangeNode() throws Exception {
        waitForFullMesh(2000);
        IStoreClient<String, String> client0 =
                syncManagers[0].getStoreClient("global",
                                               String.class, String.class);
        IStoreClient<String, String> client2 =
                syncManagers[2].getStoreClient("global",
                                               String.class, String.class);
        client0.put("key", "value");
        waitForValue(client2, "key", "value", 2000, "client2");

        nodes.set(2, new Node("localhost", 50103, (short)3, (short)1));
        nodeString = mapper.writeValueAsString(nodes);

        for(int i = 0; i < syncManagers.length; i++) {
            moduleContexts[i].addConfigParam(syncManagers[i],
                                             "nodes", nodeString);
            syncManagers[i].doUpdateConfiguration();
        }
        waitForFullMesh(2000);
       
        waitForValue(client2, "key", "value", 2000, "client2");
        client2 = syncManagers[2].getStoreClient("global",
                                                 String.class, String.class);
        client0.put("key", "newvalue");
        waitForValue(client2, "key", "newvalue", 2000, "client2");
    }

    /**
     * Do a brain-dead performance test with one thread writing and waiting
     * for the values on the other node.  The result get printed to the log
     */
    public void testSimpleWritePerformance(String store) throws Exception {
        waitForFullMesh(5000);
       
        final int count = 1000000;

        IStoreClient<String, String> client0 =
                syncManagers[0].getStoreClient(store,
                                               String.class, String.class);
        IStoreClient<String, String> client2 =
                syncManagers[2].getStoreClient(store,
                                               String.class, String.class);
       
        long then = System.currentTimeMillis();
       
        for (int i = 1; i <= count; i++) {
            client0.put(""+i, ""+i);
        }

        long donewriting = System.currentTimeMillis();

        waitForValue(client2, ""+count, null, count, "client2");
       
        long now = System.currentTimeMillis();
       
        logger.info("Simple write ({}): {} values in {}+/-100 " +
                    "millis ({} synced writes/s) ({} local writes/s)",
                    new Object[]{store, count, (now-then),
                                 1000.0*count/(now-then),
                                 1000.0*count/(donewriting-then)});

    }
   
    @Test
    @Ignore // ignored just to speed up routine tests
    public void testPerfSimpleWriteLocal() throws Exception {
        testSimpleWritePerformance("local");
    }

    @Test
    @Ignore // ignored just to speed up routine tests
    public void testPerfSimpleWriteGlobal() throws Exception {
        testSimpleWritePerformance("global");
    }

    @Test
    @Ignore
    public void testPerfOneNode() throws Exception {
        tearDown();
        tp = new ThreadPool();
        tp.init(null);
        tp.startUp(null);
        nodes = new ArrayList<Node>();
        nodes.add(new Node("localhost", 40101, (short)1, (short)1));
        nodeString = mapper.writeValueAsString(nodes);
        SyncManager sm = new SyncManager();
        FloodlightModuleContext fmc = new FloodlightModuleContext();
        setupSyncManager(fmc, sm, nodes.get(0));
        fmc.addService(ISyncService.class, sm);
        SyncTorture st = new SyncTorture();
        //fmc.addConfigParam(st, "iterations", "1");
        st.init(fmc);
        st.startUp(fmc);
        Thread.sleep(10000);
    }
}
TOP

Related Classes of org.sdnplatform.sync.internal.SyncManagerTest$Update

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.