/**
* Copyright 2016 Yahoo Inc.
*
* 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 com.yahoo.pulsar.client.impl;
import static java.lang.String.format;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.concurrent.CompletableFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.yahoo.pulsar.client.api.PulsarClientException;
import com.yahoo.pulsar.common.api.Commands;
import com.yahoo.pulsar.common.naming.DestinationName;
import com.yahoo.pulsar.common.partition.PartitionedTopicMetadata;
import io.netty.buffer.ByteBuf;
class BinaryProtoLookupService implements LookupService {
private final PulsarClientImpl client;
protected final InetSocketAddress serviceAddress;
private final boolean useTls;
public BinaryProtoLookupService(PulsarClientImpl client, String serviceUrl, boolean useTls)
throws PulsarClientException {
this.client = client;
this.useTls = useTls;
URI uri;
try {
uri = new URI(serviceUrl);
this.serviceAddress = new InetSocketAddress(uri.getHost(), uri.getPort());
} catch (Exception e) {
log.error("Invalid service-url {} provided {}", serviceUrl, e.getMessage(), e);
throw new PulsarClientException.InvalidServiceURL(e);
}
}
/**
* Calls broker binaryProto-lookup api to find broker-service address which can serve a given topic.
*
* @param destination: topic-name
* @return broker-socket-address that serves given topic
*/
public CompletableFuture<InetSocketAddress> getBroker(DestinationName destination) {
return findBroker(serviceAddress, false, destination);
}
/**
* calls broker binaryProto-lookup api to get metadata of partitioned-topic.
*
*/
public CompletableFuture<PartitionedTopicMetadata> getPartitionedTopicMetadata(DestinationName destination) {
return getPartitionedTopicMetadata(serviceAddress, destination);
}
private CompletableFuture<InetSocketAddress> findBroker(InetSocketAddress socketAddress, boolean authoritative,
DestinationName destination) {
CompletableFuture<InetSocketAddress> addressFuture = new CompletableFuture<InetSocketAddress>();
client.getCnxPool().getConnection(socketAddress).thenAccept(clientCnx -> {
long requestId = client.newRequestId();
ByteBuf request = Commands.newLookup(destination.toString(), authoritative, requestId);
clientCnx.newLookup(request, requestId).thenAccept(lookupDataResult -> {
URI uri = null;
try {
// (1) build response broker-address
if (useTls) {
uri = new URI(lookupDataResult.brokerUrlTls);
} else {
String serviceUrl = lookupDataResult.brokerUrl;
uri = new URI(serviceUrl);
}
InetSocketAddress responseBrokerAddress = new InetSocketAddress(uri.getHost(), uri.getPort());
// (2) redirect to given address if response is: redirect
if (lookupDataResult.redirect) {
findBroker(responseBrokerAddress, lookupDataResult.authoritative, destination)
.thenAccept(brokerAddress -> {
addressFuture.complete(brokerAddress);
}).exceptionally((lookupException) -> {
// lookup failed
log.warn("[{}] lookup failed : {}", destination.toString(),
lookupException.getMessage(), lookupException);
addressFuture.completeExceptionally(lookupException);
return null;
});
} else {
// (3) received correct broker to connect
addressFuture.complete(responseBrokerAddress);
}
} catch (Exception parseUrlException) {
// Failed to parse url
log.warn("[{}] invalid url {} : {}", destination.toString(), uri, parseUrlException.getMessage(),
parseUrlException);
addressFuture.completeExceptionally(parseUrlException);
}
}).exceptionally((sendException) -> {
// lookup failed
log.warn("[{}] failed to send lookup request : {}", destination.toString(), sendException.getMessage(),
sendException);
addressFuture.completeExceptionally(sendException);
return null;
});
}).exceptionally(connectionException -> {
addressFuture.completeExceptionally(connectionException);
return null;
});
return addressFuture;
}
private CompletableFuture<PartitionedTopicMetadata> getPartitionedTopicMetadata(InetSocketAddress socketAddress,
DestinationName destination) {
CompletableFuture<PartitionedTopicMetadata> partitionFuture = new CompletableFuture<PartitionedTopicMetadata>();
client.getCnxPool().getConnection(socketAddress).thenAccept(clientCnx -> {
long requestId = client.newRequestId();
ByteBuf request = Commands.newPartitionMetadataRequest(destination.toString(), requestId);
clientCnx.newLookup(request, requestId).thenAccept(lookupDataResult -> {
try {
partitionFuture.complete(new PartitionedTopicMetadata(lookupDataResult.partitions));
} catch (Exception e) {
partitionFuture.completeExceptionally(new PulsarClientException.LookupException(
format("Failed to parse partition-response redirect=%s , partitions with %s",
lookupDataResult.redirect, lookupDataResult.partitions, e.getMessage())));
}
}).exceptionally((e) -> {
log.warn("[{}] failed to get Partitioned metadata : {}", destination.toString(),
e.getCause().getMessage(), e);
partitionFuture.completeExceptionally(e);
return null;
});
}).exceptionally(connectionException -> {
partitionFuture.completeExceptionally(connectionException);
return null;
});
return partitionFuture;
}
public String getServiceUrl() {
return serviceAddress.toString();
}
static class LookupDataResult {
private String brokerUrl;
private String brokerUrlTls;
private int partitions;
private boolean authoritative;
private boolean redirect;
public LookupDataResult(String brokerUrl, String brokerUrlTls, boolean redirect, boolean authoritative) {
super();
this.brokerUrl = brokerUrl;
this.brokerUrlTls = brokerUrlTls;
this.authoritative = authoritative;
this.redirect = redirect;
}
public LookupDataResult(int partitions) {
super();
this.partitions = partitions;
}
}
private static final Logger log = LoggerFactory.getLogger(BinaryProtoLookupService.class);
}