/**
* 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.core;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.cloudera.flume.conf.Context;
import com.cloudera.flume.conf.FlumeSpecException;
import com.cloudera.flume.conf.SinkFactory.SinkBuilder;
import com.cloudera.flume.reporter.ReportEvent;
import com.cloudera.util.MultipleIOException;
import com.google.common.base.Preconditions;
/**
* This sink always attempts to append to the primary. If the primary returns a
* exception, it increments a failure counter and then appends to the backup.
* These can be chained if multiple failovers are desired. (failover to another
* failover)
*
* This sink is very simple -- it will reattempt to open after every failure.
* Most folks will want to use BackOffFailOver. This remains because it is so
* much easier for testing.
*/
public class FailOverSink extends EventSink.Base {
static final Logger LOG = LoggerFactory.getLogger(FailOverSink.class);
// makes sure this doesn't cause conflict when constructor called
// concurrently.
static AtomicInteger uniq = new AtomicInteger();
int suffix;
final EventSink primary;
final EventSink backup;
long fails;
long backups;
boolean primaryOpen = false;
boolean backupOpen = false;
final String name;
final String R_FAILS;
final String R_BACKUPS;
public FailOverSink(EventSink primary, EventSink backup) {
Preconditions.checkNotNull(primary);
Preconditions.checkNotNull(backup);
this.primary = primary;
this.backup = backup;
suffix = uniq.getAndIncrement();
this.name = "failover_" + suffix;
this.R_FAILS = "rpt." + name + ".fails";
this.R_BACKUPS = "rpt." + name + ".backups";
}
@Override
public void append(Event e) throws IOException {
if (primaryOpen) {
try {
primary.append(e);
super.append(e);
return;
} catch (IOException ex) {
LOG.info("attempt to use primary failed: " + ex.getMessage());
fails++;
}
}
backup.append(e);
backups++;
super.append(e);
}
@Override
public void close() throws IOException {
List<IOException> exs = new ArrayList<IOException>(2);
try {
if (primaryOpen)
primary.close();
} catch (IOException ex) {
exs.add(ex);
}
try {
if (backupOpen)
backup.close();
} catch (IOException ex) {
exs.add(ex);
}
if (exs.size() != 0) {
throw MultipleIOException.createIOException(exs);
}
}
@Override
public void open() throws IOException {
IOException priEx = null;
try {
primary.open();
primaryOpen = true;
} catch (IOException ex) {
primaryOpen = false;
priEx = ex;
}
try {
backup.open();
backupOpen = true;
} catch (IOException ex) {
backupOpen = false;
if (priEx != null) {
// both failed, throw multi exception
IOException mioe = MultipleIOException.createIOException(Arrays.asList(
priEx, ex));
throw mioe;
}
// if the primary was ok, just continue. (if primary fails, will attempt
// to open backup again before falling back on it)
}
}
@Override
public ReportEvent getReport() {
ReportEvent rpt = super.getReport();
rpt.setLongMetric(R_FAILS, fails);
rpt.setLongMetric(R_BACKUPS, backups);
return rpt;
}
@Override
public void getReports(String namePrefix, Map<String, ReportEvent> reports) {
super.getReports(namePrefix, reports);
primary.getReports(namePrefix + getName() + ".primary.", reports);
backup.getReports(namePrefix + getName() + ".backup.", reports);
}
@Override
public String getName() {
return name;
}
public static SinkBuilder builder() {
return new SinkBuilder() {
@Override
public EventSink build(Context context, String... argv) {
Preconditions.checkArgument(argv.length == 2);
String primary = argv[0];
String secondary = argv[1];
try {
EventSink pri = new CompositeSink(context, primary);
EventSink sec = new CompositeSink(context, secondary);
return new FailOverSink(pri, sec);
} catch (FlumeSpecException e) {
LOG.warn("Spec parsing problem", e);
throw new IllegalArgumentException("Spec parsing problem", e);
}
}
};
}
}