/* * Copyright 2011 Thomas Bocek * * Licensed 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 net.tomp2p.p2p; import io.netty.buffer.ByteBuf; import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.NavigableSet; import java.util.SortedMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReferenceArray; import net.tomp2p.connection2.ChannelCreator; import net.tomp2p.futures.BaseFuture; import net.tomp2p.futures.BaseFutureAdapter; import net.tomp2p.futures.FutureChannelCreator; import net.tomp2p.futures.FutureDHT; import net.tomp2p.futures.FutureDirect; import net.tomp2p.futures.FutureForkJoin; import net.tomp2p.futures.FutureGet; import net.tomp2p.futures.FuturePut; import net.tomp2p.futures.FutureRemove; import net.tomp2p.futures.FutureResponse; import net.tomp2p.futures.FutureRouting; import net.tomp2p.futures.FutureShutdown; import net.tomp2p.message.Message2.Type; import net.tomp2p.p2p.builder.AddBuilder; import net.tomp2p.p2p.builder.BasicBuilder; import net.tomp2p.p2p.builder.GetBuilder; import net.tomp2p.p2p.builder.PutBuilder; import net.tomp2p.p2p.builder.RemoveBuilder; import net.tomp2p.p2p.builder.RoutingBuilder; import net.tomp2p.p2p.builder.SendBuilder; import net.tomp2p.p2p.builder.ShutdownBuilder; import net.tomp2p.peers.Number160; import net.tomp2p.peers.Number480; import net.tomp2p.peers.PeerAddress; import net.tomp2p.rpc.DigestInfo; import net.tomp2p.rpc.DigestResult; import net.tomp2p.rpc.DirectDataRPC; import net.tomp2p.rpc.QuitRPC; import net.tomp2p.rpc.SimpleBloomFilter; import net.tomp2p.rpc.StorageRPC; import net.tomp2p.storage.Data; import net.tomp2p.utils.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DistributedHashTable { private static final Logger logger = LoggerFactory.getLogger(DistributedHashTable.class); private final DistributedRouting routing; private final StorageRPC storeRCP; private final DirectDataRPC directDataRPC; private final QuitRPC quitRPC; public DistributedHashTable(DistributedRouting routing, StorageRPC storeRCP, DirectDataRPC directDataRPC, QuitRPC quitRPC) { this.routing = routing; this.storeRCP = storeRCP; this.directDataRPC = directDataRPC; this.quitRPC = quitRPC; } public FuturePut add(final AddBuilder builder) { final FuturePut futureDHT = new FuturePut(builder.getRequestP2PConfiguration().getMinimumResults(), new VotingSchemeDHT()); builder.getFutureChannelCreator().addListener(new BaseFutureAdapter<FutureChannelCreator>() { @Override public void operationComplete(final FutureChannelCreator future) throws Exception { if (future.isSuccess()) { final FutureRouting futureRouting = createRouting(builder, Type.REQUEST_1, future.getChannelCreator()); futureDHT.setFutureRouting(futureRouting); futureRouting.addListener(new BaseFutureAdapter<FutureRouting>() { @Override public void operationComplete(final FutureRouting futureRouting) throws Exception { if (futureRouting.isSuccess()) { logger.debug("adding lkey={} on {}", builder.getLocationKey(), futureRouting.getPotentialHits()); parallelRequests(builder.getRequestP2PConfiguration(), futureRouting.getPotentialHits(), futureDHT, false, future.getChannelCreator(), new OperationMapper<FuturePut>() { Map<PeerAddress, Collection<Number480>> rawData480 = new HashMap<PeerAddress, Collection<Number480>>(); @Override public FutureResponse create(ChannelCreator channelCreator, PeerAddress address) { return storeRCP.add(address, builder, channelCreator); } @Override public void response(FuturePut futureDHT) { futureDHT.setStoredKeys(builder.getLocationKey(), builder.getDomainKey(), rawData480); } @Override public void interMediateResponse(FutureResponse future) { // the future tells us that the communication was successful, but we // need to check the result if we could store it. if (future.isSuccess() && future.getResponse().isOk()) { rawData480.put(future.getRequest().getRecipient(), future .getResponse().getKeys(0).keys()); } } }); } else { futureDHT.setFailed("routing failed"); } } }); Utils.addReleaseListener(future.getChannelCreator(), futureDHT); } else { futureDHT.setFailed(future); } } }); return futureDHT; } /*public FutureDHT direct(final Number160 locationKey, final ByteBuf buffer, final boolean raw, final RoutingConfiguration routingConfiguration, final RequestP2PConfiguration p2pConfiguration, final FutureCreate<FutureDHT> futureCreate, final boolean cancelOnFinish, final boolean manualCleanup, final FutureChannelCreator futureChannelCreator, final ConnectionReservation connectionReservation) {*/ public FutureDirect direct(final SendBuilder builder) { final FutureDirect futureDHT = new FutureDirect(builder.getRequestP2PConfiguration().getMinimumResults(), new VotingSchemeDHT()); builder.getFutureChannelCreator().addListener(new BaseFutureAdapter<FutureChannelCreator>() { @Override public void operationComplete(final FutureChannelCreator future) throws Exception { if (future.isSuccess()) { final FutureRouting futureRouting = createRouting(builder, Type.REQUEST_1, future.getChannelCreator()); futureDHT.setFutureRouting(futureRouting); futureRouting.addListener(new BaseFutureAdapter<FutureRouting>() { @Override public void operationComplete(FutureRouting futureRouting) throws Exception { if (futureRouting.isSuccess()) { logger.debug("storing lkey={} on {}", builder.getLocationKey(), futureRouting.getPotentialHits()); parallelRequests(builder.getRequestP2PConfiguration(), futureRouting.getPotentialHits(), futureDHT, builder.isCancelOnFinish(), future.getChannelCreator(), new OperationMapper<FutureDirect>() { Map<PeerAddress, ByteBuf> rawChannels = new HashMap<PeerAddress, ByteBuf>(); Map<PeerAddress, Object> rawObjects = new HashMap<PeerAddress, Object>(); @Override public FutureResponse create(ChannelCreator channelCreator, PeerAddress address) { return directDataRPC.send(address, builder, channelCreator); } @Override public void response(FutureDirect futureDHT) { if (builder.isRaw()) futureDHT.setDirectData1(rawChannels); else futureDHT.setDirectData2(rawObjects); } @Override public void interMediateResponse(FutureResponse future) { // the future tells us that the communication was successful, but we need to check the result if we could store it. if (future.isSuccess() && future.getResponse().isOk()) { if (builder.isRaw()) { rawChannels.put(future.getRequest().getRecipient(), future.getResponse().getBuffer(0).buffer()); } else { try { rawObjects.put(future.getRequest().getRecipient(), future.getResponse().getBuffer(0).object()); } catch (ClassNotFoundException e) { rawObjects.put(future.getRequest().getRecipient(), e); } catch (IOException e) { rawObjects.put(future.getRequest().getRecipient(), e); } } } } }); } else { futureDHT.setFailed("routing failed"); } } }); Utils.addReleaseListener(future.getChannelCreator(), futureDHT); } else { futureDHT.setFailed(future); } } }); return futureDHT; } public FuturePut put(final PutBuilder putBuilder) { final FuturePut futureDHT = new FuturePut( putBuilder.getRequestP2PConfiguration().getMinimumResults(), new VotingSchemeDHT()); putBuilder.getFutureChannelCreator().addListener(new BaseFutureAdapter<FutureChannelCreator>() { @Override public void operationComplete(final FutureChannelCreator future) throws Exception { if (future.isSuccess()) { final FutureRouting futureRouting = createRouting(putBuilder, Type.REQUEST_1, future.getChannelCreator()); futureDHT.setFutureRouting(futureRouting); futureRouting.addListener(new BaseFutureAdapter<FutureRouting>() { @Override public void operationComplete(final FutureRouting futureRouting) throws Exception { if (futureRouting.isSuccess()) { logger.debug("storing lkey={} on {}", putBuilder.getLocationKey(), futureRouting.getPotentialHits()); parallelRequests(putBuilder.getRequestP2PConfiguration(), futureRouting.getPotentialHits(), futureDHT, false, future.getChannelCreator(), new OperationMapper<FuturePut>() { Map<PeerAddress, Collection<Number480>> rawData480 = new HashMap<PeerAddress, Collection<Number480>>(); @Override public FutureResponse create(final ChannelCreator channelCreator, final PeerAddress address) { boolean protectEntry = Utils.checkEntryProtection(putBuilder .getDataMap()); // TODO: content protection boolean putIfAbsent = putBuilder.isPutIfAbsent(); if (putIfAbsent) { return storeRCP.putIfAbsent(address, putBuilder, channelCreator); } else { return storeRCP.put(address, putBuilder, channelCreator); } } @Override public void response(final FuturePut futureDHT) { futureDHT.setStoredKeys(putBuilder.getLocationKey(), putBuilder.getDomainKey(), rawData480); } @Override public void interMediateResponse(final FutureResponse future) { // the future tells us that the communication was successful, to check // the result if we could store it. if (future.isSuccess() && future.getResponse().isOk()) { rawData480.put(future.getRequest().getRecipient(), future .getResponse().getKeys(0).keys()); } } }); } else { futureDHT.setFailed("routing failed"); } } }); Utils.addReleaseListener(future.getChannelCreator(), futureDHT); } else { futureDHT.setFailed(future); } } }); return futureDHT; } /*public FutureDHT get(final Number160 locationKey, final Number160 domainKey, final Collection<Number160> contentKeys, final SimpleBloomFilter<Number160> keyBloomFilter, final SimpleBloomFilter<Number160> contentBloomFilter, final RoutingConfiguration routingConfiguration, final RequestP2PConfiguration p2pConfiguration, final EvaluatingSchemeDHT evaluationScheme, final boolean signMessage, final boolean digest, final boolean returnBloomFilter, final boolean range, final boolean isManualCleanup, final FutureChannelCreator futureChannelCreator, final ConnectionReservation connectionReservation) {*/ public FutureGet get(final GetBuilder builder) { final FutureGet futureDHT = new FutureGet(builder.getRequestP2PConfiguration().getMinimumResults(), new VotingSchemeDHT()); builder.getFutureChannelCreator().addListener(new BaseFutureAdapter<FutureChannelCreator>() { @Override public void operationComplete(final FutureChannelCreator future) throws Exception { if (future.isSuccess()) { final FutureRouting futureRouting = createRouting(builder, Type.REQUEST_2, future.getChannelCreator()); futureDHT.setFutureRouting(futureRouting); futureRouting.addListener(new BaseFutureAdapter<FutureRouting>() { @Override public void operationComplete(FutureRouting futureRouting) throws Exception { if (futureRouting.isSuccess()) { logger.debug("found direct hits for get: {}", futureRouting.getDirectHits()); // this adjust is based on results from the routing process, if we find the same data on 2 // peers, we want to get it from one only. Unless its digest, then we want to know exactly what is going on RequestP2PConfiguration p2pConfiguration2 = builder.isDigest() || builder.isRange() ? builder.getRequestP2PConfiguration() : adjustConfiguration(builder.getRequestP2PConfiguration(), futureRouting.getDirectHitsDigest()); // store in direct hits parallelRequests(p2pConfiguration2, builder.isRange() ? futureRouting.getPotentialHits() : futureRouting.getDirectHits(), futureDHT, true, future .getChannelCreator(), new OperationMapper<FutureGet>() { Map<PeerAddress, Map<Number480, Data>> rawData = new HashMap<PeerAddress, Map<Number480, Data>>(); Map<PeerAddress, DigestResult> rawDigest = new HashMap<PeerAddress, DigestResult>(); @Override public FutureResponse create(ChannelCreator channelCreator, PeerAddress address) { return storeRCP.get(address, builder, channelCreator); } @Override public void response(FutureGet futureDHT) { if (builder.isDigest()) { futureDHT.setReceivedDigest(builder.getLocationKey(), builder.getDomainKey(), rawDigest); } else { futureDHT.setReceivedData(builder.getLocationKey(), builder.getDomainKey(), rawData); } } @Override public void interMediateResponse(FutureResponse future) { // the future tells us that the communication was successful, which is ok for digest if (future.isSuccess()) { if (builder.isDigest()) { SimpleBloomFilter<Number160> sbf1 = future.getResponse() .getBloomFilter(0); SimpleBloomFilter<Number160> sbf2 = future.getResponse() .getBloomFilter(1); final DigestResult digest; if (sbf1 == null && sbf2 == null) { Map<Number480, Number160> keyDigest = future.getResponse() .getKeysMap(0).keysMap(); digest = new DigestResult(keyDigest); } else { digest = new DigestResult(sbf1, sbf2); } rawDigest.put(future.getRequest().getRecipient(), digest); } else { rawData.put(future.getRequest().getRecipient(), future .getResponse().getDataMap(0).dataMap()); } logger.debug("set data from {}", future.getRequest().getRecipient()); } } }); } else { futureDHT.setFailed("routing failed"); } } }); Utils.addReleaseListener(future.getChannelCreator(), futureDHT); } else { futureDHT.setFailed(future); } } }); return futureDHT; } /*public FutureDHT remove(final Number160 locationKey, final Number160 domainKey, final Collection<Number160> contentKeys, final RoutingConfiguration routingConfiguration, final RequestP2PConfiguration p2pConfiguration, final boolean returnResults, final boolean signMessage, final boolean isManualCleanup, FutureCreate<FutureDHT> futureCreate, final FutureChannelCreator futureChannelCreator, final ConnectionReservation connectionReservation) {*/ public FutureRemove remove(final RemoveBuilder builder) { final FutureRemove futureDHT = new FutureRemove(builder.getRequestP2PConfiguration().getMinimumResults(), new VotingSchemeDHT()); builder.getFutureChannelCreator().addListener(new BaseFutureAdapter<FutureChannelCreator>() { @Override public void operationComplete(final FutureChannelCreator future) throws Exception { if (future.isSuccess()) { final FutureRouting futureRouting = createRouting(builder, Type.REQUEST_2, future.getChannelCreator()); //TODO: add content keys to the routing builder futureDHT.setFutureRouting(futureRouting); futureRouting.addListener(new BaseFutureAdapter<FutureRouting>() { @Override public void operationComplete(FutureRouting futureRouting) throws Exception { if (futureRouting.isSuccess()) { logger.debug("found direct hits for remove: {}", futureRouting.getDirectHits()); RequestP2PConfiguration p2pConfiguration2 = adjustConfiguration(builder.getRequestP2PConfiguration(), futureRouting.getDirectHitsDigest()); parallelRequests(p2pConfiguration2, futureRouting.getDirectHits(), futureDHT, false, future.getChannelCreator(), new OperationMapper<FutureRemove>() { Map<PeerAddress, Map<Number480, Data>> rawDataResult = new HashMap<PeerAddress, Map<Number480, Data>>(); Map<PeerAddress, Collection<Number480>> rawDataNoResult = new HashMap<PeerAddress, Collection<Number480>>(); @Override public FutureResponse create(ChannelCreator channelCreator, PeerAddress address) { return storeRCP.remove(address, builder, channelCreator); } @Override public void response(FutureRemove futureDHT) { if (builder.isReturnResults()) futureDHT .setReceivedData(builder.getLocationKey(), builder.getDomainKey(), rawDataResult); else futureDHT.setStoredKeys(builder.getLocationKey(), builder.getDomainKey(), rawDataNoResult); } @Override public void interMediateResponse(FutureResponse future) { // the future tells us that the communication was successful, but we need to check the result if we could store it. if (future.isSuccess() && future.getResponse().isOk()) { if (builder.isReturnResults()) { rawDataResult.put(future.getRequest().getRecipient(), future.getResponse().getDataMap(0).dataMap()); } else { rawDataNoResult.put(future.getRequest().getRecipient(), future.getResponse().getKeys(0).keys()); } } } }); } else futureDHT.setFailed("routing failed"); } }); Utils.addReleaseListener(future.getChannelCreator(), futureDHT); } else { futureDHT.setFailed(future); } } }); return futureDHT; } /** * Send a friendly shutdown message to your close neighbors. * * @param connectionReservation * The connection reservation The routing configuration * @param builder * All other options * @return future shutdown */ public FutureShutdown quit(final ShutdownBuilder builder) { final FutureShutdown futureShutdown = new FutureShutdown(); builder.getFutureChannelCreator().addListener(new BaseFutureAdapter<FutureChannelCreator>() { @Override public void operationComplete(final FutureChannelCreator future) throws Exception { if (future.isSuccess()) { // content key and domain key are not important when sending a friendly shutdown final FutureRouting futureRouting = createRouting(builder, Type.REQUEST_2, future.getChannelCreator()); futureShutdown.setFutureRouting(futureRouting); futureRouting.addListener(new BaseFutureAdapter<FutureRouting>() { @Override public void operationComplete(final FutureRouting futureRouting) throws Exception { if (futureRouting.isSuccess()) { if (logger.isDebugEnabled()) { logger.debug("found peer to send the quit message: " + futureRouting.getPotentialHits()); } parallelRequests(builder.getRequestP2PConfiguration(), futureRouting.getPotentialHits(), futureShutdown, false, future.getChannelCreator(), new OperationMapper<FutureShutdown>() { @Override public FutureResponse create(final ChannelCreator channelCreator, final PeerAddress address) { return quitRPC.quit(address, builder, channelCreator); } @Override public void response(final FutureShutdown future) { future.setDone(); } // its fire and forget, so don't bother checking the future success @Override public void interMediateResponse(final FutureResponse future) { // contactedPeers can be accessed by several threads futureShutdown.report(future.getRequest().getRecipient(), future.isSuccess()); } }); } else { futureShutdown.setFailed("routing failed"); } } }); Utils.addReleaseListener(future.getChannelCreator(), futureShutdown); } else { futureShutdown.setFailed(future); } } }); return futureShutdown; } /** * Creates RPCs and executes them parallel. * * @param p2pConfiguration * The configuration that specifies e.g. how many parallel requests there are. * @param queue * The sorted set that will be queries. The first RPC takes the first in the queue. * @param futureDHT * The future object that tracks the progress * @param cancleOnFinish * Set to true if the operation should be canceled (e.g. file transfer) if the future has finished. * @param operation * The operation that creates the request */ public static <K extends FutureDHT> K parallelRequests(final RequestP2PConfiguration p2pConfiguration, final NavigableSet<PeerAddress> queue, final boolean cancleOnFinish, final FutureChannelCreator futureChannelCreator, final OperationMapper<K> operation, final K futureDHT) { futureChannelCreator.addListener(new BaseFutureAdapter<FutureChannelCreator>() { @Override public void operationComplete(final FutureChannelCreator future) throws Exception { if (future.isSuccess()) { parallelRequests(p2pConfiguration, queue, futureDHT, cancleOnFinish, future.getChannelCreator(), operation); Utils.addReleaseListener(future.getChannelCreator(), futureDHT); } else { futureDHT.setFailed(future); } } }); return futureDHT; } private static <K extends FutureDHT> void parallelRequests(RequestP2PConfiguration p2pConfiguration, NavigableSet<PeerAddress> queue, K future, boolean cancleOnFinish, ChannelCreator channelCreator, OperationMapper<K> operation) { if (p2pConfiguration.getMinimumResults() == 0) { operation.response(future); return; } FutureResponse[] futures = new FutureResponse[p2pConfiguration.getParallel()]; // here we split min and pardiff, par=min+pardiff loopRec(queue, p2pConfiguration.getMinimumResults(), new AtomicInteger(0), p2pConfiguration.getMaxFailure(), p2pConfiguration.getParallelDiff(), new AtomicReferenceArray<FutureResponse>(futures), future, cancleOnFinish, channelCreator, operation); } private static <K extends FutureDHT> void loopRec(final NavigableSet<PeerAddress> queue, final int min, final AtomicInteger nrFailure, final int maxFailure, final int parallelDiff, final AtomicReferenceArray<FutureResponse> futures, final K futureDHT, final boolean cancelOnFinish, final ChannelCreator channelCreator, final OperationMapper<K> operation) { // final int parallel=min+parallelDiff; int active = 0; for (int i = 0; i < min + parallelDiff; i++) { if (futures.get(i) == null) { PeerAddress next = queue.pollFirst(); if (next != null) { active++; FutureResponse futureResponse = operation.create(channelCreator, next); futures.set(i, futureResponse); futureDHT.addRequests(futureResponse); } } else { active++; } } if (active == 0) { operation.response(futureDHT); if (cancelOnFinish) { cancel(futures); } return; } if (logger.isDebugEnabled()) { logger.debug("fork/join status: " + min + "/" + active + " (" + parallelDiff + ")"); } FutureForkJoin<FutureResponse> fp = new FutureForkJoin<FutureResponse>(Math.min(min, active), false, futures); fp.addListener(new BaseFutureAdapter<FutureForkJoin<FutureResponse>>() { @Override public void operationComplete(final FutureForkJoin<FutureResponse> future) throws Exception { for (FutureResponse futureResponse : future.getCompleted()) { operation.interMediateResponse(futureResponse); } // we are finished if forkjoin says so or we got too many // failures if (future.isSuccess() || nrFailure.incrementAndGet() > maxFailure) { if (cancelOnFinish) { cancel(futures); } operation.response(futureDHT); } else { loopRec(queue, min - future.getSuccessCounter(), nrFailure, maxFailure, parallelDiff, futures, futureDHT, cancelOnFinish, channelCreator, operation); } } }); } private FutureRouting createRouting(BasicBuilder<?> builder, Type type, ChannelCreator channelCreator) { //Number160 locationKey, Number160 domainKey, //Collection<Number160> contentKeys, RoutingConfiguration routingConfiguration, //RequestP2PConfiguration p2pConfiguration RoutingBuilder routingBuilder = builder.createBuilder(builder.getRequestP2PConfiguration(), builder.getRoutingConfiguration()); routingBuilder.setLocationKey(builder.getLocationKey()); routingBuilder.setDomainKey(builder.getDomainKey()); /*if(contentKeys.size() == 1) { routingBuilder.setContentKey(contentKeys.iterator().next()); } else if(contentKeys.size() > 1) { DefaultBloomfilterFactory factory = new DefaultBloomfilterFactory(); SimpleBloomFilter<Number160> bf = factory.createContentKeyBloomFilter(); for(Number160 contentKey: contentKeys) { bf.add(contentKey); } routingBuilder.setKeyBloomFilter(bf); }*/ return routing.route(routingBuilder, type, channelCreator); } /** * Cancel the future that causes the underlying futures to cancel as well. */ private static void cancel(final AtomicReferenceArray<FutureResponse> futures) { int len = futures.length(); for (int i = 0; i < len; i++) { BaseFuture baseFuture = futures.get(i); if (baseFuture != null) { baseFuture.cancel(); } } } /** * Adjusts the number of minimum requests in the P2P configuration. When we query x peers for the get() operation * and they have y different data stored (y <= x), then set the minimum to y or to the value the user set if its * smaller. If no data is found, then return 0, so we don't start P2P RPCs. * * @param p2pConfiguration * The old P2P configuration with the user specified minimum result * @param directHitsDigest * The digest information from the routing process * @return The new RequestP2PConfiguration with the new minimum result */ public static RequestP2PConfiguration adjustConfiguration(final RequestP2PConfiguration p2pConfiguration, final SortedMap<PeerAddress, DigestInfo> directHitsDigest) { int size = directHitsDigest.size(); int requested = p2pConfiguration.getMinimumResults(); if (size >= requested) { return p2pConfiguration; } else { return new RequestP2PConfiguration(size, p2pConfiguration.getMaxFailure(), p2pConfiguration.getParallelDiff()); } } }