/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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 org.apache.hadoop.chukwa.extraction.demux;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import org.apache.hadoop.chukwa.conf.ChukwaConfiguration;
import org.apache.hadoop.chukwa.extraction.CHUKWA_CONSTANT;
import org.apache.hadoop.chukwa.util.ExceptionUtil;
import org.apache.hadoop.chukwa.util.NagiosHelper;
import org.apache.hadoop.chukwa.util.DaemonWatcher;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.filecache.DistributedCache;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.PathFilter;
import org.apache.hadoop.util.ToolRunner;
import org.apache.log4j.Logger;
public class DemuxManager implements CHUKWA_CONSTANT {
static Logger log = Logger.getLogger(DemuxManager.class);
static int globalErrorcounter = 0;
protected int ERROR_SLEEP_TIME = 60;
protected int NO_DATASINK_SLEEP_TIME = 20;
protected int DEFAULT_MAX_FILES_PER_DEMUX = 500;
protected int DEFAULT_REDUCER_COUNT = 8;
protected int demuxReducerCount = 0;
protected ChukwaConfiguration conf = null;
protected FileSystem fs = null;
protected int reprocess = 0;
protected boolean sendAlert = true;
protected SimpleDateFormat dayTextFormat = new java.text.SimpleDateFormat("yyyyMMdd");
protected volatile boolean isRunning = true;
private final static String demuxPath = System.getenv("CHUKWA_HOME")+File.separator+"lib"+File.separator+"demux";
final private static PathFilter DATA_SINK_FILTER = new PathFilter() {
public boolean accept(Path file) {
return file.getName().endsWith(".done");
}
};
public static void main(String[] args) throws Exception {
DaemonWatcher.createInstance("DemuxManager");
DemuxManager manager = new DemuxManager();
manager.start();
}
public DemuxManager() throws Exception {
this.conf = new ChukwaConfiguration();
init();
}
public DemuxManager(ChukwaConfiguration conf) throws Exception {
this.conf = conf;
init();
}
protected void init() throws IOException, URISyntaxException {
String fsName = conf.get(HDFS_DEFAULT_NAME_FIELD);
fs = FileSystem.get(new URI(fsName), conf);
}
public void shutdown() {
this.isRunning = false;
}
public int getReprocess() {
return reprocess;
}
/**
* Start the Demux Manager daemon
* @throws Exception
*/
public void start() throws Exception {
String chukwaRootDir = conf.get(CHUKWA_ROOT_DIR_FIELD, DEFAULT_CHUKWA_ROOT_DIR_NAME);
if ( ! chukwaRootDir.endsWith("/") ) {
chukwaRootDir += "/";
}
log.info("chukwaRootDir:" + chukwaRootDir);
String demuxRootDir = chukwaRootDir + DEFAULT_DEMUX_PROCESSING_DIR_NAME;
String demuxErrorDir = demuxRootDir + DEFAULT_DEMUX_IN_ERROR_DIR_NAME;
String demuxInputDir = demuxRootDir + DEFAULT_DEMUX_MR_INPUT_DIR_NAME;
String demuxOutputDir = demuxRootDir + DEFAULT_DEMUX_MR_OUTPUT_DIR_NAME;
String dataSinkDir = conf.get(CHUKWA_DATA_SINK_DIR_FIELD, chukwaRootDir +DEFAULT_CHUKWA_LOGS_DIR_NAME);
if ( ! dataSinkDir.endsWith("/") ) {
dataSinkDir += "/";
}
log.info("dataSinkDir:" + dataSinkDir);
String postProcessDir = conf.get(CHUKWA_POST_PROCESS_DIR_FIELD, chukwaRootDir +DEFAULT_CHUKWA_POSTPROCESS_DIR_NAME);
if ( ! postProcessDir.endsWith("/") ) {
postProcessDir += "/";
}
log.info("postProcessDir:" + postProcessDir);
String archiveRootDir = conf.get(CHUKWA_ARCHIVE_DIR_FIELD, chukwaRootDir +DEFAULT_CHUKWA_DATASINK_DIR_NAME);
if ( ! archiveRootDir.endsWith("/") ) {
archiveRootDir += "/";
}
log.info("archiveRootDir:" + archiveRootDir);
demuxReducerCount = conf.getInt(CHUKWA_DEMUX_REDUCER_COUNT_FIELD, DEFAULT_REDUCER_COUNT);
log.info("demuxReducerCount:" + demuxReducerCount);
String nagiosHost = conf.get(CHUKWA_NAGIOS_HOST_FIELD);
int nagiosPort = conf.getInt(CHUKWA_NAGIOS_PORT_FIELD,0);
String reportingHost = conf.get(CHUKWA_REPORTING_HOST_FIELD);
log.info("Nagios information: nagiosHost:" + nagiosHost + ", nagiosPort:"
+ nagiosPort + ", reportingHost:" + reportingHost);
if (nagiosHost == null || nagiosHost.length() == 0 || nagiosPort == 0 || reportingHost.length() == 0 || reportingHost == null) {
sendAlert = false;
log.warn("Alerting is OFF");
}
boolean demuxReady = false;
while (isRunning) {
try {
demuxReady = false;
if (globalErrorcounter > 5) {
log.warn("==================\nToo many errors, Bail out!\n==================");
DaemonWatcher.bailout(-1);
}
// Check for anomalies
if (checkDemuxOutputDir(demuxOutputDir) == true) {
// delete current demux output dir
if ( deleteDemuxOutputDir(demuxOutputDir) == false ) {
log.warn("Cannot delete an existing demux output directory!");
throw new IOException("Cannot move demuxOutput to postProcess!");
}
continue;
} else if (checkDemuxInputDir(demuxInputDir) == true) { // dataSink already there
reprocess++;
// Data has been processed more than 3 times ... move to InError directory
if (reprocess > 3) {
if (moveDataSinkFilesToDemuxErrorDirectory(demuxInputDir,demuxErrorDir) == false) {
log.warn("Cannot move dataSink files to DemuxErrorDir!");
throw new IOException("Cannot move dataSink files to DemuxErrorDir!");
}
reprocess = 0;
continue;
}
log.error("Demux inputDir aready contains some dataSink files,"
+ " going to reprocess, reprocessCount=" + reprocess);
demuxReady = true;
} else { // standard code path
reprocess = 0;
// Move new dataSink Files
if (moveDataSinkFilesToDemuxInputDirectory(dataSinkDir, demuxInputDir) == true) {
demuxReady = true; // if any are available
} else {
demuxReady = false; // if none
}
}
// start a new demux ?
if (demuxReady == true) {
boolean demuxStatus = processData(dataSinkDir, demuxInputDir, demuxOutputDir,
postProcessDir, archiveRootDir);
sendDemuxStatusToNagios(nagiosHost,nagiosPort,reportingHost,demuxErrorDir,demuxStatus,null);
} else {
log.info("Demux not ready so going to sleep ...");
Thread.sleep(NO_DATASINK_SLEEP_TIME * 1000);
}
}catch(Throwable e) {
log.warn(e);
globalErrorcounter ++;
sendDemuxStatusToNagios(nagiosHost,nagiosPort,reportingHost,demuxErrorDir,false, e.getMessage());
try { Thread.sleep(ERROR_SLEEP_TIME * 1000); }
catch (InterruptedException e1) {/*do nothing*/ }
init();
}
}
}
/**
* Send NSCA status to Nagios
* @param nagiosHost
* @param nagiosPort
* @param reportingHost
* @param demuxInErrorDir
* @param demuxStatus
* @param exception
*/
protected void sendDemuxStatusToNagios(String nagiosHost,int nagiosPort,String reportingHost,
String demuxInErrorDir,boolean demuxStatus,String demuxException) {
if (sendAlert == false) {
return;
}
boolean demuxInErrorStatus = true;
String demuxInErrorMsg = "";
try {
Path pDemuxInErrorDir = new Path(demuxInErrorDir);
if ( fs.exists(pDemuxInErrorDir)) {
FileStatus[] demuxInErrorDirs = fs.listStatus(pDemuxInErrorDir);
if (demuxInErrorDirs.length == 0) {
demuxInErrorStatus = false;
}
}
} catch (Throwable e) {
demuxInErrorMsg = e.getMessage();
log.warn(e);
}
// send Demux status
if (demuxStatus == true) {
NagiosHelper.sendNsca(nagiosHost,nagiosPort,reportingHost,"DemuxProcessing","Demux OK",NagiosHelper.NAGIOS_OK);
} else {
NagiosHelper.sendNsca(nagiosHost,nagiosPort,reportingHost,"DemuxProcessing","Demux failed. " + demuxException,NagiosHelper.NAGIOS_CRITICAL);
}
// send DemuxInErrorStatus
if (demuxInErrorStatus == false) {
NagiosHelper.sendNsca(nagiosHost,nagiosPort,reportingHost,"DemuxInErrorDirectory","DemuxInError OK",NagiosHelper.NAGIOS_OK);
} else {
NagiosHelper.sendNsca(nagiosHost,nagiosPort,reportingHost,"DemuxInErrorDirectory","DemuxInError not empty -" + demuxInErrorMsg,NagiosHelper.NAGIOS_CRITICAL);
}
}
/**
* Process Data, i.e.
* - run demux
* - move demux output to postProcessDir
* - move dataSink file to archiveDir
*
* @param dataSinkDir
* @param demuxInputDir
* @param demuxOutputDir
* @param postProcessDir
* @param archiveDir
* @return True iff succeed
* @throws IOException
*/
protected boolean processData(String dataSinkDir, String demuxInputDir,
String demuxOutputDir, String postProcessDir, String archiveDir) throws IOException {
boolean demuxStatus = false;
long startTime = System.currentTimeMillis();
demuxStatus = runDemux(demuxInputDir, demuxOutputDir);
log.info("Demux Duration: " + (System.currentTimeMillis() - startTime));
if (demuxStatus == false) {
log.warn("Demux failed!");
} else {
// Move demux output to postProcessDir
if (checkDemuxOutputDir(demuxOutputDir)) {
if (moveDemuxOutputDirToPostProcessDirectory(demuxOutputDir, postProcessDir) == false) {
log.warn("Cannot move demuxOutput to postProcess! bail out!");
throw new IOException("Cannot move demuxOutput to postProcess! bail out!");
}
} else {
log.warn("Demux processing OK but no output");
}
// Move DataSink Files to archiveDir
if (moveDataSinkFilesToArchiveDirectory(demuxInputDir, archiveDir) == false) {
log.warn("Cannot move datasinkFile to archive! bail out!");
throw new IOException("Cannot move datasinkFile to archive! bail out!");
}
}
return demuxStatus;
}
/**
* Submit and Run demux Job
* @param demuxInputDir
* @param demuxOutputDir
* @return true id Demux succeed
*/
protected boolean runDemux(String demuxInputDir, String demuxOutputDir) {
String[] demuxParams;
int i=0;
Demux.addParsers(conf);
demuxParams = new String[4];
demuxParams[i++] = "-r";
demuxParams[i++] = "" + demuxReducerCount;
demuxParams[i++] = demuxInputDir;
demuxParams[i++] = demuxOutputDir;
try {
return ( 0 == ToolRunner.run(this.conf,new Demux(), demuxParams) );
} catch (Throwable e) {
e.printStackTrace();
log.error("failed to run demux", e);
globalErrorcounter ++;
}
return false;
}
/**
* Move dataSink files to Demux input directory
* @param dataSinkDir
* @param demuxInputDir
* @return true if there's any dataSink files ready to be processed
* @throws IOException
*/
protected boolean moveDataSinkFilesToDemuxInputDirectory(
String dataSinkDir, String demuxInputDir) throws IOException {
Path pDataSinkDir = new Path(dataSinkDir);
Path pDemuxInputDir = new Path(demuxInputDir);
log.info("dataSinkDir: " + dataSinkDir);
log.info("demuxInputDir: " + demuxInputDir);
boolean containsFile = false;
FileStatus[] dataSinkFiles = fs.listStatus(pDataSinkDir,DATA_SINK_FILTER);
if (dataSinkFiles.length > 0) {
setup(pDemuxInputDir);
}
int maxFilesPerDemux = 0;
for (FileStatus fstatus : dataSinkFiles) {
boolean rename = fs.rename(fstatus.getPath(),pDemuxInputDir);
log.info("Moving " + fstatus.getPath() + " to " + pDemuxInputDir +", status is:" + rename);
maxFilesPerDemux ++;
containsFile = true;
if (maxFilesPerDemux >= DEFAULT_MAX_FILES_PER_DEMUX) {
log.info("Max File per Demux reached:" + maxFilesPerDemux);
break;
}
}
return containsFile;
}
/**
* Move sourceFolder inside destFolder
* @param dataSinkDir : ex chukwa/demux/inputDir
* @param demuxErrorDir : ex /chukwa/demux/inError
* @return true if able to move chukwa/demux/inputDir to /chukwa/demux/inError/<YYYYMMDD>/demuxInputDirXXX
* @throws IOException
*/
protected boolean moveDataSinkFilesToDemuxErrorDirectory(
String dataSinkDir, String demuxErrorDir) throws IOException {
demuxErrorDir += "/" + dayTextFormat.format(System.currentTimeMillis());
return moveFolder(dataSinkDir,demuxErrorDir,"demuxInputDir");
}
/**
* Move sourceFolder inside destFolder
* @param demuxInputDir: ex chukwa/demux/inputDir
* @param archiveDirectory: ex /chukwa/archives
* @return true if able to move chukwa/demux/inputDir to /chukwa/archives/raw/<YYYYMMDD>/dataSinkDirXXX
* @throws IOException
*/
protected boolean moveDataSinkFilesToArchiveDirectory(
String demuxInputDir, String archiveDirectory) throws IOException {
archiveDirectory += "/" + dayTextFormat.format(System.currentTimeMillis());
return moveFolder(demuxInputDir,archiveDirectory,"dataSinkDir");
}
/**
* Move sourceFolder inside destFolder
* @param demuxOutputDir: ex chukwa/demux/outputDir
* @param postProcessDirectory: ex /chukwa/postProcess
* @return true if able to move chukwa/demux/outputDir to /chukwa/postProcess/demuxOutputDirXXX
* @throws IOException
*/
protected boolean moveDemuxOutputDirToPostProcessDirectory(
String demuxOutputDir, String postProcessDirectory) throws IOException {
return moveFolder(demuxOutputDir,postProcessDirectory,"demuxOutputDir");
}
/**
* Test if demuxInputDir exists
* @param demuxInputDir
* @return true if demuxInputDir exists
* @throws IOException
*/
protected boolean checkDemuxInputDir(String demuxInputDir)
throws IOException {
return dirExists(demuxInputDir);
}
/**
* Test if demuxOutputDir exists
* @param demuxOutputDir
* @return true if demuxOutputDir exists
* @throws IOException
*/
protected boolean checkDemuxOutputDir(String demuxOutputDir)
throws IOException {
return dirExists(demuxOutputDir);
}
/**
* Delete DemuxOutput directory
* @param demuxOutputDir
* @return true if succeed
* @throws IOException
*/
protected boolean deleteDemuxOutputDir(String demuxOutputDir) throws IOException
{
return fs.delete(new Path(demuxOutputDir), true);
}
/**
* Create directory if !exists
* @param directory
* @throws IOException
*/
protected void setup(Path directory) throws IOException {
if ( ! fs.exists(directory)) {
fs.mkdirs(directory);
}
}
/**
* Check if source exists and if source is a directory
* @param f source file
*/
protected boolean dirExists(String directory) throws IOException {
Path pDirectory = new Path(directory);
return (fs.exists(pDirectory) && fs.getFileStatus(pDirectory).isDir());
}
/**
* Move sourceFolder inside destFolder
* @param srcDir
* @param destDir
* @return
* @throws IOException
*/
protected boolean moveFolder(String srcDir,String destDir, String prefix) throws IOException {
if (!destDir.endsWith("/")) {
destDir +="/";
}
Path pSrcDir = new Path(srcDir);
Path pDestDir = new Path(destDir );
setup(pDestDir);
destDir += prefix +"_" +System.currentTimeMillis();
Path pFinalDestDir = new Path(destDir );
return fs.rename(pSrcDir, pFinalDestDir);
}
}