/**
* Copyright 2017 Netflix, 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.netflix.raigad.identity;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.netflix.raigad.configuration.IConfiguration;
import com.netflix.raigad.utils.RetriableCallable;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
/**
* This class provides the central place to create and consume the identity of the instance
*/
@Singleton
public class InstanceManager {
private static final Logger logger = LoggerFactory.getLogger(InstanceManager.class);
private static final String COMMA_SEPARATOR = ",";
private static final String PARAM_SEPARATOR = "=";
private final IRaigadInstanceFactory instanceFactory;
private final IMembership membership;
private final IConfiguration config;
private RaigadInstance thisInstance;
@Inject
public InstanceManager(IRaigadInstanceFactory instanceFactory, IMembership membership, IConfiguration config) throws Exception {
this.instanceFactory = instanceFactory;
this.membership = membership;
this.config = config;
init();
}
private void init() throws Exception {
logger.info("Deregistering dead instances");
new RetriableCallable<Void>() {
@Override
public Void retriableCall() throws Exception {
deregisterInstance(instanceFactory, config);
return null;
}
}.call();
logger.info("Registering this instance");
thisInstance = new RetriableCallable<RaigadInstance>() {
@Override
public RaigadInstance retriableCall() throws Exception {
RaigadInstance instance = registerInstance(instanceFactory, config);
return instance;
}
}.call();
logger.info("Raigad instance details: " + thisInstance.toString());
}
private RaigadInstance registerInstance(IRaigadInstanceFactory instanceFactory, IConfiguration config) throws Exception {
return instanceFactory.create(
config.getAppName(),
config.getDC() + "." + config.getInstanceId(),
config.getInstanceId(), config.getHostname(),
config.getHostIP(), config.getRac(), config.getDC(), config.getASGName(), null);
}
private void deregisterInstance(IRaigadInstanceFactory instanceFactory, IConfiguration config) throws Exception {
final List<RaigadInstance> allInstances = getInstanceList();
HashSet<String> asgNames = new HashSet<>();
for (RaigadInstance raigadInstance : allInstances) {
if (!asgNames.contains(raigadInstance.getAsg())) {
asgNames.add(raigadInstance.getAsg());
}
}
logger.info("Known instances: {}", allInstances);
logger.info("Known ASG's: {}", StringUtils.join(asgNames, ","));
Map<String, List<String>> instancesPerAsg = membership.getRacMembership(asgNames);
logger.info("Known instances per ASG: {}", instancesPerAsg);
for (RaigadInstance knownInstance : allInstances) {
// Test same region and if it is alive.
// TODO: Provide a config property to choose same DC/Region
if (instancesPerAsg.containsKey(knownInstance.getAsg())) {
if (!knownInstance.getAsg().equals(config.getASGName())) {
logger.info("Skipping {} - different ASG", knownInstance.getInstanceId());
continue;
}
if (!knownInstance.getAvailabilityZone().equals(config.getRac())) {
logger.info("Skipping {} - different AZ", knownInstance.getInstanceId());
continue;
}
if (instancesPerAsg.get(config.getASGName()).contains(knownInstance.getInstanceId())) {
logger.info("Skipping {} - legitimate node", knownInstance.getInstanceId());
continue;
}
logger.info("Found dead instance: " + knownInstance.getInstanceId());
instanceFactory.delete(knownInstance);
}
else if (config.isMultiDC()) {
logger.info("Multi DC setup, skipping unknown instances (" + knownInstance.getInstanceId() + ")");
}
else if (config.amISourceClusterForTribeNode()) {
logger.info("Tribe setup, skipping unknown instances (" + knownInstance.getInstanceId() + ")");
}
else {
logger.info("Found dead instance: " + knownInstance.getInstanceId());
instanceFactory.delete(knownInstance);
}
}
}
public RaigadInstance getInstance() {
return thisInstance;
}
public List<RaigadInstance> getAllInstances() {
return getInstanceList();
}
private List<RaigadInstance> getInstanceList() {
List<RaigadInstance> instances = new ArrayList<RaigadInstance>();
// Considering same cluster will not serve as a tribe node and source cluster for the tribe node
if (config.amITribeNode()) {
String clusterParams = config.getCommaSeparatedSourceClustersForTribeNode();
assert (clusterParams != null) : "I am a tribe node but I need one or more source clusters";
String[] clusters = StringUtils.split(clusterParams, COMMA_SEPARATOR);
assert (clusters.length != 0) : "One or more clusters needed";
List<String> sourceClusters = new ArrayList<>();
// Adding current cluster
sourceClusters.add(config.getAppName());
// Common settings
for (int i = 0; i < clusters.length; i ++) {
String[] clusterAndPort = clusters[i].split(PARAM_SEPARATOR);
assert (clusterAndPort.length != 2) : "Cluster name or transport port is missing in configuration";
sourceClusters.add(clusterAndPort[0]);
logger.info("Adding cluster = <{}> ", clusterAndPort[0]);
}
for (String sourceClusterName : sourceClusters) {
instances.addAll(instanceFactory.getAllIds(sourceClusterName));
}
logger.info("Printing tribe node related nodes...");
for (RaigadInstance instance:instances) {
logger.info(instance.toString());
}
}
else {
instances.addAll(instanceFactory.getAllIds(config.getAppName()));
}
if (config.isDebugEnabled()) {
for (RaigadInstance instance : instances) {
logger.debug(instance.toString());
}
}
return instances;
}
public List<RaigadInstance> getAllInstancesPerCluster(String clusterName) {
return getInstanceListPerCluster(clusterName);
}
private List<RaigadInstance> getInstanceListPerCluster(String clusterName) {
List<RaigadInstance> instances = new ArrayList<RaigadInstance>();
instances.addAll(instanceFactory.getAllIds(clusterName.trim().toLowerCase()));
if (config.isDebugEnabled()) {
for (RaigadInstance instance : instances) {
logger.debug(instance.toString());
}
}
return instances;
}
public boolean isMaster() {
//For non-dedicated deployments, return true (every node can be a master)
return (!config.isAsgBasedDedicatedDeployment() || config.getASGName().toLowerCase().contains("master"));
}
}