/*
* Copyright 2013 Jive Software, 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.jivesoftware.os.amza.sync.deployable;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.google.common.collect.Sets;
import com.jivesoftware.os.amza.api.AmzaInterner;
import com.jivesoftware.os.amza.api.partition.Consistency;
import com.jivesoftware.os.amza.api.partition.Durability;
import com.jivesoftware.os.amza.api.partition.PartitionName;
import com.jivesoftware.os.amza.api.partition.PartitionProperties;
import com.jivesoftware.os.amza.api.stream.RowType;
import com.jivesoftware.os.amza.client.aquarium.AmzaClientAquariumProvider;
import com.jivesoftware.os.amza.client.collection.AmzaMarshaller;
import com.jivesoftware.os.amza.client.http.AmzaClientProvider;
import com.jivesoftware.os.amza.client.http.HttpPartitionClientFactory;
import com.jivesoftware.os.amza.client.http.HttpPartitionHostsProvider;
import com.jivesoftware.os.amza.client.http.RingHostHttpClientProvider;
import com.jivesoftware.os.amza.sync.api.AmzaSyncPartitionConfig;
import com.jivesoftware.os.amza.sync.api.AmzaSyncPartitionTuple;
import com.jivesoftware.os.amza.sync.api.AmzaSyncSenderConfig;
import com.jivesoftware.os.amza.sync.deployable.endpoints.AmzaSyncApiEndpoints;
import com.jivesoftware.os.amza.sync.deployable.endpoints.AmzaSyncEndpoints;
import com.jivesoftware.os.amza.sync.deployable.oauth.AmzaSyncOAuthValidatorInitializer;
import com.jivesoftware.os.amza.sync.deployable.oauth.AmzaSyncOAuthValidatorInitializer.AmzaSyncOAuthValidatorConfig;
import com.jivesoftware.os.amza.ui.soy.SoyRenderer;
import com.jivesoftware.os.amza.ui.soy.SoyRendererInitializer;
import com.jivesoftware.os.amza.ui.soy.SoyRendererInitializer.SoyRendererConfig;
import com.jivesoftware.os.aquarium.AquariumStats;
import com.jivesoftware.os.aquarium.Member;
import com.jivesoftware.os.jive.utils.ordered.id.ConstantWriterIdProvider;
import com.jivesoftware.os.jive.utils.ordered.id.JiveEpochTimestampProvider;
import com.jivesoftware.os.jive.utils.ordered.id.OrderIdProviderImpl;
import com.jivesoftware.os.jive.utils.ordered.id.SnowflakeIdPacker;
import com.jivesoftware.os.jive.utils.ordered.id.TimestampedOrderIdProvider;
import com.jivesoftware.os.routing.bird.deployable.Deployable;
import com.jivesoftware.os.routing.bird.deployable.DeployableHealthCheckRegistry;
import com.jivesoftware.os.routing.bird.deployable.ErrorHealthCheckConfig;
import com.jivesoftware.os.routing.bird.deployable.InstanceConfig;
import com.jivesoftware.os.routing.bird.deployable.TenantAwareHttpClientHealthCheck;
import com.jivesoftware.os.routing.bird.endpoints.base.FullyOnlineVersion;
import com.jivesoftware.os.routing.bird.endpoints.base.HasUI;
import com.jivesoftware.os.routing.bird.endpoints.base.HasUI.UI;
import com.jivesoftware.os.routing.bird.endpoints.base.LoadBalancerHealthCheckEndpoints;
import com.jivesoftware.os.routing.bird.health.api.HealthFactory;
import com.jivesoftware.os.routing.bird.health.checkers.FileDescriptorCountHealthChecker;
import com.jivesoftware.os.routing.bird.health.checkers.GCLoadHealthChecker;
import com.jivesoftware.os.routing.bird.health.checkers.GCPauseHealthChecker;
import com.jivesoftware.os.routing.bird.health.checkers.LoadAverageHealthChecker;
import com.jivesoftware.os.routing.bird.health.checkers.ServiceStartupHealthCheck;
import com.jivesoftware.os.routing.bird.health.checkers.SystemCpuHealthChecker;
import com.jivesoftware.os.routing.bird.http.client.HttpClient;
import com.jivesoftware.os.routing.bird.http.client.HttpDeliveryClientHealthProvider;
import com.jivesoftware.os.routing.bird.http.client.HttpRequestHelperUtils;
import com.jivesoftware.os.routing.bird.http.client.TailAtScaleStrategy;
import com.jivesoftware.os.routing.bird.http.client.TenantAwareHttpClient;
import com.jivesoftware.os.routing.bird.http.client.TenantRoutingHttpClientInitializer;
import com.jivesoftware.os.routing.bird.server.oauth.validator.AuthValidator;
import com.jivesoftware.os.routing.bird.server.util.Resource;
import com.jivesoftware.os.routing.bird.shared.BoundedExecutor;
import com.jivesoftware.os.routing.bird.shared.ConnectionDescriptor;
import com.jivesoftware.os.routing.bird.shared.ConnectionDescriptors;
import com.jivesoftware.os.routing.bird.shared.HttpClientException;
import com.jivesoftware.os.routing.bird.shared.TenantRoutingProvider;
import com.jivesoftware.os.routing.bird.shared.TenantsServiceConnectionDescriptorProvider;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import org.glassfish.jersey.oauth1.signature.OAuth1Request;
import org.glassfish.jersey.oauth1.signature.OAuth1Signature;
public class AmzaSyncMain {
public static void main(String[] args) throws Exception {
new AmzaSyncMain().run(args);
}
public void run(String[] args) throws Exception {
ServiceStartupHealthCheck serviceStartupHealthCheck = new ServiceStartupHealthCheck();
try {
final Deployable deployable = new Deployable(args);
InstanceConfig instanceConfig = deployable.config(InstanceConfig.class);
HealthFactory.initialize(deployable::config, new DeployableHealthCheckRegistry(deployable));
deployable.addManageInjectables(HasUI.class, new HasUI(Arrays.asList(new UI("Sync", "main", "/ui"))));
deployable.addHealthCheck(new GCPauseHealthChecker(deployable.config(GCPauseHealthChecker.GCPauseHealthCheckerConfig.class)));
deployable.addHealthCheck(new GCLoadHealthChecker(deployable.config(GCLoadHealthChecker.GCLoadHealthCheckerConfig.class)));
deployable.addHealthCheck(new SystemCpuHealthChecker(deployable.config(SystemCpuHealthChecker.SystemCpuHealthCheckerConfig.class)));
deployable.addHealthCheck(new LoadAverageHealthChecker(deployable.config(LoadAverageHealthChecker.LoadAverageHealthCheckerConfig.class)));
deployable.addHealthCheck(
new FileDescriptorCountHealthChecker(deployable.config(FileDescriptorCountHealthChecker.FileDescriptorCountHealthCheckerConfig.class)));
deployable.addHealthCheck(serviceStartupHealthCheck);
deployable.addErrorHealthChecks(deployable.config(ErrorHealthCheckConfig.class));
deployable.addManageInjectables(FullyOnlineVersion.class, (FullyOnlineVersion) () -> {
if (serviceStartupHealthCheck.startupHasSucceeded()) {
return instanceConfig.getVersion();
} else {
return null;
}
});
deployable.buildManageServer().start();
HttpDeliveryClientHealthProvider clientHealthProvider = new HttpDeliveryClientHealthProvider(instanceConfig.getInstanceKey(),
HttpRequestHelperUtils.buildRequestHelper(false, false, null, instanceConfig.getRoutesHost(), instanceConfig.getRoutesPort()),
instanceConfig.getConnectionsHealth(), 5_000, 100);
TenantRoutingProvider tenantRoutingProvider = deployable.getTenantRoutingProvider();
TenantsServiceConnectionDescriptorProvider syncDescriptorProvider = tenantRoutingProvider
.getConnections(instanceConfig.getServiceName(), "main", 10_000); // TODO config
TenantRoutingHttpClientInitializer<String> tenantRoutingHttpClientInitializer = deployable.getTenantRoutingHttpClientInitializer();
TenantAwareHttpClient<String> amzaClient = tenantRoutingHttpClientInitializer.builder(
deployable.getTenantRoutingProvider().getConnections("amza", "main", 10_000), // TODO config
clientHealthProvider)
.deadAfterNErrors(10)
.checkDeadEveryNMillis(10_000)
.socketTimeoutInMillis(60_000)
.build(); // TODO expose to conf
deployable.addHealthCheck(new TenantAwareHttpClientHealthCheck("amzaClient", amzaClient));
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
AmzaSyncConfig syncConfig = deployable.config(AmzaSyncConfig.class);
TailAtScaleStrategy tailAtScaleStrategy = new TailAtScaleStrategy(
deployable.newBoundedExecutor(1024, "tas"),
100, // TODO config
95, // TODO config
1000 // TODO config
);
AmzaInterner amzaInterner = new AmzaInterner();
AmzaClientProvider<HttpClient, HttpClientException> amzaClientProvider = new AmzaClientProvider<>(
new HttpPartitionClientFactory(),
new HttpPartitionHostsProvider(amzaClient, tailAtScaleStrategy, mapper),
new RingHostHttpClientProvider(amzaClient),
deployable.newBoundedExecutor(syncConfig.getAmzaCallerThreadPoolSize(), "amza-client"),
syncConfig.getAmzaAwaitLeaderElectionForNMillis(),
-1,
-1);
TimestampedOrderIdProvider orderIdProvider = new OrderIdProviderImpl(
new ConstantWriterIdProvider(instanceConfig.getInstanceName()),
new SnowflakeIdPacker(),
new JiveEpochTimestampProvider());
AmzaClientAquariumProvider amzaClientAquariumProvider = new AmzaClientAquariumProvider(new AquariumStats(),
instanceConfig.getServiceName(),
amzaClientProvider,
orderIdProvider,
new Member(instanceConfig.getInstanceKey().getBytes(StandardCharsets.UTF_8)),
count -> {
ConnectionDescriptors descriptors = syncDescriptorProvider.getConnections("");
int ringSize = descriptors.getConnectionDescriptors().size();
return count > ringSize / 2;
},
() -> {
Set<Member> members = Sets.newHashSet();
ConnectionDescriptors descriptors = syncDescriptorProvider.getConnections("");
for (ConnectionDescriptor connectionDescriptor : descriptors.getConnectionDescriptors()) {
members.add(new Member(connectionDescriptor.getInstanceDescriptor().instanceKey.getBytes(StandardCharsets.UTF_8)));
}
return members;
},
128, //TODO config
128, //TODO config
5_000L, //TODO config
100L, //TODO config
60_000L, //TODO config
10_000L, //TODO config
Executors.newSingleThreadExecutor(),
100L, //TODO config
1_000L, //TODO config
10_000L,//TODO config
syncConfig.getAquariumUseSolutionLog());
ObjectMapper miruSyncMapper = new ObjectMapper();
miruSyncMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
miruSyncMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
AmzaMarshaller<String> stringMarshaller = new AmzaMarshaller<String>() {
@Override
public String fromBytes(byte[] bytes) throws Exception {
return new String(bytes, StandardCharsets.UTF_8);
}
@Override
public byte[] toBytes(String value) throws Exception {
return value == null ? null : value.getBytes(StandardCharsets.UTF_8);
}
};
AmzaMarshaller<AmzaSyncSenderConfig> amzaSyncSenderConfigMarshaller = new AmzaMarshaller<AmzaSyncSenderConfig>() {
@Override
public AmzaSyncSenderConfig fromBytes(byte[] bytes) throws Exception {
return mapper.readValue(bytes, AmzaSyncSenderConfig.class);
}
@Override
public byte[] toBytes(AmzaSyncSenderConfig value) throws Exception {
return mapper.writeValueAsBytes(value);
}
};
AmzaSyncSenderMap senderConfigStorage = new AmzaSyncSenderMap(
amzaClientProvider,
"amza-sync-sender-config",
new PartitionProperties(Durability.fsync_async,
0, 0, 0, 0, 0, 0, 0, 0,
false,
Consistency.leader_quorum,
true,
true,
false,
RowType.snappy_primary,
"lab",
-1,
null,
-1,
-1),
stringMarshaller,
amzaSyncSenderConfigMarshaller
);
AmzaMarshaller<AmzaSyncPartitionTuple> tupleMarshaller = new AmzaMarshaller<AmzaSyncPartitionTuple>() {
@Override
public AmzaSyncPartitionTuple fromBytes(byte[] bytes) throws Exception {
return AmzaSyncPartitionTuple.fromBytes(bytes, 0, amzaInterner);
}
@Override
public byte[] toBytes(AmzaSyncPartitionTuple value) throws Exception {
return AmzaSyncPartitionTuple.toBytes(value);
}
};
AmzaMarshaller<AmzaSyncPartitionConfig> partitionConfigMarshaller = new AmzaMarshaller<AmzaSyncPartitionConfig>() {
@Override
public AmzaSyncPartitionConfig fromBytes(byte[] bytes) throws Exception {
return mapper.readValue(bytes, AmzaSyncPartitionConfig.class);
}
@Override
public byte[] toBytes(AmzaSyncPartitionConfig value) throws Exception {
return mapper.writeValueAsBytes(value);
}
};
AmzaSyncPartitionConfigStorage syncPartitionConfigStorage = new AmzaSyncPartitionConfigStorage(
amzaClientProvider,
"amza-sync-partitions-config-",
new PartitionProperties(Durability.fsync_async,
0, 0, 0, 0, 0, 0, 0, 0,
false,
Consistency.leader_quorum,
true,
true,
false,
RowType.snappy_primary,
"lab",
-1,
null,
-1,
-1),
tupleMarshaller,
partitionConfigMarshaller
);
AmzaSyncStats stats = new AmzaSyncStats();
AmzaSyncReceiver syncReceiver = new AmzaSyncReceiver(amzaClientProvider, syncConfig.getSyncReceiverUseSolutionLog());
AmzaSyncSenders syncSenders = null;
if (syncConfig.getSyncSenderEnabled()) {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(syncConfig.getSyncSendersThreadCount());
syncSenders = new AmzaSyncSenders(stats,
syncConfig,
syncReceiver,
executorService,
amzaClientProvider,
amzaClientAquariumProvider,
amzaInterner,
mapper,
orderIdProvider,
senderConfigStorage,
syncPartitionConfigStorage,
30_000); // TODO config
}
amzaClientAquariumProvider.start();
if (syncSenders != null) {
syncSenders.start();
}
SoyRendererConfig rendererConfig = deployable.config(SoyRendererConfig.class);
File staticResourceDir = new File(System.getProperty("user.dir"));
System.out.println("Static resources rooted at " + staticResourceDir.getAbsolutePath());
Resource sourceTree = new Resource(staticResourceDir)
.addResourcePath(rendererConfig.getPathToStaticResources())
.setDirectoryListingAllowed(false)
.setContext("/ui/static");
deployable.addResource(sourceTree);
SoyRenderer renderer = new SoyRendererInitializer().initialize(rendererConfig);
AmzaSyncUIService amzaSyncUIService = new AmzaSyncUIServiceInitializer().initialize(renderer,
syncSenders,
stats,
syncConfig.getSyncSenderEnabled(),
syncConfig.getSyncReceiverEnabled(),
mapper);
deployable.addEndpoints(LoadBalancerHealthCheckEndpoints.class);
deployable.addNoAuth("/health/check");
if (instanceConfig.getMainServiceAuthEnabled()) {
if (syncConfig.getSyncReceiverEnabled()) {
AmzaSyncOAuthValidatorConfig oAuthValidatorConfig = deployable.config(AmzaSyncOAuthValidatorConfig.class);
AuthValidator<OAuth1Signature, OAuth1Request> syncOAuthValidator = new AmzaSyncOAuthValidatorInitializer()
.initialize(oAuthValidatorConfig);
deployable.addCustomOAuth(syncOAuthValidator, "/api/*");
}
deployable.addRouteOAuth("/amza/*", "/api/*");
deployable.addSessionAuth("/ui/*", "/amza/*", "/api/*");
} else {
deployable.addNoAuth("/amza/*", "/api/*");
deployable.addSessionAuth("/ui/*");
}
deployable.addEndpoints(AmzaSyncEndpoints.class);
deployable.addInjectables(AmzaInterner.class, amzaInterner);
if (syncSenders != null) {
deployable.addInjectables(AmzaSyncSenders.class, syncSenders);
}
deployable.addEndpoints(AmzaSyncUIEndpoints.class);
deployable.addInjectables(AmzaSyncUIService.class, amzaSyncUIService);
if (syncConfig.getSyncReceiverEnabled()) {
deployable.addEndpoints(AmzaSyncApiEndpoints.class);
deployable.addInjectables(AmzaSyncReceiver.class, syncReceiver);
}
deployable.addInjectables(ObjectMapper.class, mapper);
deployable.addInjectables(AmzaSyncSenderMap.class, senderConfigStorage);
deployable.addInjectables(AmzaSyncPartitionConfigStorage.class, syncPartitionConfigStorage);
deployable.buildServer().start();
clientHealthProvider.start();
serviceStartupHealthCheck.success();
} catch (Throwable t) {
serviceStartupHealthCheck.info("Encountered the following failure during startup.", t);
}
}
private PartitionName extractPartition(String s) {
if (s.contains("/")) {
String[] parts = s.split("/");
return new PartitionName(false,
parts[0].trim().getBytes(StandardCharsets.UTF_8),
parts[1].trim().getBytes(StandardCharsets.UTF_8));
} else {
throw new IllegalArgumentException("Provide partition names in the format ringName/name");
}
}
}