Rev 7033 | Blame | Compare with Previous | Last modification | View Log | RSS feed
package com.erggroup.buildtool.daemon;import com.erggroup.buildtool.abt.BuildToolException;import com.erggroup.buildtool.abt.RemoteExecution;import com.erggroup.buildtool.abt.RemoteExecution.LogOutput;import com.erggroup.buildtool.daemon.BuildDaemon.NagiosInfo;import com.erggroup.buildtool.daemon.ResumeTimerTask;import com.erggroup.buildtool.ripple.Package;import com.erggroup.buildtool.ripple.Phase;import com.erggroup.buildtool.ripple.ReleaseManager;import com.erggroup.buildtool.ripple.DaemonInstruction;import com.erggroup.buildtool.ripple.ReportingData;import com.erggroup.buildtool.ripple.RunLevel;import com.erggroup.buildtool.smtp.CreateUrls;import com.erggroup.buildtool.smtp.Smtpsend;import com.erggroup.buildtool.utilities.MutableDate;import com.erggroup.buildtool.utilities.utilities;import com.erggroup.buildtool.ripple.RippleEngine;import com.erggroup.buildtool.ripple.RunLevel.BuildState;import com.erggroup.buildtool.daemon.BuildDaemon;import java.io.BufferedReader;import java.io.File;import java.io.FileNotFoundException;import java.io.FileWriter;import java.io.FilenameFilter;import java.io.IOException;import java.io.PrintStream;import java.io.StringReader;import java.sql.SQLException;import java.text.SimpleDateFormat;import java.util.Date;import java.util.LinkedHashMap;import java.util.Map;import java.util.Timer;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.apache.tools.ant.BuildException;import org.apache.tools.ant.DefaultLogger;import org.apache.tools.ant.Project;import org.apache.tools.ant.ProjectHelper;/**Build Thread sub component*/public abstract class BuildThreadextends Thread{/**baseline identifier (which release manager release this BuildThread is dealing with)* @attribute*/protected int mRtagId = 0;/**unique identifier of this BuildThread* @attribute*/protected int mRconId = 0;/*** Used as a monitor for:* Slave: Indication that a command has been issued by the master* Master: Not yet used*/protected Object mActiveBuildMonitor = new Object();/*** @aggregation composite*/protected RunLevel mRunLevel = new RunLevel(BuildState.DB_IDLE);/*** @aggregation composite*/protected ReleaseManager mReleaseManager;/****/protected RippleEngine mRippleEngine;/** Parsed ANT project**/Project mProject = null;/*** Indicate the start time of the thread* Not nanosecond accurate, just an indication of when it was started*/protected final long mStartTime = System.currentTimeMillis();/**unit test support* @attribute*/protected String mUnitTest = "";/*** @aggregation composite*/protected ResumeTimerTask mResumeTimerTask;/*** @aggregation composite*/private Timer mTimer;/**Synchroniser object* Use to Synchronize on by multiple threads* @attribute*/static final Object mSynchroniser = new Object();/**BuildThread group* @attribute*/public static final ThreadGroup mThreadGroup = new ThreadGroup("BuildThread");/**the advertised build file content when either* a) no package in the baseline has a build requirement* b) the next package in the baseline with a build requirement is generic* @attribute*/protected static final String mDummyBuildFileContent = "<dummy/>";/**Set true when last build cycle was benign.* @attribute*/protected boolean mLastBuildWasBenign;/** Last retrieved release sequence number* Used to determine if the Release Content has been changed by a user* The sequence number does not take into account changes by the build system*/protected int mReleaseSeqNum = -1;/** Holds the current buildfile**/protected File mBuildFile = new File("");/**Set true when last build cycle caught SQLException or Exception.* @attribute*/protected boolean mException;/**Set true when ant error reported on any target.* @attribute*/protected boolean mErrorReported;/**Logger* @attribute*/private static final Logger mLogger = LoggerFactory.getLogger(BuildThread.class);/** Data about the package that is being built* @attribute*/protected ReportingData mReporting = new ReportingData();/**Indicate the type of indefinite pause being processed* If true, then it is one that the daemon threads can recover from* If false, then wait for RM Database to indicate that its OK to proceed* @attribute*/protected boolean mRecoverable;/**Logger for the entire build process* @attribute*/DefaultLogger mBuildLogger ;/**Nagios Monitoring* @attribute*/Phase mPhase = new Phase();/**constructor* @param rtagId - Identifies the Release* @param rconId - Identifies the Controlling entry* @param releaseManager - A ReleaseManager entry to use* @param unitTest - Unit Test data*/BuildThread(int rtagId, int rconId, ReleaseManager releaseManager, String unitTest){super(mThreadGroup, "");mLogger.debug("BuildThread");mLastBuildWasBenign = true;mException = false;mErrorReported = false;mRecoverable = false;mRtagId = rtagId;mRconId = rconId;mReleaseManager = releaseManager;mBuildFile = new File(mRtagId + "build.xml");mUnitTest = ( unitTest == null ) ? "" : unitTest;// no need to be synchronized - BuildThreads are instantiated in a single threadif ( mResumeTimerTask == null ){mResumeTimerTask = new ResumeTimerTask();mTimer = new Timer();mResumeTimerTask.setTimer(mTimer);}}/**Flags that a new build cycle is about to start* Create a new logger to capture all the complete build log* even though its done in stages we want a complete log.*/void flagStartBuildCycle(){try{mBuildLogger = new DefaultLogger();PrintStream ps = new PrintStream(mRtagId + ".log");mBuildLogger.setOutputPrintStream(ps);mBuildLogger.setMessageOutputLevel(Project.MSG_INFO);}catch( FileNotFoundException e ){mLogger.error("BuildThread caught FileNotFoundException");}}/*** Sleeps when mException is set* Called at the top of the run() loop, just after exception processing*/protected void sleepCheck(){if (mException && ! BuildDaemon.mShutDown){Integer sleepTime = 5 * 60;mLogger.warn("sleepCheck sleep {} secs", sleepTime);BuildDaemon.daemonSleepSecs(sleepTime);mLogger.info("sleepCheck sleep returned");// Force planning cycle when recovering from an exceptionmReleaseSeqNum = -1;}mException = false;}/**initially changes the run level to IDLE* <br>Determines if the BuildThread is still configured* <br>Detects shutdown requests* <br>a) Determines if the BuildThread is running in scheduled down time* <br>b) Determines if the BuildThread is directed to pause* <br>Changes the run level to PAUSED if a) or b) are true* <br>throws ExitException when not configured or shutdown request** @param master True: Master Daemon* @exception ExitException* @exception SQLException* @exception Exception*/protected void allowedToProceed(boolean master) throws ExitException, SQLException, Exception{mLogger.debug("allowedToProceed");// Release Manager Database operations// Change the run level to IDLE// Discard any reserved version numbers// Reset the current package being builttry{mLogger.error("allowedToProceed changing run level to IDLE for rcon_id {}", mRconId);mRunLevel.persist(mReleaseManager, mRconId, BuildState.DB_IDLE);}catch(SQLException e){mLogger.warn("allowedToProceed caught SQLException");}// Sleep for the required time - if the last build cycle was benign// Slaves sleep for 3 seconds// Masters will sleep for up to 60 minutesif (mLastBuildWasBenign && !BuildDaemon.mShutDown){if (master){long startMasterWait = System.currentTimeMillis();while(!BuildDaemon.mShutDown){int masterCount = mReleaseManager.queryMasterCount( mRtagId );if ( masterCount != 1){mLogger.warn("allowedToProceed Invalid MasterCount: {}", masterCount);throw new ExitException();}int seqNum = mReleaseManager.queryReleaseSeqNum(mRtagId, mRconId, BuildDaemon.mHostname);if ( seqNum < 0 ){mLogger.warn("allowedToProceed Not part of buildset");throw new ExitException();}if ( seqNum != mReleaseSeqNum){// Release Content has been modifiedmReleaseSeqNum = seqNum;mLogger.error("allowedToProceed. Release Sequence changed");break;}// Check daemon instructions that need to be processed//DaemonInstruction di = new DaemonInstruction(mRtagId, -1, true);if ( mReleaseManager.getDaemonInst( di ) ){// At least one daemon instruction has expiredmLogger.error("allowedToProceed. Deamon instruction to be scheduled");break;}//// Backup plan// Force a planning session every 60 minutes//if ( System.currentTimeMillis() - startMasterWait > 60 * 60 * 1000){mLogger.error("allowedToProceed. Force periodic plan");break;}// Have no reason to suspect that we need to plan a release// Persist idle state to indicate the master is polling// Wait 60 seconds and test again//mLogger.warn("allowedToProceed sleep 60 secs no build requirement");mRunLevel.persist(mReleaseManager, mRconId, BuildState.DB_IDLE);BuildDaemon.daemonSleepSecs(60);mLogger.info("allowedToProceed sleep returned");}}else{// Slave - Wait 3 secondsmLogger.warn("allowedToProceed sleep 3 secs no build requirement");BuildDaemon.daemonSleepSecs(3);mLogger.info("allowedToProceed sleep returned");}}if (BuildDaemon.mShutDown){mLogger.warn("allowedToProceed ShutDown requested");throw new ExitException();}// Loop until this daemon can proceed - or no longer configured as a part of the build set// Things that will hold it up:// Indefinite pause// Scheduled down time// Waiting for recoverable error (dpkg_archive access)boolean proceed = false;try{while ( !proceed && !BuildDaemon.mShutDown ){mLogger.error("allowedToProceed calling mReleaseManager.connect");mReleaseManager.connect();// Ensure that the machine is a part of the current build setmLogger.error("allowedToProceed calling mReleaseManager.queryReleaseConfig");if ( !mReleaseManager.queryReleaseConfig(mRtagId, mRconId, BuildDaemon.mHostname, getMode(), mStartTime) ){mLogger.warn("allowedToProceed queryReleaseConfig failed");mReleaseManager.disconnect();throw new ExitException();}// If dpkg_archive has been flagged as unavailable, then check for its recovery//if ( mRecoverable ){if ( Package.recover() ){// Package archive(s) exist// clear the indefinite pause conditionmReleaseManager.resumeIndefinitePause();notifyIndefiniteRecovery();mRecoverable = false;}}if ( mUnitTest.compareTo("unit test not allowed to proceed") == 0 ){mRunLevel.persist(mReleaseManager, mRconId, BuildState.DB_PAUSED);throw new ExitException();}// Check for scheduled downtime or indefinite pauseMutableDate resumeTime = new MutableDate();mLogger.error("allowedToProceed calling mReleaseManager.queryRunLevelSchedule");if ( !mReleaseManager.queryRunLevelSchedule(resumeTime) ){mLogger.info("allowedToProceed scheduled downtime");mReleaseManager.disconnect();mLogger.error("allowedToProceed changing run level to PAUSED for rcon_id {}", mRconId);mRunLevel.persist(mReleaseManager, mRconId, BuildState.DB_PAUSED);//// Limit the pause to 10 minutes at a time// Allows early get out for the daemonslong tenMinutesFromNow = System.currentTimeMillis() + 10 * 60 * 1000;if (resumeTime.value.getTime() > tenMinutesFromNow){resumeTime.value.setTime(tenMinutesFromNow);mLogger.warn("allowedToProceed delay limited to 10 minutes");}synchronized(mSynchroniser){// contain the schedule and wait in the same synchronized block to prevent a deadlock// eg this thread calls schedule, timer thread calls notifyall, this thread calls wait (forever)try{if (mResumeTimerTask.isCancelled()){mResumeTimerTask = new ResumeTimerTask();mTimer = new Timer();mResumeTimerTask.setTimer(mTimer);}mLogger.warn("allowedToProceed schedule passed {}", resumeTime.value.getTime());mTimer.schedule(mResumeTimerTask, resumeTime.value);}catch( IllegalStateException e ){// this may be thrown by schedule if already scheduled// it signifies another BuildThread has already scheduled the ResumeTimerTaskmLogger.warn("allowedToProceed already scheduled");}try{mLogger.warn("allowedToProceed wait");mSynchroniser.wait();mLogger.warn("allowedToProceed wait returned");}catch( InterruptedException e ){mLogger.warn("allowedToProceed caught InterruptedException");Thread.currentThread().interrupt();}}}else{//// If commanded to pause, then wait around until the command has been removed.//mLogger.error("allowedToProceed calling mReleaseManager.queryDirectedRunLevel");if ( !mReleaseManager.queryDirectedRunLevel(mRconId) && !BuildDaemon.mShutDown ){mLogger.error("allowedToProceed changing run level to PAUSED for rcon_id {}", mRconId);mReleaseManager.disconnect();mRunLevel.persist(mReleaseManager, mRconId, BuildState.DB_PAUSED);// Sleep for periodicMsmLogger.warn("allowedToProceed sleep 1 mins directed downtime");BuildDaemon.daemonSleepSecs(60);mLogger.info("allowedToProceed sleep returned");}else{mReleaseManager.disconnect();proceed = true;}}}}finally{// this block is executed regardless of what happens in the try block// even if an exception is thrown// ensure disconnectmLogger.error("allowedToProceed calling mReleaseManager.disconnect");mReleaseManager.disconnect();}if (BuildDaemon.mShutDown){mLogger.warn("allowedToProceed ShutDown requested");throw new ExitException();}}/**periodically* <br>a) performs disk housekeeping* <br>b) determines if a minimum threshold of disk space is available* <br>c) determines if a file can be touched* <br>d) Master: Checks that a ssh connection can be established to the package server* <br>changes the run level to CANNOT_CONTINUE if insufficient disk space* <br>otherwise changes the run level to ACTIVE and returns* <p>This function does not return until all check have passed** @return true - The task was delayed until all checks passed. The caller may* wish to reevaluate the ability to continue the current task.**/protected boolean checkEnvironment() throws Exception{mLogger.debug("checkEnvironment");boolean exit = false;boolean waitPerformed = false;String reason;while( !exit ){mPhase.setPhase("checkEnvironment");exit = false;reason = "No Reason";// Ugly Unit Testing Hookif ( mUnitTest.startsWith("unit test") ){if ( mUnitTest.startsWith("unit test check environment") ){if (mUnitTest.endsWith("Pass2")){exit = true;}else{mUnitTest += ".Pass2";reason = "Unit Test Check";}}else{exit= true;}}else{housekeep();// Perform checks in order// Failure of the first one terminates the check//if ( ! hasSufficientDiskSpace() ){mLogger.warn("checkEnvironment below disk free threshold");mPhase.setPhase("checkEnvironment:Wait for diskspace");reason = "Insufficient disk space";}else if (!touch() ){mLogger.warn("checkEnvironment read only file system detected");mPhase.setPhase("checkEnvironment:Wait for writable files system");reason = "Readonly file system";}else if ( !checkRemoteExecution() ){mLogger.warn("checkEnvironment Remote connection not established");mPhase.setPhase("checkEnvironment:Wait for remote execution");reason = "No remote connection";}else{exit = true;mPhase.setPhase("checkEnvironment");reason = "No Reason";}}if ( !exit ){waitPerformed = true;mLogger.error("checkEnvironment changing run level to CANNOT_CONTINUE for rcon_id {}", mRconId);mRunLevel.persistFault(mReleaseManager, mRconId, reason);if ( !mUnitTest.startsWith("unit test check environment") ){mLogger.warn("checkEnvironment sleep 5 mins");exit = BuildDaemon.daemonSleepSecs(5 * 60);mLogger.info("checkEnvironment sleep returned");}}}mLogger.error("checkEnvironment changing run level to ACTIVE for rcon_id {}", mRconId);mRunLevel.persist(mReleaseManager, mRconId, BuildState.DB_ACTIVE);return waitPerformed;}/** Perform check of the remote execution target machine* Used so that we know at the start of a build that the target machine can be contacted* Check is only done on the master** @return true - the check has passed**/private boolean checkRemoteExecution(){if ( getMode() != 'M' || mUnitTest.startsWith("unit test") ){// Don't check if// This thread is not a master// This is a unit testreturn true;}RemoteExecution mRemoteExecution = new RemoteExecution(new LogOutput(){@Overridepublic void data(String message) {mLogger.info(message);}@Overridepublic void fatal(String message) {mLogger.error(message);}@Overridepublic void info(String message) {mLogger.info(message);}});return mRemoteExecution.testConnection();}/**performs disk housekeeping which involves deleting build directories > 5 days old* refer to the sequence diagram check environment*/private void housekeep(){mLogger.debug("housekeep");FilenameFilter filter = new FilenameFilter(){// Filter function to process the files processed within this routinepublic boolean accept(File file, String name){mLogger.debug("accept {}", name);boolean retVal = false;if ( file.isDirectory() && !name.startsWith( "." ) ){retVal = true;}mLogger.info("accept returned {}", retVal);return retVal;}};try{// DEVI 46729, 46730, solaris 10 core dumps implicate deleteDirectory// let each BuildThread look after its own housekeepingString cwdPath = utilities.catDir(BuildDaemon.mGbeLog ,BuildDaemon.mHostname ,String.valueOf( mRtagId ));File cwd = utilities.freshFile(cwdPath);File[] children = cwd.listFiles( filter );if ( children != null ){for ( int child=0; child < children.length; child++ ){// child is named uniquely to encapsulate a build// 5 days = 432,000,000 millisecondsif ( ( System.currentTimeMillis() - children[ child ].lastModified() ) > 432000000 ){// the directory is over 5 days oldmLogger.warn("housekeep deleting directory {}", children[ child ].getName());deleteDirectory( children[ child ] );}}}}catch( SecurityException e ){// this can be thrown by lastModifiedmLogger.warn("housekeep caught SecurityException");}}/**returns true if a file exists and can be deleted,* created and exists in the file system* this is to guard against read-only file systems*/private boolean touch(){mLogger.debug("touch");boolean retVal = true;try{File touch = new File( String.valueOf( mRtagId ) + "touch" );if ( touch.exists() ){// delete itretVal = touch.delete();}if ( retVal ){// file does not existretVal = touch.createNewFile();}}catch( SecurityException e ){// this can be thrown by exists, delete, createNewFileretVal = false;mLogger.warn("touch caught SecurityException");}catch( IOException e ){// this can be thrown by createNewFileretVal = false;mLogger.warn("touch caught IOException");}mLogger.info("touch returned {}", retVal);return retVal;}/**returns true if free disk space > 5G* this may become configurable if the need arises* refer to the sequence diagram check environment*/private boolean hasSufficientDiskSpace(){mLogger.debug("hasSufficientDiskSpace");boolean retVal = true;long freeSpace = 0;try{File cwd = new File( "." );// 5G = 5368709120 bytes// 1G = 1073741824 bytes - useful for testingfreeSpace = cwd.getUsableSpace();if ( freeSpace < 5368709120L ){mLogger.warn("hasSufficientDiskSpace failed: {} freespace {}", cwd.getAbsolutePath() , freeSpace);retVal = false;}}catch( SecurityException e ){// this can be thrown by getFreeSpacemLogger.warn("hasSufficientDiskSpace caught SecurityException");}mLogger.info("hasSufficientDiskSpace returned {} {}", retVal, freeSpace);return retVal;}/**abstract method*/@Overridepublic abstract void run();/** deletes directory and all its files* Will set files and directories to be writable in case the user has messed with* file permissions*/FilenameFilter deleteDirectoryFilter = new FilenameFilter(){public boolean accept(File file, String name){mLogger.debug("accept {}", name);boolean retVal = false;if ( name.compareTo(".") != 0 && ( name.compareTo("..") != 0 ) ){retVal = true;}mLogger.info("accept returned {}", retVal);return retVal;}};@SuppressWarnings("squid:S899")protected void deleteDirectory(File directory){mLogger.debug("deleteDirectory {}", directory.getName());try{if ( directory.exists() ){//directory.setWritable(true);File[] children = directory.listFiles( deleteDirectoryFilter );if ( children != null ){for ( int child=0; child < children.length; child++ ){if ( children[ child ].isDirectory() ){deleteDirectory( children[ child ] );}else{children[ child ].setWritable(true);children[ child ].delete();}}}directory.delete();}}catch( SecurityException e ){// this can be thrown by exists and deletemLogger.warn("deleteDirectory caught SecurityException");}}/*** abstract method. Re-implemented by each class* Returns the Mode in which the thread is operating.** @return M if Master, S if Slave*/protected abstract char getMode();/** Save the buildfile to disk* @param buildFileContent The generated build file that will control this run* @throws Exception**/protected void saveBuildFile(String buildFileContent) throws BuildSystemException{mReporting.buildFailureLogFile = null;mErrorReported = false;mBuildFile.delete();FileWriter buildFileWriter = null;BufferedReader buildFileBufferedReader = null;boolean errorSeen = false;if ( buildFileContent != null ) {try {StringReader buildFileContentStringReader = new StringReader(buildFileContent);buildFileBufferedReader = new BufferedReader(buildFileContentStringReader);// Sanitize the buildFileContent// it may contain line.separators of "\n", "\r", or "\r\n" variety,// depending on the location of the ripple engineStringBuilder sanitisedBFC = new StringBuilder ();String lf = System.getProperty("line.separator") ;String line;while( ( line = buildFileBufferedReader.readLine() ) != null){sanitisedBFC.append (line).append(lf);}buildFileBufferedReader.close();buildFileBufferedReader = null;buildFileWriter = new FileWriter(mBuildFile);buildFileWriter.write(sanitisedBFC.toString());buildFileWriter.close();buildFileWriter = null;}catch( IOException e ){mLogger.error("SaveBuildFile caught IOException");errorSeen = true;}finally{if ( buildFileWriter!= null ) {try {buildFileWriter.close();} catch (IOException e) {errorSeen = true;}}if ( buildFileBufferedReader != null) {try {buildFileBufferedReader.close();} catch (IOException e) {errorSeen = true;}}}}if (errorSeen) {mBuildFile.delete();throw new BuildSystemException("SaveBuildFile caught FileNotFoundException");}}/** Delete the build file**/protected void deleteBuildFile(){if ( mBuildFile.exists() ){mBuildFile.delete(); //NOSONAR}}/** Parse the ANT project* Done once per build - useful information is extracted for use within the build* @param updateReporting*/protected void parseBuildFile(boolean updateReporting){mLogger.debug("parseBuildFile");//try{mProject = null;if (updateReporting) {mReporting.resetData();}if ( mBuildFile.exists() ){mProject = new Project();mProject.setProperty("ant.file", mBuildFile.getAbsolutePath());// Add listener for logging the complete build process// If the daemon has been restarted, then the listener will not have been// set up - so don't add it. Perhaps we need a way to open an existing// log file to append.if ( mBuildLogger != null ) {mProject.addBuildListener(mBuildLogger);}// parse can throw BuildException, this is serious// Generate a general exception to force an indefinite pause// Delete build file to prevent attempts to use ittry {mProject.init();ProjectHelper pH = ProjectHelper.getProjectHelper();mProject.addReference("ant.projectHelper", pH);pH.parse(mProject, mBuildFile);}catch (BuildException be){mLogger.error("Parse Error: {}", be );mBuildFile.delete(); //NOSONARthrow new BuildToolException("Error parsing build file:"+ be.getCause());}if (updateReporting) {// set up project properties for reporting purposes// this first group are hard coded in the build filemReporting.rtagId = mRtagId;mReporting.buildRef = mProject.getProperty("abt_daemon");mReporting.packageName = mProject.getProperty("abt_package_name");mReporting.packageVersion = mProject.getProperty("abt_package_version");mReporting.packageExtension = mProject.getProperty("abt_package_extension");mReporting.packageLocation = mProject.getProperty("basedir") + mProject.getProperty("abt_package_location");mReporting.packageDepends = mProject.getProperty("abt_package_depends");mReporting.packageOwners = mProject.getProperty("abt_package_ownerlist");mReporting.packageBuildInfo = mProject.getProperty("abt_package_build_info");mReporting.isRipple = ReportingData.toBool(mProject.getProperty("abt_is_ripple"));mReporting.buildReason = ReportingData.toBuildReason(mProject.getProperty("abt_build_reason"));mReporting.packageVersionId = ReportingData.toInt(mProject.getProperty("abt_package_version_id"), -1);mReporting.doesNotRequireSourceControlInteraction = ReportingData.toBool(mProject.getProperty("abt_does_not_require_source_control_interaction"));mReporting.isaTestBuild = ReportingData.toBool (mProject.getProperty("abt_test_build_instruction"));}//// Log the abt_daemon value so that we can trace the build log// Build logs from all machines are stored with this number// Only do it at the start of the buildmLogger.error("BuildRef: {}", mReporting.buildRef );}}}/*** builds a buildFile from the buildFileContent* triggers ant to operate on the buildFile** @param target The command to be run. This is one of the commands known to the ABT class* @param updateReporting True to update the mReportingData from the buildfiles properties* @param forceOpr True - force operation on errors* @throws Exception* @throws BuildException* @return Success - False on error*/protected boolean deliverChange(String target, boolean updateReporting, boolean forceOpr) throws Exception{mLogger.debug("deliverChange");// Bypass the operation if an error is being propagated// Always perform a AbtSetUp and AbtTearDown//if (!forceOpr) {if ( mErrorReported ) {// SaveBuildFile, AbtTestPath, AbtSetUp or the build failed// the default target will inevitably fail and will generate further email if allowed to proceed// do not mask the root causemLogger.debug("deliverChange - Error already detected");return false;}}// Parse the build fileparseBuildFile(updateReporting);if (mProject != null){if ( target == null ) {target = mProject.getDefaultTarget();}mLogger.warn("deliverChange ant launched: {} Target: {}", mBuildFile.getAbsolutePath() , target);try {mProject.executeTarget(target);mLogger.warn("deliverChange ant returned");}//// Do not catch ALL exceptions. The MasterThread::run and SlaveThread::run// relies on exceptions propagating upwards for the indefinite pause feature//catch( BuildException e ){if ( mReporting.buildFailureLogFile == null ){mReporting.buildFailureLogFile = e.getMessage();}mLogger.debug("deliverChange caught BuildException, big deal, the build failed {}", mReporting.buildFailureLogFile);mErrorReported = true;}if (updateReporting ){// this group is set at run time (by the AbtPublish target only)// they will be null for every other target,// and null if an error occurs in the AbtPublish targetmReporting.isFullyPublished = ReportingData.toBool(mProject.getProperty("abt_fully_published"));mReporting.newVcsTag = mProject.getProperty("abt_new_vcstag");}}return !mErrorReported;}/*** eMail an indefinite pause notification** @param cause Reason for the pause*/protected void notifyIndefinitePause(String cause){mLogger.debug("indefinitePause");String body ="Hostname: " + BuildDaemon.mHostname + "<p>" +"Release: " + mRippleEngine.mBaselineName + "<p>" +"Cause: " + cause + "<p>" +"RmRef: " + CreateUrls.generateReleaseUrl(mRippleEngine.getRtagId()) +"<p>";try{Smtpsend.send(mRippleEngine.getMailServer(), // mailServermRippleEngine.getMailSender(), // sourcemRippleEngine.getMailGlobalTarget(), // target (list)null, // ccnull, // bcc"BUILD DAEMON Indefinite Pause", // subjectbody, // bodynull // attachment);}catch( Exception e ){}}/*** eMail an indefinite pause recovery notification*/private void notifyIndefiniteRecovery(){mLogger.debug("indefiniteRecovery");String body ="Hostname: " + BuildDaemon.mHostname + "<p>" +"Release: " + mRippleEngine.mBaselineName + "<p>" +"Cause: " + "Recovery of previous condition" + "<p>" +"RmRef: " + CreateUrls.generateReleaseUrl(mRippleEngine.getRtagId()) +"<p>";try{Smtpsend.send(mRippleEngine.getMailServer(), // mailServermRippleEngine.getMailSender(), // sourcemRippleEngine.getMailGlobalTarget(), // target (list)null, // ccnull, // bcc"BUILD DAEMON Indefinite Pause Recovery", // subjectbody, // bodynull // attachment);}catch( Exception e ){}}/*** Nagios interface* Returns true if the thread looks OK* @param nagInfo*/boolean checkThread(NagiosInfo nagInfo){boolean retVal = true;if ( mRunLevel.mBuildState == BuildState.DB_CANNOT_CONTINUE ) {retVal = false;nagInfo.extendedReason.add("[" + mRtagId + "] " + mRunLevel.toString());} else {retVal = checkThreadExtended(nagInfo);}if (this.getMode() == 'M') {nagInfo.masterCount++;} else {nagInfo.slaveCount++;}mLogger.warn("checkThread[{}] returned {}",mRtagId, retVal);return retVal;}/*** Nagios interface extension* This method should be overridden by classes that extend this class* If not overridden then the test indicates OK** Returns true if the thread looks OK*/boolean checkThreadExtended(NagiosInfo nagInfo){boolean retVal = mPhase.isHappy();if ( ! retVal) {nagInfo.extendedReason.add("[" + mRtagId + "] " + mPhase.happyText());}mLogger.info("checkThreadExtended: {}:{}",retVal, mPhase.toStringSecs() );return retVal;}/*** Nagios interface extension* This method should be overridden by classes that extend this class* If not overridden then the test indicates OK** Returns true if the thread looks OK* @param wr Output buffer* @param nagInfo* @param estatus* @throws IOException*/public void extendedStatus(NagiosInfo nagInfo, Map<String, Object> estatus){LinkedHashMap<String, Object> data = new LinkedHashMap<String, Object>();estatus.put(String.valueOf(mRtagId), data);data.put("rtagId", Integer.valueOf(mRtagId));data.put("rconId", Integer.valueOf(mRconId));data.put("Thread Start Text", new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").format(new Date(mStartTime)));data.put("Thread Start", Long.valueOf(mStartTime));data.put("Thread Alive:", Boolean.valueOf(this.isAlive() ));data.put("Thread Check:", Boolean.valueOf(checkThread(nagInfo) ));data.put("Mode" , String.valueOf(this.getMode() ));data.put("mRunLevel" , mRunLevel.toString() );data.put("mLastBuildWasBenign" , Boolean.valueOf(mLastBuildWasBenign));data.put("mException" , Boolean.valueOf(mException));data.put("mErrorReported" , Boolean.valueOf(mErrorReported));data.put("mRecoverable", Boolean.valueOf(mRecoverable));data.put("mPhase" , mPhase.sText);data.put("mPhaseDelta" , mPhase.getDeltaSecs());if ( mReleaseManager != null)data.put("Rm Mutex" , mReleaseManager.mMutexState );elsedata.put("Rm Mutex" , null );}}