/**
* Copyright (C) 2015 meltmedia (christian.trimble@meltmedia.com)
*
* 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.meltmedia.dropwizard.etcd.cluster;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Function;
import com.codahale.metrics.MetricRegistry;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.meltmedia.dropwizard.etcd.json.EtcdJson;
import com.meltmedia.dropwizard.etcd.json.EtcdJson.EtcdDirectory;
import com.meltmedia.dropwizard.etcd.json.EtcdJson.MappedEtcdDirectory;
import com.meltmedia.dropwizard.etcd.json.Heartbeat;
public class ClusterService {
public static class Builder {
private MappedEtcdDirectory<ClusterNode> nodesDirectory;
private ScheduledExecutorService executor;
private ClusterNode thisNode;
private EtcdJson factory;
private MetricRegistry registry;
public Builder withNodesDirectory(MappedEtcdDirectory<ClusterNode> nodesDirectory) {
this.nodesDirectory = nodesDirectory;
return this;
}
public Builder withEtcdFactory(EtcdJson factory) {
this.factory = factory;
return this;
}
public Builder withThisNode(ClusterNode thisNode) {
this.thisNode = thisNode;
return this;
}
public Builder withExecutor(ScheduledExecutorService executor) {
this.executor = executor;
return this;
}
public Builder withMetricRegistry( MetricRegistry registry ) {
this.registry = registry;
return this;
}
public ClusterService build() {
return new ClusterService(executor, factory, thisNode, nodesDirectory, registry);
}
}
public static Builder builder() {
return new Builder();
}
private EtcdJson factory;
private ClusterNode thisNode;
private ClusterStateTracker stateTracker;
private ScheduledExecutorService executor;
private Heartbeat<ClusterNode> heartbeats;
private MetricRegistry registry;
public ClusterService(ScheduledExecutorService executor, EtcdJson factory, ClusterNode thisNode,
MappedEtcdDirectory<ClusterNode> nodesDirectory, MetricRegistry registry) {
this.executor = executor;
this.factory = factory;
this.thisNode = thisNode;
this.heartbeats = nodesDirectory.newHeartbeat("/" + thisNode.getId(), thisNode, 60);
this.stateTracker =
ClusterStateTracker.builder()
.withDirectory(nodesDirectory)
.withThisNode(thisNode)
.build();
this.registry = registry;
}
public void start() {
heartbeats.start();
stateTracker.start();
}
public void stop() {
stateTracker.stop();
heartbeats.stop();
}
public <C> ProcessService<C> newProcessService(String directory,
Function<C, ClusterProcessLifecycle> lifecycleFactory, TypeReference<C> configType) {
return newProcessService(factory.newDirectory(directory), lifecycleFactory, configType);
}
public <C> ProcessService<C> newProcessService(EtcdDirectory directory,
Function<C, ClusterProcessLifecycle> lifecycleFactory, TypeReference<C> type) {
return ProcessService.<C> builder()
.withDirectory(directory)
.withLifecycleFactory(lifecycleFactory)
.withStateTracker(stateTracker)
.withExecutor(executor)
.withThisNode(thisNode)
.withType(type)
.withMapper(factory.getMapper())
.withMetricRegistry(registry)
.build();
}
public static class ProcessService<C> {
public static class Builder<C> {
private EtcdDirectory directory;
private ClusterNode thisNode;
private Function<C, ClusterProcessLifecycle> lifecycleFactory;
private ClusterStateTracker stateTracker;
private ScheduledExecutorService executor;
private TypeReference<C> type;
private ObjectMapper mapper;
private MetricRegistry registry;
public Builder<C> withDirectory(EtcdDirectory directory) {
this.directory = directory;
return this;
}
public Builder<C> withExecutor(ScheduledExecutorService executor) {
this.executor = executor;
return this;
}
public Builder<C> withStateTracker(ClusterStateTracker stateTracker) {
this.stateTracker = stateTracker;
return this;
}
public Builder<C> withThisNode(ClusterNode thisNode) {
this.thisNode = thisNode;
return this;
}
public Builder<C> withLifecycleFactory(Function<C, ClusterProcessLifecycle> lifecycleFactory) {
this.lifecycleFactory = lifecycleFactory;
return this;
}
public Builder<C> withType(TypeReference<C> type) {
this.type = type;
return this;
}
public Builder<C> withMapper(ObjectMapper mapper) {
this.mapper = mapper;
return this;
}
public Builder<C> withMetricRegistry( MetricRegistry registry ) {
this.registry = registry;
return this;
}
public ProcessService<C> build() {
if( registry == null ) throw new IllegalStateException("metric registry is required");
return new ProcessService<C>(thisNode, stateTracker, executor, directory, lifecycleFactory,
type, mapper, registry);
}
}
public static <C> Builder<C> builder() {
return new Builder<C>();
}
private ClusterAssignmentService assignments;
private ClusterAssignmentTracker tracker;
private ProcessorStateTracker processorStateTracker;
private ClusterProcessor<C> processor;
private MappedEtcdDirectory<ClusterProcess> processDirectory;
private MappedEtcdDirectory<ProcessorNode> processorDirectory;
private ClusterNode thisNode;
public ProcessService(ClusterNode thisNode, ClusterStateTracker stateTracker, ScheduledExecutorService executor, EtcdDirectory directory,
Function<C, ClusterProcessLifecycle> lifecycleFactory, TypeReference<C> type,
ObjectMapper mapper, MetricRegistry registry) {
this.thisNode = thisNode;
String dirName = directory.getName().replace('.', '_');
Function<String, String> metricName = name->MetricRegistry.name(ClusterAssignmentService.class, dirName, name);
this.processDirectory = directory.newDirectory("/processes", new TypeReference<ClusterProcess>(){});
this.processorDirectory = directory.newDirectory("/processors", new TypeReference<ProcessorNode>(){});
this.tracker = new ClusterAssignmentTracker(thisNode, processDirectory, registry, metricName);
this.processorStateTracker = ProcessorStateTracker.builder().withDirectory(processorDirectory).withThisNode(thisNode).build();
this.assignments =
ClusterAssignmentService.builder()
.withExecutor(executor)
.withProcessDir(processDirectory)
.withProcessorState(processorStateTracker)
.withAssignmentState(tracker::getState)
.withThisNode(thisNode)
.withClusterState(stateTracker)
.withMetricRegistry(registry)
.withMetricName(metricName)
.build();
this.processor =
ClusterProcessor.<C> builder()
.withLifecycleFactory(lifecycleFactory)
.withDirectory(processDirectory)
.withType(type)
.withNodeId(thisNode.getId())
.withMapper(mapper)
.build();
}
public void start() {
processorStateTracker.start(()->{
processor.start();
tracker.start();
assignments.start();
}).run();
}
public void stop() {
processorStateTracker.stop(()->{
assignments.stop();
tracker.stop();
processor.stop();
}).run();
}
public MappedEtcdDirectory<ClusterProcess> getDirectory() {
return processDirectory;
}
public String getId() {
return thisNode.getId();
}
}
public ClusterNode getThisNode() {
return thisNode;
}
public ClusterStateTracker getStateTracker() {
return stateTracker;
}
}