org.limewire.mojito.CacheForwardTest.java Source code

Java tutorial

Introduction

Here is the source code for org.limewire.mojito.CacheForwardTest.java

Source

/*
 * Mojito Distributed Hash Table (Mojito DHT)
 * Copyright (C) 2006-2007 LimeWire LLC
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package org.limewire.mojito;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;

import junit.framework.TestSuite;

import org.limewire.collection.PatriciaTrie;
import org.limewire.collection.Trie;
import org.limewire.collection.TrieUtils;
import org.limewire.mojito.concurrent.CallableDHTTask;
import org.limewire.mojito.concurrent.DHTFuture;
import org.limewire.mojito.concurrent.DHTTask;
import org.limewire.mojito.db.DHTValue;
import org.limewire.mojito.db.DHTValueEntity;
import org.limewire.mojito.db.DHTValueType;
import org.limewire.mojito.db.impl.DHTValueImpl;
import org.limewire.mojito.exceptions.DHTException;
import org.limewire.mojito.result.Result;
import org.limewire.mojito.result.StoreResult;
import org.limewire.mojito.routing.Contact;
import org.limewire.mojito.routing.Version;
import org.limewire.mojito.settings.BucketRefresherSettings;
import org.limewire.mojito.settings.DatabaseSettings;
import org.limewire.mojito.settings.KademliaSettings;
import org.limewire.mojito.settings.RouteTableSettings;
import org.limewire.mojito.util.UnitTestUtils;
import org.limewire.security.SecurityToken;
import org.limewire.util.StringUtils;

@SuppressWarnings("null")
public class CacheForwardTest extends MojitoTestCase {

    private static final int PORT = 3000;

    /*static {
    System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.NoOpLog");
    }*/

    public CacheForwardTest(String name) {
        super(name);
    }

    public static TestSuite suite() {
        return buildTestSuite(CacheForwardTest.class);
    }

    public static void main(String[] args) throws Exception {
        junit.textui.TestRunner.run(suite());
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        setLocalIsPrivate(false);
        RouteTableSettings.INCOMING_REQUESTS_UNKNOWN.setValue(true);
    }

    @SuppressWarnings("unchecked")
    public void testGetSecurityToken() throws Exception {

        MojitoDHT dht1 = null;
        MojitoDHT dht2 = null;

        try {

            // Setup the first instance so that it thinks it's bootstrapping
            dht1 = MojitoFactory.createDHT();
            dht1.bind(2000);
            dht1.start();
            Context context1 = (Context) dht1;

            UnitTestUtils.setBootstrapping(dht1, true);
            assertFalse(dht1.isBootstrapped());
            assertTrue(context1.isBootstrapping());

            // And setup the second instance so that it thinks it's bootstrapped 
            dht2 = MojitoFactory.createDHT();
            dht2.bind(3000);
            dht2.start();
            Context context2 = (Context) dht2;

            UnitTestUtils.setBootstrapped(dht2, true);
            assertTrue(dht2.isBootstrapped());
            assertFalse(context2.isBootstrapping());

            // Get the SecurityToken...
            Class clazz = Class.forName("org.limewire.mojito.manager.StoreProcess$GetSecurityTokenHandler");
            Constructor<DHTTask<Result>> con = clazz.getDeclaredConstructor(Context.class, Contact.class);
            con.setAccessible(true);

            DHTTask<Result> task = con.newInstance(context2, context1.getLocalNode());
            CallableDHTTask<Result> callable = new CallableDHTTask<Result>(task);

            try {
                Result result = callable.call();
                clazz = Class.forName("org.limewire.mojito.manager.StoreProcess$GetSecurityTokenResult");
                Method m = clazz.getDeclaredMethod("getSecurityToken", new Class[0]);
                m.setAccessible(true);

                SecurityToken securityToken = (SecurityToken) m.invoke(result, new Object[0]);
                assertNotNull(securityToken);
            } catch (ExecutionException err) {
                assertInstanceof(DHTException.class, err.getCause());
                fail("DHT-1 did not return a SecurityToken", err);
            }

        } finally {
            if (dht1 != null) {
                dht1.close();
            }
            if (dht2 != null) {
                dht2.close();
            }
        }
    }

    public void disabledtestCacheForward() throws Exception {
        // This test is testing a disabled feature and keeps failing. 
        // We disable this test for now. See LWC-2778 for detail

        final long waitForNodes = 1000; // ms
        final long BUCKET_REFRESH = 1 * 1000;
        // it takes my machine 8.5 seconds to bootstrap the 3*k nodes and the build machine
        // is faster than mine. So 8.5 seconds should be enough for the build machine to 
        // bootstrapp the 3*k nodes. To be safe, we set BOOTSTRAP_TIME to be 10 seconds. 
        // We use this value as BUCKET_REFRESHER_DELAY since we want all nodes finish 
        // bootstrapping (joining the network) before pinging nearest neighbors. 
        final long BOOTSTRAP_TIME = 10 * 1000;

        //ContextSettings.SEND_SHUTDOWN_MESSAGE.setValue(false);
        BucketRefresherSettings.BUCKET_REFRESHER_PING_NEAREST.setValue(BUCKET_REFRESH);
        BucketRefresherSettings.BUCKET_REFRESHER_DELAY.setValue(BOOTSTRAP_TIME);
        DatabaseSettings.DELETE_VALUE_IF_FURTHEST_NODE.setValue(false);
        int k = KademliaSettings.REPLICATION_PARAMETER.getValue();

        // KUID valueId = KUID.create("40229239B68FFA66575E59D0AB1F685AD3191960");
        KUID valueId = KUID.createRandomID();

        Map<KUID, MojitoDHT> dhts = new HashMap<KUID, MojitoDHT>();
        MojitoDHT first = null;
        try {
            for (int i = 0; i < 3 * k; i++) {
                MojitoDHT dht = MojitoFactory.createDHT("DHT-" + i);
                dht.bind(PORT + i);
                dht.start();

                if (i > 0) {
                    Thread.sleep(100);
                    dht.bootstrap(new InetSocketAddress("localhost", PORT)).get();
                    // the bootstrapper needs to ping every joining node.
                    first.ping(new InetSocketAddress("localhost", PORT + i)).get();
                } else {
                    first = dht;
                }

                dhts.put(dht.getLocalNodeID(), dht);
            }

            first.bootstrap(new InetSocketAddress("localhost", PORT + 1)).get();
            Thread.sleep(waitForNodes);

            // Sort all KUIDs by XOR distance and use the Node as 
            // creator that's furthest away from the value ID so
            // that it never cannot be member of the k-closest Nodes
            Trie<KUID, KUID> trie = new PatriciaTrie<KUID, KUID>(KUID.KEY_ANALYZER);
            for (KUID id : dhts.keySet()) {
                trie.put(id, id);
            }
            List<KUID> idsByXorDistance = TrieUtils.select(trie, valueId, trie.size());

            // Creator happens to be the furthest of the k-closest Nodes. 
            //MojitoDHT creator = dhts.get(idsByXorDistance.get(k-1)); 

            // Use the furthest Node as the creator.
            MojitoDHT creator = dhts.get(idsByXorDistance.get(idsByXorDistance.size() - 1));
            // Store the value
            DHTValue value = new DHTValueImpl(DHTValueType.TEST, Version.ZERO,
                    StringUtils.toUTF8Bytes("Hello World"));
            StoreResult evt = creator.put(valueId, value).get();

            // see LWC-2778. In case the root is UNKNOWN in others route table, 
            // we wait BOOTSTRAP_TIME (the delay of ping the nearest bucket)
            // so that every node has a chance to ping the root, hence realize 
            // the root is ALIVE
            boolean waiting = true;
            KUID rootsID = TrieUtils.select(trie, valueId, 1).get(0);
            for (Contact c : evt.getLocations()) {
                if (c.getNodeID().equals(rootsID)) {
                    waiting = false;
                    break;
                }
            }
            if (waiting) {
                for (Contact c : evt.getLocations()) {
                    Context dht = (Context) dhts.get(c.getNodeID());
                    dht.getDatabase().clear();
                }
                Thread.sleep(BOOTSTRAP_TIME);
                evt = creator.put(valueId, value).get();
            }

            assertEquals(k, evt.getLocations().size());

            // Give everybody time to process the store request
            Thread.sleep(waitForNodes);

            // And check the initial state
            Context closest = null;
            for (Contact remote : evt.getLocations()) {
                Context dht = (Context) dhts.get(remote.getNodeID());
                assertEquals(1, dht.getDatabase().getKeyCount());
                assertEquals(1, dht.getDatabase().getValueCount());
                for (DHTValueEntity dhtValue : dht.getDatabase().values()) {
                    assertEquals(valueId, dhtValue.getPrimaryKey());
                    assertEquals(value, dhtValue.getValue());
                    assertEquals(creator.getLocalNodeID(), dhtValue.getSecondaryKey());
                    assertEquals(creator.getLocalNodeID(), dhtValue.getSender().getNodeID());
                }

                if (closest == null) {
                    closest = dht;
                }
            }

            // Create a Node with the nearest possible Node ID
            // That means we set the Node ID to the Value ID
            Context nearest = (Context) MojitoFactory.createDHT("Nearest");
            Method m = nearest.getClass().getDeclaredMethod("setLocalNodeID", new Class[] { KUID.class });
            m.setAccessible(true);
            m.invoke(nearest, new Object[] { valueId });

            nearest.bind(PORT + 500);
            nearest.start();
            bootstrap(nearest, dhts.values());

            // Give everybody time to figure out whether to forward
            // a value or to remove it
            Thread.sleep(waitForNodes);

            // The Node with the nearest possible ID should have the value
            assertEquals(1, nearest.getDatabase().getValueCount());

            for (Contact remote : evt.getLocations()) {
                Context dht = (Context) dhts.get(remote.getNodeID());

                assertEquals("I dont have it?? " + dht.getLocalNodeID(), 1, dht.getDatabase().getKeyCount());
                assertEquals(1, dht.getDatabase().getValueCount());
                for (DHTValueEntity dhtValue : dht.getDatabase().values()) {
                    assertEquals(valueId, dhtValue.getPrimaryKey());
                    assertEquals(value, dhtValue.getValue());
                    assertEquals(creator.getLocalNodeID(), dhtValue.getSecondaryKey());
                    assertEquals(creator.getLocalNodeID(), dhtValue.getSender().getNodeID());
                }
            }

            // The 'nearest' Node received the value from the 
            // previous 'closest' Node
            assertEquals(1, nearest.getDatabase().getKeyCount());
            assertEquals(1, nearest.getDatabase().getValueCount());
            for (DHTValueEntity dhtValue : nearest.getDatabase().values()) {
                assertEquals(valueId, dhtValue.getPrimaryKey());
                assertEquals(value, dhtValue.getValue());
                assertEquals(creator.getLocalNodeID(), dhtValue.getSecondaryKey());

                // The closest Node send us the value!
                assertEquals(closest.getLocalNodeID(), dhtValue.getSender().getNodeID());
            }

            // Clear the Database but don't change the instanceId!
            // The other Nodes don't know that we cleared our DB
            // and will this not store values!
            nearest.getDatabase().clear();
            assertEquals(0, nearest.getDatabase().getKeyCount());
            assertEquals(0, nearest.getDatabase().getValueCount());
            bootstrap(nearest, dhts.values());

            // Give everybody time to figure out whether to forward
            // a value or to remove it
            Thread.sleep(waitForNodes);

            assertEquals(0, nearest.getDatabase().getKeyCount());
            assertEquals(0, nearest.getDatabase().getValueCount());

            // Change the instanceId and we'll asked to store the
            // value again!
            nearest.getLocalNode().nextInstanceID();
            bootstrap(nearest, dhts.values());

            // Give everybody time to figure out whether to forward
            // a value or to remove it
            Thread.sleep(waitForNodes);

            assertEquals(1, nearest.getDatabase().getKeyCount());
            assertEquals(1, nearest.getDatabase().getValueCount());
            for (DHTValueEntity dhtValue : nearest.getDatabase().values()) {
                assertEquals(valueId, dhtValue.getPrimaryKey());
                assertEquals(value, dhtValue.getValue());
                assertEquals(creator.getLocalNodeID(), dhtValue.getSecondaryKey());

                // The closest Node send us the value!
                assertEquals(closest.getLocalNodeID(), dhtValue.getSender().getNodeID());
            }

            // Pick a Node from the middle of the k-closest Nodes,
            // clear its Database and do the same test as with the
            // nearest Node above
            Context middle = null;
            int index = 0;
            for (Contact node : evt.getLocations()) {
                if (index == k / 2) {
                    middle = (Context) dhts.get(node.getNodeID());
                    break;
                }

                index++;
            }
            assertNotNull(middle);

            middle.getDatabase().clear();
            assertEquals(0, middle.getDatabase().getKeyCount());
            assertEquals(0, middle.getDatabase().getValueCount());
            bootstrap(middle, dhts.values());
            Thread.sleep(waitForNodes);

            assertEquals(0, middle.getDatabase().getKeyCount());
            assertEquals(0, middle.getDatabase().getValueCount());

            middle.getLocalNode().nextInstanceID();
            bootstrap(middle, dhts.values());
            Thread.sleep(waitForNodes);

            assertEquals(1, middle.getDatabase().getKeyCount());
            assertEquals(1, middle.getDatabase().getValueCount());
            for (DHTValueEntity dhtValue : middle.getDatabase().values()) {
                assertEquals(valueId, dhtValue.getPrimaryKey());
                assertEquals(value, dhtValue.getValue());
                assertEquals(creator.getLocalNodeID(), dhtValue.getSecondaryKey());

                // The nearest Node send us the value
                assertEquals(nearest.getLocalNodeID(), dhtValue.getSender().getNodeID());
            }

            // Check the final state. k + 1 Nodes should have the value!
            int count = 0;
            dhts.put(nearest.getLocalNodeID(), nearest);
            for (MojitoDHT dht : dhts.values()) {
                count += ((Context) dht).getDatabase().values().size();
            }

            // If the creator is a member of the k-closest Nodes then
            // make sure we're counting it as well
            for (Contact node : evt.getLocations()) {
                if (node.getNodeID().equals(creator.getLocalNodeID())) {
                    count++;
                    break;
                }
            }

            assertEquals(k + 1, count);

        } finally {
            for (MojitoDHT dht : dhts.values()) {
                dht.close();
            }
        }
    }

    /**
     * This test will fail if the deletion step of store-forwarding
     * is enabled.  Its a reminder to test that functionality if
     * we ever decide to enable it.
     */
    public void testCacheForwardAndDelete() {
        DatabaseSettings.DELETE_VALUE_IF_FURTHEST_NODE.revertToDefault();
        assertFalse(DatabaseSettings.DELETE_VALUE_IF_FURTHEST_NODE.getValue());
    }

    public void testStoreMultipleValues() throws Exception {
        MojitoDHT dht1 = null;
        MojitoDHT dht2 = null;

        try {
            dht1 = MojitoFactory.createDHT("DHT1");
            dht1.bind(2000);
            dht1.start();

            dht2 = MojitoFactory.createDHT("DHT2");
            dht2.bind(2001);
            dht2.start();

            dht1.bootstrap(new InetSocketAddress("localhost", 2001)).get();
            dht2.bootstrap(new InetSocketAddress("localhost", 2000)).get();

            Context context1 = (Context) dht1;

            KUID primaryKey1 = KUID.createRandomID();
            KUID primaryKey2 = KUID.createRandomID();

            DHTValue value1 = new DHTValueImpl(DHTValueType.TEST, Version.ZERO,
                    StringUtils.toUTF8Bytes("Hello World"));
            DHTValue value2 = new DHTValueImpl(DHTValueType.TEST, Version.ZERO, StringUtils.toUTF8Bytes("Foo Bar"));

            DHTValueEntity entity1 = DHTValueEntity.createFromValue(context1, primaryKey1, value1);
            DHTValueEntity entity2 = DHTValueEntity.createFromValue(context1, primaryKey2, value2);

            Collection<DHTValueEntity> entities = Arrays.asList(entity1, entity2);
            DHTFuture<StoreResult> future = context1.store(dht2.getLocalNode(), null, entities);
            /*  StoreResult result = */ future.get(); // TODO: should result be tested?

            assertEquals(2, dht2.getDatabase().getKeyCount());
            assertEquals(2, dht2.getDatabase().getValueCount());
        } finally {
            if (dht1 != null) {
                dht1.close();
            }
            if (dht2 != null) {
                dht2.close();
            }
        }
    }

    /**
     * Bootstraps the given Node from one of the other Nodes and makes sure a
     * Node isn't trying to bootstrap from itself which would fail.
     */
    private static void bootstrap(MojitoDHT dht, Collection<MojitoDHT> dhts)
            throws ExecutionException, InterruptedException {

        for (MojitoDHT other : dhts) {
            if (!dht.getLocalNodeID().equals(other.getLocalNodeID())) {
                InetSocketAddress addr = (InetSocketAddress) other.getContactAddress();
                dht.bootstrap(new InetSocketAddress("localhost", addr.getPort())).get();
                return;
            }
        }

        throw new IllegalStateException("Could not bootstrap: " + dht);
    }
}