package com.jivesoftware.os.amza.sync.deployable;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.jivesoftware.os.amza.api.AmzaInterner;
import com.jivesoftware.os.amza.api.PartitionClientProvider;
import com.jivesoftware.os.amza.client.aquarium.AmzaClientAquariumProvider;
import com.jivesoftware.os.amza.sync.api.AmzaSyncSenderConfig;
import com.jivesoftware.os.jive.utils.ordered.id.TimestampedOrderIdProvider;
import com.jivesoftware.os.mlogger.core.MetricLogger;
import com.jivesoftware.os.mlogger.core.MetricLoggerFactory;
import com.jivesoftware.os.routing.bird.http.client.HttpClient;
import com.jivesoftware.os.routing.bird.http.client.HttpRequestHelperUtils;
import com.jivesoftware.os.routing.bird.http.client.OAuthSigner;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
import oauth.signpost.signature.HmacSha1MessageSigner;
import org.apache.commons.lang.StringUtils;
/**
* Created by jonathan.colt on 12/22/16.
*/
public class AmzaSyncSenders {
private static final MetricLogger LOG = MetricLoggerFactory.getLogger();
private final AtomicBoolean running = new AtomicBoolean(false);
private final Map<String, AmzaSyncSender> senders = Maps.newConcurrentMap();
private final AmzaSyncStats stats;
private final AmzaSyncConfig syncConfig;
private final AmzaSyncReceiver syncReceiver;
private final ScheduledExecutorService executorService;
private final PartitionClientProvider partitionClientProvider;
private final AmzaClientAquariumProvider clientAquariumProvider;
private final AmzaInterner amzaInterner;
private final ObjectMapper mapper;
private final TimestampedOrderIdProvider orderIdProvider;
private final AmzaSyncSenderConfigProvider syncSenderConfigProvider;
private final AmzaSyncPartitionConfigProvider syncPartitionConfigProvider;
private final long ensureSendersInterval;
private final ExecutorService ensureSenders = Executors.newFixedThreadPool(1, new ThreadFactoryBuilder().setNameFormat("ensure-sender-%d").build());
public AmzaSyncSenders(AmzaSyncStats stats,
AmzaSyncConfig syncConfig,
AmzaSyncReceiver syncReceiver,
ScheduledExecutorService executorService,
PartitionClientProvider partitionClientProvider,
AmzaClientAquariumProvider clientAquariumProvider,
AmzaInterner amzaInterner,
ObjectMapper mapper,
TimestampedOrderIdProvider orderIdProvider,
AmzaSyncSenderConfigProvider syncSenderConfigProvider,
AmzaSyncPartitionConfigProvider syncPartitionConfigProvider,
long ensureSendersInterval) {
this.stats = stats;
this.syncConfig = syncConfig;
this.syncReceiver = syncReceiver;
this.executorService = executorService;
this.partitionClientProvider = partitionClientProvider;
this.clientAquariumProvider = clientAquariumProvider;
this.amzaInterner = amzaInterner;
this.mapper = mapper;
this.orderIdProvider = orderIdProvider;
this.syncSenderConfigProvider = syncSenderConfigProvider;
this.syncPartitionConfigProvider = syncPartitionConfigProvider;
this.ensureSendersInterval = ensureSendersInterval;
}
public Collection<String> getSyncspaces() {
return senders.keySet();
}
public Collection<AmzaSyncSender> getActiveSenders() {
return senders.values();
}
public AmzaSyncSender getSender(String syncspaceName) {
return senders.get(syncspaceName);
}
public void start() {
if (running.compareAndSet(false, true)) {
ensureSenders.submit(() -> {
while (running.get()) {
try {
Map<String, AmzaSyncSenderConfig> all = syncSenderConfigProvider.getAll();
for (Entry<String, AmzaSyncSenderConfig> entry : all.entrySet()) {
AmzaSyncSender amzaSyncSender = senders.get(entry.getKey());
AmzaSyncSenderConfig senderConfig = entry.getValue();
if (amzaSyncSender != null && amzaSyncSender.configHasChanged(senderConfig)) {
amzaSyncSender.stop();
amzaSyncSender = null;
}
if (amzaSyncSender == null) {
amzaSyncSender = new AmzaSyncSender(
stats,
senderConfig,
clientAquariumProvider,
syncConfig.getSyncSenderRingStripes(),
executorService,
partitionClientProvider,
amzaSyncClient(senderConfig),
syncPartitionConfigProvider,
amzaInterner
);
senders.put(entry.getKey(), amzaSyncSender);
amzaSyncSender.start();
}
}
// stop any senders that are no longer registered
for (Iterator<Entry<String, AmzaSyncSender>> iterator = senders.entrySet().iterator(); iterator.hasNext(); ) {
Entry<String, AmzaSyncSender> entry = iterator.next();
if (!all.containsKey(entry.getKey())) {
entry.getValue().stop();
iterator.remove();
}
}
Thread.sleep(ensureSendersInterval);
} catch (InterruptedException e) {
LOG.info("Ensure senders thread {} was interrupted");
} catch (Throwable t) {
LOG.error("Failure while ensuring senders", t);
Thread.sleep(ensureSendersInterval);
}
}
return null;
});
}
}
public void stop() {
if (running.compareAndSet(true, false)) {
ensureSenders.shutdownNow();
}
for (AmzaSyncSender amzaSyncSender : senders.values()) {
try {
amzaSyncSender.stop();
} catch (Exception x) {
LOG.warn("Failure while stopping sender:{}", new Object[] { amzaSyncSender }, x);
}
}
}
private AmzaSyncClient amzaSyncClient(AmzaSyncSenderConfig config) throws Exception {
if (config.loopback) {
return syncReceiver;
} else {
String consumerKey = StringUtils.trimToNull(config.oAuthConsumerKey);
String consumerSecret = StringUtils.trimToNull(config.oAuthConsumerSecret);
String consumerMethod = StringUtils.trimToNull(config.oAuthConsumerMethod);
if (consumerKey == null || consumerSecret == null || consumerMethod == null) {
throw new IllegalStateException("OAuth consumer has not been configured");
}
consumerMethod = consumerMethod.toLowerCase();
if (!consumerMethod.equals("hmac") && !consumerMethod.equals("rsa")) {
throw new IllegalStateException("OAuth consumer method must be one of HMAC or RSA");
}
String scheme = config.senderScheme;
String host = config.senderHost;
int port = config.senderPort;
boolean sslEnable = scheme.equals("https");
OAuthSigner authSigner = (request) -> {
CommonsHttpOAuthConsumer oAuthConsumer = new CommonsHttpOAuthConsumer(consumerKey, consumerSecret);
oAuthConsumer.setMessageSigner(new HmacSha1MessageSigner());
oAuthConsumer.setTokenWithSecret(consumerKey, consumerSecret);
return oAuthConsumer.sign(request);
};
HttpClient httpClient = HttpRequestHelperUtils.buildHttpClient(sslEnable,
config.allowSelfSignedCerts,
authSigner,
host,
port,
syncConfig.getSyncSenderSocketTimeout());
return new HttpAmzaSyncClient(httpClient,
mapper,
"/api/sync/v1/commit/rows",
"/api/sync/v1/ensure/partition");
}
}
}