/**
* Licensed to Cloudera, Inc. under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Cloudera, Inc. licenses this file
* to you 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.cloudera.flume.master.failover;
import static com.cloudera.flume.conf.PatternMatch.recursive;
import static com.cloudera.flume.conf.PatternMatch.var;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.tree.CommonTree;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.cloudera.flume.conf.FlumeBuilder;
import com.cloudera.flume.conf.FlumePatterns;
import com.cloudera.flume.conf.FlumeSpecException;
import com.cloudera.flume.conf.FlumeSpecGen;
import com.cloudera.flume.conf.PatternMatch;
import com.cloudera.flume.master.ConfigurationManager;
import com.cloudera.flume.master.TranslatingConfigurationManager;
import com.cloudera.flume.master.Translator;
import com.cloudera.flume.master.availability.FailoverChainManager;
import com.google.common.base.Preconditions;
/**
* This translates autoXXXsinks into full configurations.
*/
public class FailoverConfigurationManager extends
TranslatingConfigurationManager implements Translator {
public static final Logger LOG = LoggerFactory
.getLogger(FailoverConfigurationManager.class);
FailoverChainManager failchainMan;
final public static String NAME = "FailoverTranslator";
final public static String AUTO_BE = "autoBEChain";
final public static String AUTO_DFO = "autoDFOChain";
final public static String AUTO_E2E = "autoE2EChain";
/**
* Create Failover chain translating manager.
*/
public FailoverConfigurationManager(ConfigurationManager parent,
ConfigurationManager self, FailoverChainManager fcMan) {
super(parent, self);
Preconditions.checkArgument(fcMan != null);
this.failchainMan = fcMan;
}
/**
* Remove the logical node.
*/
@Override
public void removeLogicalNode(String logicNode) throws IOException {
failchainMan.removeCollector(logicNode);
super.removeLogicalNode(logicNode);
}
/**
* Sources that are collectors are not translated, however they are registered
*/
@Override
public String translateSource(String lnode, String source)
throws FlumeSpecException {
Preconditions.checkArgument(lnode != null);
Preconditions.checkArgument(source != null);
// register the source.
if ("autoCollectorSource".equals(source)) {
failchainMan.addCollector(lnode);
source = "logicalSource"; // convert to logical source.
} else {
// remove if was previously a collector
failchainMan.removeCollector(lnode);
}
return source;
}
/**
* This translates all autoBEChain, autoE2EChain, and autoDFOChain into low
* level sinks taking the failover chain mangers info into account.
*/
@Override
public String translateSink(String lnode, String sink)
throws FlumeSpecException {
Preconditions.checkArgument(lnode != null);
Preconditions.checkArgument(sink != null);
String xsink;
try {
List<String> failovers = failchainMan.getFailovers(lnode);
xsink = FlumeSpecGen.genEventSink(substBEChains(sink, failovers));
xsink = FlumeSpecGen.genEventSink(substDFOChainsNoLet(xsink, failovers));
xsink = FlumeSpecGen.genEventSink(substE2EChainsSimple(xsink, failovers));
return xsink;
} catch (RecognitionException e) {
throw new FlumeSpecException(e.getMessage());
}
}
/**
* Takes a full sink specification and substitutes 'autoBEChain' with an
* expanded best effort failover chain.
*
* 'autoBEChain' gets translated to
*
* < < logicalSink(arg1) ? <... ? null > > >
*
* Basically this will try to to send data to logicalsink(arg1), and failing
* that to logicalsink(arg2) etc. If all logicalSinks fail it will fall back
* to a null sink which drops messages. The failover sink by default has a
* timed backoff policy and will reattempt opening and sending to the
* different sink in the failover chains.
*
*/
static CommonTree substBEChains(String sink, List<String> collectors)
throws RecognitionException, FlumeSpecException {
PatternMatch bePat = recursive(var("be", FlumePatterns.sink(AUTO_BE)));
CommonTree sinkTree = FlumeBuilder.parseSink(sink);
Map<String, CommonTree> beMatches = bePat.match(sinkTree);
ArrayList<String> collSnks = new ArrayList<String>();
for (String coll : collectors) {
collSnks.add("{ lazyOpen => logicalSink(\"" + coll + "\") }");
}
collSnks.add("null");
if (beMatches == null) {
// bail out early
return sinkTree;
}
while (beMatches != null) {
// found a autoBEChain, replace it with the chain.
CommonTree beTree = beMatches.get("be");
// generate
CommonTree beFailChain = buildFailChainAST("%s", collSnks);
// Check if beFailChain is null
if (beFailChain == null) {
beFailChain = FlumeBuilder
.parseSink("fail(\"no physical collectors\")");
}
// subst
int idx = beTree.getChildIndex();
CommonTree parent = beTree.parent;
if (parent == null) {
sinkTree = beFailChain;
} else {
parent.replaceChildren(idx, idx, beFailChain);
}
// patern match again.
beMatches = bePat.match(sinkTree);
}
return sinkTree;
}
/**
* This is a simpler DFO that doesn't use let statements. This approach will
* use more resources (more ports used up on this node and the downstream node
* because of no sharing). Unfortunately 'let's end up being very tricky to
* use in the cases where failures occur, and need more thought.
*
* 'autoDFOChain' becomes:
*
* < < logicalSink(arg1) ? ... > ? { diskFailover => { insistentAppend => {
* stubbornAppend => { insistentOpen => < logicalSink(arg1) ? ... > } } } } >
*
* This pipeline writes attempts to send data to each of the logical node
* arg1, then then to logical node arg2, etc. If the logical nodes all fail,
* we go to the diskFailover, which writes data to the local log. The subsink
* of the diskFailover has a subservient DriverThread that will attempt to
* send data to logical sink arg1, and then to logical sink arg2, etc.. If all
* fail of these fail, the stubbornAppend causes the entire failover chain to
* be closed and then reopened. The insistentOpen insistentOpen ensures that
* they are tried again after an backing off. Stubborn append gives up after a
* second failure -- the insistentAppend wrapping it ensures that it will
* continue retrying while the sink is open.
*/
static CommonTree substDFOChainsNoLet(String sink, List<String> collectors)
throws RecognitionException, FlumeSpecException {
PatternMatch dfoPat = recursive(var("dfo", FlumePatterns.sink(AUTO_DFO)));
CommonTree sinkTree = FlumeBuilder.parseSink(sink);
Map<String, CommonTree> dfoMatches = dfoPat.match(sinkTree);
if (dfoMatches == null) {
return sinkTree;
}
while (dfoMatches != null) {
// found a autoDFOChain, replace it with the chain.
CommonTree dfoTree = dfoMatches.get("dfo");
// All the logical sinks are lazy individually
CommonTree dfoPrimaryChain = buildFailChainAST(
"{ lazyOpen => logicalSink(\"%s\") }", collectors);
// Check if dfo is null
if (dfoPrimaryChain == null) {
dfoPrimaryChain = FlumeBuilder.parseSink("fail(\"no collectors\")");
}
// diskfailover's subsink needs to never give up. So we wrap it with an
// inistentAppend. But append can fail if its subsink is not open. So
// we add a stubborn append (it closes and reopens a subsink) and retries
// opening the chain using the insistentOpen
String dfo = "< " + FlumeSpecGen.genEventSink(dfoPrimaryChain)
+ " ? {diskFailover => "
+ "{ insistentAppend => { stubbornAppend => { insistentOpen =>"
+ FlumeSpecGen.genEventSink(dfoPrimaryChain) + " } } } } >";
CommonTree newDfoTree = FlumeBuilder.parseSink(dfo);
// subst
int idx = dfoTree.getChildIndex();
CommonTree parent = dfoTree.parent;
if (parent == null) {
sinkTree = newDfoTree;
} else {
parent.replaceChildren(idx, idx, newDfoTree);
}
// pattern match again.
dfoMatches = dfoPat.match(sinkTree);
}
return sinkTree;
}
/**
* Takes a full sink specification and substitutes 'autoDFOChain' with an
* expanded disk failover mode failover chain.
*
* This version is deprecated because it uses 'let' expressions. 'let'
* expressions semantics are not clear in the face of failures.
*/
@Deprecated
static CommonTree substDFOChains(String sink, List<String> collectors)
throws RecognitionException, FlumeSpecException {
PatternMatch dfoPat = recursive(var("dfo", FlumePatterns.sink(AUTO_DFO)));
CommonTree sinkTree = FlumeBuilder.parseSink(sink);
Map<String, CommonTree> dfoMatches = dfoPat.match(sinkTree);
if (dfoMatches == null) {
return sinkTree;
}
while (dfoMatches != null) {
// found a autoDFOChain, replace it with the chain.
CommonTree dfoTree = dfoMatches.get("dfo");
CommonTree dfoFailChain = buildFailChainAST(
"{ lazyOpen => { stubbornAppend => logicalSink(\"%s\") } } ",
collectors);
// Check if dfo is null
if (dfoFailChain == null) {
dfoFailChain = FlumeBuilder.parseSink("fail(\"no collectors\")");
}
String dfo = "let primary := " + FlumeSpecGen.genEventSink(dfoFailChain)
+ " in "
+ "< primary ? {diskFailover => { insistentOpen => primary} } >";
CommonTree newDfoTree = FlumeBuilder.parseSink(dfo);
// subst
int idx = dfoTree.getChildIndex();
CommonTree parent = dfoTree.parent;
if (parent == null) {
sinkTree = newDfoTree;
} else {
parent.replaceChildren(idx, idx, newDfoTree);
}
// pattern match again.
dfoMatches = dfoPat.match(sinkTree);
}
return sinkTree;
}
/**
* Takes a full sink specification and substitutes 'autoE2EChain' with an
* expanded wal+end2end ack chain.
*
* This version at one point was different from substE2EChainSimple's
* implementation but they have not convernged. This one should likely be
* removed in the future.
*/
@Deprecated
static CommonTree substE2EChains(String sink, List<String> collectors)
throws RecognitionException, FlumeSpecException {
PatternMatch e2ePat = recursive(var("e2e", FlumePatterns.sink(AUTO_E2E)));
CommonTree sinkTree = FlumeBuilder.parseSink(sink);
Map<String, CommonTree> e2eMatches = e2ePat.match(sinkTree);
if (e2eMatches == null) {
// bail out early.
return sinkTree;
}
while (e2eMatches != null) {
// found a autoE2EChain, replace it with the chain.
CommonTree beTree = e2eMatches.get("e2e");
// generate
CommonTree beFailChain = buildFailChainAST("logicalSink(\"%s\") ",
collectors);
// Check if beFailChain is null
if (beFailChain == null) {
beFailChain = FlumeBuilder.parseSink("fail(\"no collectors\")");
}
// subst
int idx = beTree.getChildIndex();
CommonTree parent = beTree.parent;
if (parent == null) {
sinkTree = beFailChain;
} else {
parent.replaceChildren(idx, idx, beFailChain);
}
// pattern match again.
e2eMatches = e2ePat.match(sinkTree);
}
// wrap the sink with the ackedWriteAhead
CommonTree wrapper = FlumeBuilder
.parseSink("{ ackedWriteAhead => { stubbornAppend => { insistentOpen => null } } }");
PatternMatch nullPath = recursive(var("x", FlumePatterns.sink("null")));
CommonTree replace = nullPath.match(wrapper).get("x");
int idx = replace.getChildIndex();
replace.parent.replaceChildren(idx, idx, sinkTree);
return wrapper;
}
/**
* Takes a full sink specification and substitutes 'autoE2EChain' with an
* expanded wal+end2end ack chain. It just replaces the sink and does not
* attempt any sandwiching of decorators
*
* 'autoE2EChain' becomes:
*
* { ackedWriteAhead => { stubbornAppend => { insistentOpen => <
* logicalSink(arg1) ? ... > } } }
*
* This pipeline writes data to the WAL adding ack tags. In the WAL's subsink
* in a subservient DriverThread will attempt to send data to logical sink
* arg1, and then to logicla sink arg2, etc.. If all fail, stubbornAppend
* causes the entire failover chain to be closed and then reopened. If all the
* elements of the failover chain still fail, the insistentOpen ensures that
* they are tried again after an backing off.
*/
static CommonTree substE2EChainsSimple(String sink, List<String> collectors)
throws RecognitionException, FlumeSpecException {
PatternMatch e2ePat = recursive(var("e2e", FlumePatterns.sink(AUTO_E2E)));
CommonTree sinkTree = FlumeBuilder.parseSink(sink);
Map<String, CommonTree> e2eMatches = e2ePat.match(sinkTree);
if (e2eMatches == null) {
// bail out early.
return sinkTree;
}
while (e2eMatches != null) {
// found a autoE2EChain, replace it with the chain.
CommonTree e2eTree = e2eMatches.get("e2e");
// generate
CommonTree e2eFailChain = buildFailChainAST("logicalSink(\"%s\") ",
collectors);
// Check if beFailChain is null
if (e2eFailChain == null) {
e2eFailChain = FlumeBuilder.parseSink("fail(\"no collectors\")");
}
// now lets wrap the beFailChain with the ackedWriteAhead
String translated = "{ ackedWriteAhead => { stubbornAppend => { insistentOpen => "
+ FlumeSpecGen.genEventSink(e2eFailChain) + " } } }";
CommonTree wrapper = FlumeBuilder.parseSink(translated);
// subst
int idx = e2eTree.getChildIndex();
CommonTree parent = e2eTree.parent;
if (parent == null) {
sinkTree = wrapper;
} else {
parent.replaceChildren(idx, idx, wrapper);
}
// pattern match again.
e2eMatches = e2ePat.match(sinkTree);
}
// wrap the sink with the ackedWriteAhead
return sinkTree;
}
/**
* This current version requires a "%s" that gets replaced with the value from
* the list.
*
* Warning! this is a potential security problem.
*/
static CommonTree buildFailChainAST(String spec, List<String> collectors)
throws FlumeSpecException, RecognitionException {
// iterate through the list backwards
CommonTree cur = null;
for (int i = collectors.size() - 1; i >= 0; i--) {
String s = collectors.get(i);
// this should be a composite sink.
String failoverSpec = String.format(spec, s);
LOG.debug("failover spec is : " + failoverSpec);
CommonTree branch = FlumeBuilder.parseSink(failoverSpec);
if (cur == null) {
cur = branch;
continue;
}
String fail = "< " + FlumeSpecGen.genEventSink(branch) + " ? "
+ FlumeSpecGen.genEventSink(cur) + " >";
cur = FlumeBuilder.parseSink(fail);
}
return cur;
}
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return NAME;
}
}