/*
* Copyright 2014 the original author or authors.
*
* 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.kuujo.vertigo.network.manager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.kuujo.vertigo.cluster.Cluster;
import net.kuujo.vertigo.cluster.Group;
import net.kuujo.vertigo.cluster.Node;
import net.kuujo.vertigo.cluster.data.AsyncMap;
import net.kuujo.vertigo.cluster.data.MapEvent;
import net.kuujo.vertigo.cluster.data.WatchableAsyncMap;
import net.kuujo.vertigo.cluster.data.impl.WrappedWatchableAsyncMap;
import net.kuujo.vertigo.cluster.impl.DefaultCluster;
import net.kuujo.vertigo.component.ComponentContext;
import net.kuujo.vertigo.component.InstanceContext;
import net.kuujo.vertigo.component.ModuleContext;
import net.kuujo.vertigo.network.NetworkContext;
import net.kuujo.vertigo.util.Components;
import net.kuujo.vertigo.util.Contexts;
import net.kuujo.vertigo.util.CountingCompletionHandler;
import net.kuujo.vertigo.util.Task;
import net.kuujo.vertigo.util.TaskRunner;
import org.vertx.java.core.AsyncResult;
import org.vertx.java.core.Future;
import org.vertx.java.core.Handler;
import org.vertx.java.core.impl.DefaultFutureResult;
import org.vertx.java.core.json.JsonObject;
import org.vertx.java.core.logging.Logger;
import org.vertx.java.core.logging.impl.LoggerFactory;
import org.vertx.java.platform.Verticle;
/**
* Vertigo network manager.<p>
*
* The manager is at the core of all Vertigo networks. Its responsibilities
* are to handle deployment, undeployment, reconfiguration, and coordination
* of components within a network. Each network has its own network manager.
* The manager will be deployed to the network's cluster scope using the
* network name as the user-assigned deployment ID.<p>
*
* The manager uses the highest level cluster scope available to coordinate
* networks. When the manager deploys a component, it will set a key in the
* cluster for that component containing the component's configuration. Once
* the component has been deployed, the component will then watch that key
* for changes. If the network's configuration changes - e.g. through an
* active network configuration change - the network will automatically
* notify all running components by updating their individual configurations
* in the cluster.<p>
*
* Once a component has completed startup, it will set a status key in the
* cluster. The manager watches status keys for each component instance in
* the network and once all status keys have been set the manager sets a
* network-wide status key indicating that the network is ready, e.g. all
* instances in the network are running and their connections are open.
* When a configuration change occurs, the manager will unset the network-wide
* status key, indicating to deployed instances that a configuration change
* is taking place. This allows components to potentially take action preventing
* data loss prior to configuration changes.<p>
*
* Note that configuration changes are essentially atomic. When a configuration
* change is detected, if the manager is already processing a configuration change
* then the change will be queued for processing once the current configuration
* change is complete.
*
* @author <a href="http://github.com/kuujo">Jordan Halterman</a>
*/
public class NetworkManager extends Verticle {
private Logger log;
private String address;
private Cluster cluster;
private WatchableAsyncMap<String, String> data;
private Set<String> ready = new HashSet<>();
private NetworkContext currentContext;
private AsyncMap<String, String> deploymentIDs;
private AsyncMap<String, String> deploymentNodes;
private final TaskRunner tasks = new TaskRunner();
private final Map<String, Handler<MapEvent<String, String>>> watchHandlers = new HashMap<>();
private final Handler<MapEvent<String, String>> watchHandler = new Handler<MapEvent<String, String>>() {
@Override
public void handle(MapEvent<String, String> event) {
if (event.type().equals(MapEvent.Type.CREATE)) {
handleCreate(Contexts.<NetworkContext>deserialize(new JsonObject(event.value())));
} else if (event.type().equals(MapEvent.Type.UPDATE)) {
handleUpdate(Contexts.<NetworkContext>deserialize(new JsonObject(event.value())));
} else if (event.type().equals(MapEvent.Type.DELETE)) {
handleDelete();
}
}
};
private final Handler<Node> joinHandler = new Handler<Node>() {
@Override
public void handle(Node node) {
doNodeJoined(node);
}
};
private final Handler<Node> leaveHandler = new Handler<Node>() {
@Override
public void handle(Node node) {
doNodeLeft(node);
}
};
@Override
public void start(final Future<Void> startResult) {
address = container.config().getString("address");
if (address == null) {
startResult.setFailure(new IllegalArgumentException("No network address specified."));
return;
}
log = LoggerFactory.getLogger(String.format("%s-%s", NetworkManager.class.getCanonicalName(), address));
String scluster = container.config().getString("cluster");
if (scluster == null) {
startResult.setFailure(new IllegalArgumentException("No cluster address specified."));
return;
}
cluster = new DefaultCluster(scluster, vertx, container);
deploymentIDs = cluster.<String, String>getMap(String.format("deployments.%s", address));
deploymentNodes = cluster.<String, String>getMap(String.format("nodes.%s", address));
// Load the current cluster. Regardless of the network's cluster scope,
// we use the CLUSTER for coordination if it's available. This ensures
// that identical networks cannot be deployed from separate clustered
// Vert.x instances.
log.debug(String.format("%s - Scheduling manager startup task", NetworkManager.this));
tasks.runTask(new Handler<Task>() {
@Override
public void handle(final Task task) {
final CountingCompletionHandler<Void> counter = new CountingCompletionHandler<Void>(2);
counter.setHandler(new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
startResult.setFailure(result.cause());
} else {
data = new WrappedWatchableAsyncMap<String, String>(cluster.<String, String>getMap(address), vertx);
log.debug(String.format("%s - start() watching key %s", NetworkManager.this, address));
data.watch(address, watchHandler, new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
startResult.setFailure(result.cause());
} else {
// In the event that the manager failed, the current network context
// will be persisted in the cluster. Attempt to retrieve it.
log.debug(String.format("%s - start() getting key %s", NetworkManager.this, address));
data.get(address, new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
if (result.failed()) {
startResult.setFailure(result.cause());
} else if (result.result() != null) {
currentContext = Contexts.<NetworkContext>deserialize(new JsonObject(result.result()));
final CountingCompletionHandler<Void> componentCounter = new CountingCompletionHandler<Void>(currentContext.components().size());
componentCounter.setHandler(new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
startResult.setFailure(result.cause());
} else {
NetworkManager.super.start(startResult);
}
log.debug(String.format("%s - start() task complete", NetworkManager.this));
task.complete();
}
});
// Try to determine the current status of the network.
for (ComponentContext<?> component : currentContext.components()) {
final CountingCompletionHandler<Void> instanceCounter = new CountingCompletionHandler<Void>(component.instances().size());
instanceCounter.setHandler(new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
componentCounter.fail(result.cause());
} else {
componentCounter.succeed();
}
}
});
for (final InstanceContext instance : component.instances()) {
log.debug(String.format("%s - start() checking status of %s", NetworkManager.this, instance.address()));
data.containsKey(instance.status(), new Handler<AsyncResult<Boolean>>() {
@Override
public void handle(AsyncResult<Boolean> result) {
if (result.failed()) {
instanceCounter.fail(result.cause());
} else if (result.result()) {
handleReady(instance.address());
instanceCounter.succeed();
} else {
handleUnready(instance.address());
instanceCounter.succeed();
}
}
});
}
}
} else {
log.debug(String.format("%s - start() task complete", NetworkManager.this));
task.complete();
NetworkManager.super.start(startResult);
}
}
});
}
}
});
}
}
});
// Register a handler to be called when a node joins the cluster.
log.debug(String.format("%s - start() registering cluster join handler on cluster: %s", NetworkManager.this, cluster.address()));
cluster.registerJoinHandler(joinHandler, counter);
// Register a handler to be called when a node leaves the cluster.
log.debug(String.format("%s - start() registering cluster leave handler on cluster: %s", NetworkManager.this, cluster.address()));
cluster.registerLeaveHandler(leaveHandler, counter);
}
});
}
/**
* Handles the creation of the network.
*/
private void handleCreate(final NetworkContext context) {
log.debug(String.format("%s - Network configuration created in cluster: %s", NetworkManager.this, cluster.address()));
log.debug(String.format("%s - Scheduling network deployment task", NetworkManager.this));
tasks.runTask(new Handler<Task>() {
@Override
public void handle(final Task task) {
currentContext = context;
// Any time the network is being reconfigured, unready the network.
// This will cause components to pause during the reconfiguration.
unready(new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
log.error(result.cause());
task.complete();
} else {
deployNetwork(context, new Handler<AsyncResult<NetworkContext>>() {
@Override
public void handle(AsyncResult<NetworkContext> result) {
task.complete();
if (result.failed()) {
log.error(result.cause());
} else {
log.info(String.format("%s - Successfully deployed network", NetworkManager.this));
}
}
});
}
}
});
}
});
}
/**
* Handles the update of the network.
*/
private void handleUpdate(final NetworkContext context) {
log.debug(String.format("%s - Network configuration updated in cluster: %s", NetworkManager.this, cluster.address()));
log.debug(String.format("%s - Scheduling network update task", NetworkManager.this));
tasks.runTask(new Handler<Task>() {
@Override
public void handle(final Task task) {
// Any time the network is being reconfigured, unready the network.
// This will cause components to pause during the reconfiguration.
unready(new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
log.error(result.cause());
task.complete();
} else {
if (currentContext != null) {
final NetworkContext runningContext = currentContext;
currentContext = context;
// We have to update all instance contexts before deploying
// any new components in order to ensure connections are
// available for startup.
log.info(String.format("%s - Updating network in cluster: %s", NetworkManager.this, address));
log.debug(String.format("%s - Network:%n%s", NetworkManager.this, currentContext.toString(true)));
updateNetwork(currentContext, new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
log.warn(result.cause());
task.complete();
} else {
undeployRemovedComponents(currentContext, runningContext, new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
log.warn(result.cause());
task.complete();
} else {
deployAddedComponents(currentContext, runningContext, new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
task.complete();
if (result.failed()) {
log.warn(result.cause());
}
}
});
}
}
});
}
}
});
}
else {
// Just deploy the entire network if it wasn't already deployed.
currentContext = context;
log.info(String.format("%s - Deploying network in cluster: %s", NetworkManager.this, address));
log.debug(String.format("%s - Network:%n%s", NetworkManager.this, currentContext.toString(true)));
deployNetwork(context, new Handler<AsyncResult<NetworkContext>>() {
@Override
public void handle(AsyncResult<NetworkContext> result) {
task.complete();
if (result.failed()) {
log.warn(result.cause());
} else {
log.info(String.format("%s - Successfully deployed network", NetworkManager.this));
}
}
});
}
}
}
});
}
});
}
/**
* Undeploys components that were removed from the network.
*/
private void undeployRemovedComponents(final NetworkContext context, final NetworkContext runningContext, final Handler<AsyncResult<Void>> doneHandler) {
// Undeploy any components that were removed from the network.
final List<ComponentContext<?>> removedComponents = new ArrayList<>();
for (ComponentContext<?> runningComponent : runningContext.components()) {
if (context.component(runningComponent.name()) == null) {
removedComponents.add(runningComponent);
}
}
if (!removedComponents.isEmpty()) {
final CountingCompletionHandler<Void> counter = new CountingCompletionHandler<Void>(removedComponents.size());
counter.setHandler(new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
new DefaultFutureResult<Void>(result.cause()).setHandler(doneHandler);
} else {
log.info(String.format("%s - Removed %d components", NetworkManager.this, removedComponents.size()));
new DefaultFutureResult<Void>((Void) null).setHandler(doneHandler);
}
}
});
undeployComponents(removedComponents, counter);
} else {
new DefaultFutureResult<Void>((Void) null).setHandler(doneHandler);
}
}
/**
* Deploys components that were added to the network.
*/
private void deployAddedComponents(final NetworkContext context, NetworkContext runningContext, final Handler<AsyncResult<Void>> doneHandler) {
// Deploy any components that were added to the network.
final List<ComponentContext<?>> addedComponents = new ArrayList<>();
for (ComponentContext<?> component : context.components()) {
if (runningContext.component(component.name()) == null) {
addedComponents.add(component);
}
}
if (!addedComponents.isEmpty()) {
final CountingCompletionHandler<Void> counter = new CountingCompletionHandler<Void>(addedComponents.size());
counter.setHandler(new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
new DefaultFutureResult<Void>(result.cause()).setHandler(doneHandler);
} else {
log.info(String.format("%s - Added %d components", NetworkManager.this, addedComponents.size()));
new DefaultFutureResult<Void>((Void) null).setHandler(doneHandler);
}
}
});
deployComponents(addedComponents, counter);
} else {
new DefaultFutureResult<Void>((Void) null).setHandler(doneHandler);
}
}
/**
* Handles the deletion of the network.
*/
private void handleDelete() {
log.debug(String.format("%s - Network configuration removed from cluster: %s", NetworkManager.this, cluster.address()));
tasks.runTask(new Handler<Task>() {
@Override
public void handle(final Task task) {
unready(new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
log.error(result.cause());
}
if (currentContext != null) {
log.info(String.format("%s - Undeploying network", NetworkManager.this));
undeployNetwork(currentContext, new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
log.error(result.cause());
task.complete();
} else {
// Once we've finished undeploying all the components of the
// network, set the network's status to nothing in order to
// indicate that the manager (this) can be undeployed.
data.put(currentContext.status(), "", new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
if (result.failed()) {
log.error(result.cause());
} else {
log.info(String.format("%s - Successfully undeployed all components", NetworkManager.this));
}
task.complete();
}
});
}
}
});
} else {
task.complete();
}
}
});
}
});
}
/**
* Unreadies the network.
*/
private void unready(final Handler<AsyncResult<Void>> doneHandler) {
if (currentContext != null && data != null) {
log.debug(String.format("%s - Pausing network", NetworkManager.this));
data.remove(currentContext.status(), new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
if (result.failed()) {
new DefaultFutureResult<Void>(result.cause()).setHandler(doneHandler);
} else {
new DefaultFutureResult<Void>((Void) null).setHandler(doneHandler);
}
}
});
}
}
/**
* Called when a component instance is ready.
*/
private void handleReady(String address) {
log.debug(String.format("%s - Received ready message from %s", NetworkManager.this, address));
ready.add(address);
checkReady();
}
/**
* Checks whether the network is ready.
*/
private void checkReady() {
if (allReady()) {
log.debug(String.format("%s - All components ready in network, starting components", NetworkManager.this));
// Set the network's status key to the current context version. This
// can be used by listeners to determine when a configuration change is complete.
data.put(currentContext.status(), currentContext.version(), new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
if (result.failed()) {
log.error(result.cause());
}
}
});
}
}
/**
* Called when a component instance is unready.
*/
private void handleUnready(String address) {
log.debug(String.format("%s - Received unready message from: %s", NetworkManager.this, address));
ready.remove(address);
checkUnready();
}
/**
* Checks whether the network is unready.
*/
private void checkUnready() {
if (!allReady()) {
log.debug(String.format("%s - Components not ready, pausing components", NetworkManager.this));
data.remove(currentContext.address(), new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
if (result.failed()) {
log.error(result.cause());
}
}
});
}
}
/**
* Checks whether all components are ready in the network.
*/
private boolean allReady() {
if (currentContext == null) return false;
List<InstanceContext> instances = new ArrayList<>();
boolean match = true;
outer: for (ComponentContext<?> component : currentContext.components()) {
instances.addAll(component.instances());
for (InstanceContext instance : component.instances()) {
if (!ready.contains(instance.address())) {
match = false;
break outer;
}
}
}
return match;
}
/**
* Deploys a complete network.
*/
private void deployNetwork(final NetworkContext context, final Handler<AsyncResult<NetworkContext>> doneHandler) {
log.debug(String.format("%s - Deploying network", NetworkManager.this));
final CountingCompletionHandler<Void> complete = new CountingCompletionHandler<Void>(context.components().size());
complete.setHandler(new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
new DefaultFutureResult<NetworkContext>(result.cause()).setHandler(doneHandler);
} else {
new DefaultFutureResult<NetworkContext>(context).setHandler(doneHandler);
}
}
});
deployComponents(context.components(), complete);
}
/**
* Deploys all network components.
*/
private void deployComponents(Collection<ComponentContext<?>> components, final CountingCompletionHandler<Void> counter) {
for (final ComponentContext<?> component : components) {
deployComponent(component, new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
counter.fail(result.cause());
} else {
data.put(component.address(), Contexts.serialize(component).encode(), new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
if (result.failed()) {
counter.fail(result.cause());
} else {
counter.succeed();
}
}
});
}
}
});
}
}
/**
* Deploys a component.
*/
private void deployComponent(final ComponentContext<?> component, final Handler<AsyncResult<Void>> doneHandler) {
log.info(String.format("%s - Deploying %d instances of %s", NetworkManager.this, component.instances().size(), component.isModule() ? component.asModule().module() : component.asVerticle().main()));
log.debug(String.format("%s - Deploying component:%n%s", NetworkManager.this, component.toString(true)));
// If the component is installable then we first need to install the
// component to all the nodes in the cluster.
if (component.isModule()) {
final ModuleContext module = component.asModule();
// If the component has a group then install the module to the group.
if (component.group() != null) {
// Make sure to install the module only to the appropriate deployment group.
cluster.getGroup(component.group(), new Handler<AsyncResult<Group>>() {
@Override
public void handle(AsyncResult<Group> result) {
if (result.failed()) {
new DefaultFutureResult<Void>(result.cause()).setHandler(doneHandler);
} else {
result.result().getNodes(new Handler<AsyncResult<Collection<Node>>>() {
@Override
public void handle(AsyncResult<Collection<Node>> result) {
if (result.failed()) {
new DefaultFutureResult<Void>(result.cause()).setHandler(doneHandler);
} else {
final CountingCompletionHandler<Void> counter = new CountingCompletionHandler<Void>(result.result().size());
counter.setHandler(new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
new DefaultFutureResult<Void>(result.cause()).setHandler(doneHandler);
} else {
deployInstances(component.instances(), doneHandler);
}
}
});
for (Node node : result.result()) {
installModule(node, module, counter);
}
}
}
});
}
}
});
} else {
// If the component doesn't have a group then it needs to be installed
// to the cluster.
cluster.getNodes(new Handler<AsyncResult<Collection<Node>>>() {
@Override
public void handle(AsyncResult<Collection<Node>> result) {
if (result.failed()) {
new DefaultFutureResult<Void>(result.cause()).setHandler(doneHandler);
} else {
final CountingCompletionHandler<Void> counter = new CountingCompletionHandler<Void>(result.result().size());
counter.setHandler(new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
new DefaultFutureResult<Void>(result.cause()).setHandler(doneHandler);
} else {
deployInstances(component.instances(), doneHandler);
}
}
});
for (Node node : result.result()) {
installModule(node, module, counter);
}
}
}
});
}
} else {
deployInstances(component.instances(), doneHandler);
}
}
/**
* Deploys all network component instances.
*/
private void deployInstances(List<InstanceContext> instances, final Handler<AsyncResult<Void>> doneHandler) {
final CountingCompletionHandler<Void> counter = new CountingCompletionHandler<Void>(instances.size()).setHandler(doneHandler);
for (final InstanceContext instance : instances) {
// Before deploying the instance, check if the instance is already deployed in
// the network's cluster.
deploymentIDs.get(instance.address(), new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
if (result.failed()) {
counter.fail(result.cause());
} else if (result.result() == null) {
// If no deployment ID has been assigned to the instance then that means it hasn't
// yet been deployed. Deploy the instance.
deployInstance(instance, counter);
} else {
// Even if the instance is already deployed, update its context in the cluster.
// It's possible that the instance's connections could have changed with the update.
data.put(instance.address(), Contexts.serialize(instance).encode(), new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
if (result.failed()) {
counter.fail(result.cause());
} else {
counter.succeed();
}
}
});
}
}
});
}
}
/**
* Deploys a single component instance.
*/
private void deployInstance(final InstanceContext instance, final CountingCompletionHandler<Void> counter) {
// First we need to select a node from the component's deployment group
// to which to deploy the component.
// If the component doesn't specify a group then deploy to any node in the cluster.
if (instance.component().group() != null) {
cluster.getGroup(instance.component().group(), new Handler<AsyncResult<Group>>() {
@Override
public void handle(AsyncResult<Group> result) {
if (result.failed()) {
counter.fail(result.cause());
} else {
result.result().selectNode(instance.address(), new Handler<AsyncResult<Node>>() {
@Override
public void handle(AsyncResult<Node> result) {
if (result.failed()) {
counter.fail(result.cause());
} else {
deployInstance(result.result(), instance, counter);
}
}
});
}
}
});
} else {
cluster.selectNode(instance.address(), new Handler<AsyncResult<Node>>() {
@Override
public void handle(AsyncResult<Node> result) {
if (result.failed()) {
counter.fail(result.cause());
} else {
deployInstance(result.result(), instance, counter);
}
}
});
}
}
/**
* Deploys an instance to a specific node.
*/
private void deployInstance(final Node node, final InstanceContext instance, final CountingCompletionHandler<Void> counter) {
data.put(instance.address(), Contexts.serialize(instance).encode(), new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
if (result.failed()) {
counter.fail(result.cause());
} else {
if (!watchHandlers.containsKey(instance.address())) {
final Handler<MapEvent<String, String>> watchHandler = new Handler<MapEvent<String, String>>() {
@Override
public void handle(MapEvent<String, String> event) {
if (event.type().equals(MapEvent.Type.CREATE) || event.type().equals(MapEvent.Type.UPDATE)) {
handleReady(instance.address());
} else if (event.type().equals(MapEvent.Type.DELETE)) {
handleUnready(instance.address());
}
}
};
// Watch the instance's status for changes. Once the instance has started
// up, the component coordinator will set the instance's status key in the
// cluster, indicating that the instance has completed started. Once all
// instances in the network have completed startup the network will be started.
data.watch(instance.status(), watchHandler, new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
counter.fail(result.cause());
} else {
watchHandlers.put(instance.address(), watchHandler);
if (instance.component().isModule()) {
deployModule(node, instance, counter);
} else if (instance.component().isVerticle() && !instance.component().asVerticle().isWorker()) {
deployVerticle(node, instance, counter);
} else if (instance.component().isVerticle() && instance.component().asVerticle().isWorker()) {
deployWorkerVerticle(node, instance, counter);
}
}
}
});
} else {
if (instance.component().isModule()) {
deployModule(node, instance, counter);
} else if (instance.component().isVerticle() && !instance.component().asVerticle().isWorker()) {
deployVerticle(node, instance, counter);
} else if (instance.component().isVerticle() && instance.component().asVerticle().isWorker()) {
deployWorkerVerticle(node, instance, counter);
}
}
}
}
});
}
/**
* Deploys a module component instance in the network's cluster.
*/
private void deployModule(final Node node, final InstanceContext instance, final CountingCompletionHandler<Void> counter) {
log.debug(String.format("%s - Deploying %s to %s", NetworkManager.this, instance.component().asModule().module(), node.address()));
node.deployModule(instance.component().asModule().module(), Components.buildConfig(instance, cluster), 1, new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
if (result.failed()) {
counter.fail(result.cause());
} else {
deploymentIDs.put(instance.address(), result.result(), new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
counter.succeed();
}
});
}
}
});
}
/**
* Deploys a verticle component instance in the network's cluster.
*/
private void deployVerticle(final Node node, final InstanceContext instance, final CountingCompletionHandler<Void> counter) {
log.debug(String.format("%s - Deploying %s to %s", NetworkManager.this, instance.component().asVerticle().main(), node.address()));
node.deployVerticle(instance.component().asVerticle().main(), Components.buildConfig(instance, cluster), 1, new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
if (result.failed()) {
counter.fail(result.cause());
} else {
deploymentIDs.put(instance.address(), result.result(), new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
counter.succeed();
}
});
}
}
});
}
/**
* Deploys a worker verticle component instance in the network's cluster.
*/
private void deployWorkerVerticle(final Node node, final InstanceContext instance, final CountingCompletionHandler<Void> counter) {
log.debug(String.format("%s - Deploying %s to %s", NetworkManager.this, instance.component().asVerticle().main(), node.address()));
node.deployWorkerVerticle(instance.component().asVerticle().main(), Components.buildConfig(instance, cluster), 1,instance.component().asVerticle().isMultiThreaded(), new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
if (result.failed()) {
counter.fail(result.cause());
} else {
deploymentIDs.put(instance.address(), result.result(), new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
counter.succeed();
}
});
}
}
});
}
/**
* Installs all modules on a node.
*/
private void installModules(final Node node, final NetworkContext context, final Handler<AsyncResult<Void>> doneHandler) {
List<ModuleContext> modules = new ArrayList<>();
for (ComponentContext<?> component : context.components()) {
if (component.isModule()) {
modules.add(component.asModule());
}
}
final CountingCompletionHandler<Void> counter = new CountingCompletionHandler<Void>(modules.size()).setHandler(doneHandler);
for (ModuleContext module : modules) {
installModule(node, module, counter);
}
}
/**
* Installs a module on a node.
*/
private void installModule(final Node node, final ModuleContext module, final Handler<AsyncResult<Void>> doneHandler) {
node.installModule(module.module(), doneHandler);
}
/**
* Undeploys a network.
*/
private void undeployNetwork(final NetworkContext context, final Handler<AsyncResult<Void>> doneHandler) {
final CountingCompletionHandler<Void> complete = new CountingCompletionHandler<Void>(context.components().size());
complete.setHandler(new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
new DefaultFutureResult<Void>(result.cause()).setHandler(doneHandler);
} else {
new DefaultFutureResult<Void>((Void) null).setHandler(doneHandler);
}
}
});
undeployComponents(context.components(), complete);
}
/**
* Undeploys all network components.
*/
private void undeployComponents(Collection<ComponentContext<?>> components, final CountingCompletionHandler<Void> complete) {
for (final ComponentContext<?> component : components) {
final CountingCompletionHandler<Void> counter = new CountingCompletionHandler<Void>(component.instances().size());
counter.setHandler(new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
complete.fail(result.cause());
} else {
// Remove the component's context from the cluster.
data.remove(component.address(), new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
if (result.failed()) {
complete.fail(result.cause());
} else {
complete.succeed();
}
}
});
}
}
});
undeployInstances(component.instances(), counter);
}
}
/**
* Undeploys all component instances.
*/
private void undeployInstances(List<InstanceContext> instances, final CountingCompletionHandler<Void> counter) {
for (final InstanceContext instance : instances) {
if (instance.component().isModule()) {
undeployModule(instance, counter);
} else if (instance.component().isVerticle()) {
undeployVerticle(instance, counter);
}
}
}
/**
* Unwatches a component instance's status and context.
*/
private void unwatchInstance(final InstanceContext instance, final CountingCompletionHandler<Void> counter) {
Handler<MapEvent<String, String>> watchHandler = watchHandlers.remove(instance.address());
if (watchHandler != null) {
data.unwatch(instance.status(), watchHandler, new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
data.remove(instance.address(), new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
if (result.failed()) {
counter.fail(result.cause());
} else {
counter.succeed();
}
}
});
}
});
} else {
data.remove(instance.address(), new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
if (result.failed()) {
counter.fail(result.cause());
} else {
counter.succeed();
}
}
});
}
}
/**
* Undeploys a module component instance.
*/
private void undeployModule(final InstanceContext instance, final CountingCompletionHandler<Void> counter) {
deploymentIDs.remove(instance.address(), new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
if (result.failed()) {
counter.fail(result.cause());
} else if (result.result() != null) {
cluster.undeployModule(result.result(), new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
unwatchInstance(instance, counter);
}
});
} else {
unwatchInstance(instance, counter);
}
}
});
}
/**
* Undeploys a verticle component instance.
*/
private void undeployVerticle(final InstanceContext instance, final CountingCompletionHandler<Void> counter) {
deploymentIDs.remove(instance.address(), new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
if (result.failed()) {
counter.fail(result.cause());
} else if (result.result() != null) {
cluster.undeployVerticle(result.result(), new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
unwatchInstance(instance, counter);
}
});
} else {
unwatchInstance(instance, counter);
}
}
});
}
/**
* Updates a network.
*/
private void updateNetwork(final NetworkContext context, final Handler<AsyncResult<Void>> doneHandler) {
final CountingCompletionHandler<Void> complete = new CountingCompletionHandler<Void>(context.components().size());
complete.setHandler(new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
new DefaultFutureResult<Void>(result.cause()).setHandler(doneHandler);
} else {
new DefaultFutureResult<Void>((Void) null).setHandler(doneHandler);
}
}
});
updateComponents(context.components(), complete);
}
/**
* Updates all network components.
*/
private void updateComponents(Collection<ComponentContext<?>> components, final CountingCompletionHandler<Void> complete) {
for (final ComponentContext<?> component : components) {
final CountingCompletionHandler<Void> counter = new CountingCompletionHandler<Void>(component.instances().size());
counter.setHandler(new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
complete.fail(result.cause());
} else {
complete.succeed();
}
}
});
updateInstances(component.instances(), counter);
}
}
/**
* Updates all component instances.
*/
private void updateInstances(List<InstanceContext> instances, final CountingCompletionHandler<Void> counter) {
for (final InstanceContext instance : instances) {
data.put(instance.address(), Contexts.serialize(instance).encode(), new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
if (result.failed()) {
counter.fail(result.cause());
} else {
counter.succeed();
}
}
});
}
}
/**
* Handles a node joining the cluster.
*/
private void doNodeJoined(final Node node) {
tasks.runTask(new Handler<Task>() {
@Override
public void handle(final Task task) {
if (currentContext != null) {
log.info(String.format("%s - %s joined the cluster. Uploading components to new node", NetworkManager.this, node.address()));
installModules(node, currentContext, new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.failed()) {
log.error(result.cause());
} else {
log.info(String.format("%s - Successfully uploaded components to %s", NetworkManager.this, node.address()));
}
task.complete();
}
});
} else {
task.complete();
}
}
});
}
/**
* Handles a node leaving the cluster.
*/
private void doNodeLeft(final Node node) {
tasks.runTask(new Handler<Task>() {
@Override
public void handle(final Task task) {
if (currentContext != null) {
log.info(String.format("%s - %s left the cluster. Reassigning components", NetworkManager.this, node.address()));
deploymentNodes.keySet(new Handler<AsyncResult<Set<String>>>() {
@Override
public void handle(AsyncResult<Set<String>> result) {
if (result.succeeded()) {
final CountingCompletionHandler<Void> counter = new CountingCompletionHandler<Void>(result.result().size());
counter.setHandler(new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
task.complete();
}
});
// If the instance was deployed on the node that left the cluster then
// redeploy it on a new node.
for (final String instanceAddress : result.result()) {
deploymentNodes.get(instanceAddress, new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
if (result.succeeded() && result.result().equals(node.address())) {
// Look up the current instance context in the cluster.
data.get(instanceAddress, new Handler<AsyncResult<String>>() {
@Override
public void handle(AsyncResult<String> result) {
if (result.succeeded() && result.result() != null) {
deployInstance(Contexts.<InstanceContext>deserialize(new JsonObject(result.result())), counter);
} else {
counter.succeed();
}
}
});
} else {
counter.succeed();
}
}
});
}
}
}
});
} else {
task.complete();
}
}
});
}
@Override
public String toString() {
if (currentContext != null) {
return String.format("Network[%s]", currentContext.name());
}
return "Network[?]";
}
}