Rev 862 | Blame | Last modification | View Log | RSS feed
package com.erggroup.buildtool.daemon;import com.erggroup.buildtool.daemon.ResumeTimerTask;import com.erggroup.buildtool.ripple.ReleaseManager;import java.io.BufferedReader;import java.io.File;import java.io.FileNotFoundException;import java.io.FileOutputStream;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.util.Date;import java.util.Timer;import org.apache.log4j.Logger;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;/*** @aggregation composite*/protected RunLevel mRunLevel;/*** @aggregation composite*/protected ReleaseManager mReleaseManager;/**configured GBEBUILDFILTER for this BuildThread* @attribute*/protected String mGbebuildfilter = "";/*** @aggregation composite*/protected static ResumeTimerTask mResumeTimerTask;/*** @aggregation composite*/private static 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 = new String("<dummy/>");/**Set true when last build cycle was benign.* @attribute*/protected boolean mSleep;/**Set true when ant error reported on any target.* @attribute*/private boolean mErrorReported;/**Set true when ant error reported on AbtSetUp target.* @attribute*/private boolean mSetUpErrorReported;/**Logger* @attribute*/private static final Logger mLogger = Logger.getLogger(BuildThread.class);/**constructor*/BuildThread(){super(mThreadGroup, "");mLogger.debug("BuildThread");mSleep = false;mErrorReported = false;mSetUpErrorReported = false;mReleaseManager = new ReleaseManager();// no need to be synchronized - BuildThreads are instantiated in a single threadif ( mResumeTimerTask == null ){mResumeTimerTask = new ResumeTimerTask();mTimer = new Timer();mResumeTimerTask.setTimer(mTimer);}}/**initially changes the run level to IDLE* determines if the BuildThread is still configured* a) determines if the BuildThread is running in scheduled downtime* b) determines if the BuildThread is directed to pause* changes the run level to PAUSED if a) or b) are true* throws ExitException when not configured* otherwise changes the run level to WAITING and returns when allowed to proceed* implements the sequence diagrams allowed to proceed, not allowed to proceed, exit*/protected void allowedToProceed() throws ExitException, SQLException, Exception{mLogger.debug("allowedToProceed");if (mSleep){try{mRunLevel = RunLevel.IDLE;mLogger.warn("allowedToProceed changing run level to IDLE for rcon_id " + mRconId);mRunLevel.persist(mReleaseManager, mRconId);mReleaseManager.clearCurrentPackageBeingBuilt(mRconId);mLogger.warn("allowedToProceed sleep 5 mins no build requirement");Thread.sleep(300000);mLogger.info("allowedToProceed sleep returned");}catch(SQLException e){mLogger.warn("allowedToProceed caught SQLException");}catch(InterruptedException e){mLogger.warn("allowedToProceed sleep caught InterruptedException");}}// IMPORTANT - this is done AFTER a Thread.sleep by design// In the case of an SQLException (signifying a database access issue)// avoid accessing the database immediatelymRunLevel = RunLevel.IDLE;mLogger.warn("allowedToProceed changing run level to IDLE for rcon_id " + mRconId);mRunLevel.persist(mReleaseManager, mRconId);boolean proceed = false;while ( !proceed ){mReleaseManager.connect();if ( !mReleaseManager.queryReleaseConfig(mRtagId, mRconId, BuildDaemon.mHostname, getMode(), mGbebuildfilter) ){mReleaseManager.disconnect();mLogger.warn("allowedToProceed queryReleaseConfig failed");throw new ExitException();}Date resumeTime = new Date( 0 );if ( !mReleaseManager.queryRunLevelSchedule(resumeTime) ){mLogger.info("allowedToProceed scheduled downtime");mReleaseManager.disconnect();mRunLevel = RunLevel.PAUSED;mLogger.warn("allowedToProceed changing run level to PAUSED for rcon_id " + mRconId);mRunLevel.persist(mReleaseManager, mRconId);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.getTime());mTimer.schedule(mResumeTimerTask, resumeTime);}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");if ( mGbebuildfilter.compareTo("unit test not allowed to proceed") == 0 ){throw new ExitException();}}catch( InterruptedException e ){mLogger.warn("allowedToProceed caught InterruptedException");}}}else{if ( !mReleaseManager.queryDirectedRunLevel(mRconId) ){mLogger.info("allowedToProceed downtime");mReleaseManager.disconnect();mRunLevel = RunLevel.PAUSED;mLogger.warn("allowedToProceed changing run level to PAUSED for rcon_id " + mRconId);mRunLevel.persist(mReleaseManager, mRconId);try{// to do, sleep for periodicMsmLogger.warn("allowedToProceed sleep 5 mins directed downtime");Thread.sleep(300000);mLogger.info("allowedToProceed sleep returned");}catch (InterruptedException e){mLogger.warn("allowedToProceed caught InterruptedException");}}else{mReleaseManager.disconnect();proceed = true;}}}mRunLevel = RunLevel.WAITING;mLogger.warn("allowedToProceed changing run level to WAITING for rcon_id " + mRconId);mRunLevel.persist(mReleaseManager, mRconId);}/**periodically* a) performs disk housekeeping* b) determines if a minimum threshold of disk space is available* changes the run level to CANNOT_CONTINUE if insufficient disk space* otherwise changes the run level to ACTIVE and returns* implements the sequence diagram check environment*/protected void checkEnvironment() throws Exception{mLogger.debug("checkEnvironment");boolean exit = false;while( !exit ){housekeep();// attempt to exitexit = true;if ( !hasSufficientDiskSpace() ){mLogger.warn("checkEnvironment below disk free threshold");exit = false;mRunLevel = RunLevel.CANNOT_CONTINUE;mLogger.warn("checkEnvironment changing run level to CANNOT_CONTINUE for rcon_id " + mRconId);mRunLevel.persist(mReleaseManager, mRconId);try{// to do, sleep for periodicMsif ( mGbebuildfilter.compareTo("unit test check environment") != 0 ){mLogger.warn("checkEnvironment sleep 5 mins below disk free threshold");Thread.sleep(300000);mLogger.info("checkEnvironment sleep returned");}}catch (InterruptedException e){mLogger.warn("checkEnvironment caught InterruptedException");}}}mRunLevel = RunLevel.ACTIVE;mLogger.warn("checkEnvironment changing run level to ACTIVE for rcon_id " + mRconId);mRunLevel.persist(mReleaseManager, mRconId);}/**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(){public 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 housekeepingFile ocwd = new File( BuildDaemon.mGbeLog );File hcwd = new File( ocwd, BuildDaemon.mHostname );File cwd = new File( hcwd, String.valueOf( mRtagId ) );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());if ( mGbebuildfilter.compareTo("unit test check environment") != 0 ){deleteDirectory( children[ child ] );}}}}}catch( SecurityException e ){// this can be thrown by lastModifiedmLogger.warn("housekeep caught SecurityException");}}/**returns true if free disk space > 10G* 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 bytesif ( mGbebuildfilter.compareTo("unit test check environment") == 0 ){if ( ReleaseManager.mPersistedRunLevelCollection.size() == 0 ){retVal = false;}else{retVal = true;}}else{freeSpace = cwd.getUsableSpace();if ( freeSpace < 5368709120L ){mLogger.warn("hasSufficientDiskSpace on " + cwd.getAbsolutePath() + " freeSpace " + 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*/public abstract void run();/**deletes directory and all its files*/protected void deleteDirectory(File directory){mLogger.debug("deleteDirectory " + directory.getName());try{if ( directory.exists() ){FilenameFilter filter = 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;}};File[] children = directory.listFiles( filter );if ( children != null ){for ( int child=0; child < children.length; child++ ){if ( children[ child ].isDirectory() ){deleteDirectory( children[ child ] );}else{children[ child ].delete();}}}directory.delete();}}catch( SecurityException e ){// this can be thrown by exists and deletemLogger.warn("deleteDirectory caught SecurityException");}}/**abstract method*/protected abstract char getMode();/**injects GBE_BUILDFILTER into the passed buildFileContent* builds a buildFile from the buildFileContent* triggers ant to operate on the buildFile*/protected void deliverChange(String buildFileContent, String target, boolean master){mLogger.debug("deliverChange");if ( ( target == null || target.compareTo( "AbtPublish" ) == 0 ) && ( mErrorReported ) ){// previous error occurred processing the build file contentreturn;}File buildFile = new File(mRtagId + "build.xml");boolean logError = true;try{// clear the file contentsif ( buildFileContent != null && target != null && target.compareTo("AbtSetUp") == 0 ){FileOutputStream buildFileOutputStream = new FileOutputStream(buildFile, false);buildFileOutputStream.close();StringReader buildFileContentStringReader = new StringReader(buildFileContent);BufferedReader buildFileBufferedReader = new BufferedReader(buildFileContentStringReader);// sanitise the buildFileContent// it may contain line.separators of "\n", "\r", or "\r\n" variety, depending on the location of the ripple engineString sanitisedBFC = new String();String lf = new String( System.getProperty("line.separator") );int lineCount = 0;String line = new String();while( ( line = buildFileBufferedReader.readLine() ) != null){sanitisedBFC += line + lf;lineCount++;if ( lineCount == 2 ){// have read the first two linesString inject = "<property name=\"abt_MASTER\" value=\"";if ( master ){inject += "yes\"/>" + lf;}else{inject += "no\"/>" + lf;}// insert a GBE_BUILDFILTER property if necessaryif ( mGbebuildfilter.length() > 0 ){inject += "<property name=\"abt_GBE_BUILDFILTER\" value=\"" + mGbebuildfilter + "\"/>" + lf;}mLogger.info("deliverChange injecting " + inject);sanitisedBFC += inject;}}buildFileBufferedReader.close();FileWriter buildFileWriter = new FileWriter(buildFile);buildFileWriter.write(sanitisedBFC);buildFileWriter.close();}Project p = new Project();p.setProperty("ant.file", buildFile.getAbsolutePath());DefaultLogger dl = new DefaultLogger();PrintStream ps = new PrintStream(mRtagId + ".log");dl.setOutputPrintStream(ps);dl.setMessageOutputLevel(Project.MSG_INFO);p.addBuildListener(dl);p.init();ProjectHelper pH = ProjectHelper.getProjectHelper();p.addReference("ant.projectHelper", pH);// parse can throw BuildException, this is seriouspH.parse(p, buildFile);mLogger.warn("deliverChange ant launched on " + buildFile.getAbsolutePath());if ( target == null ){target = p.getDefaultTarget();}mLogger.warn("deliverChange ant launched against target " + target);// executeTarget can throw BuildException, this is not seriouslogError = false;p.executeTarget(target);mLogger.warn("deliverChange ant returned");}catch( BuildException e ){if ( logError ){mLogger.error("deliverChange caught BuildException, the build failed " + e.getMessage());}else{mLogger.debug("deliverChange caught BuildException, big deal, the build failed " + e.getMessage());}if ( target.compareTo( "AbtSetUp ") == 0 ){mSetUpErrorReported = true;}mErrorReported = true;}catch( FileNotFoundException e ){mLogger.error("deliverChange caught FileNotFoundException");}catch( IOException e ){mLogger.error("deliverChange caught IOException");}}/**sets up a ClearCase static view*/protected void setViewUp(String content, boolean master){mLogger.debug("setViewUp");mErrorReported = false;mSetUpErrorReported = false;// run ant on the AbtSetUp targetdeliverChange(content, "AbtSetUp", master);}/**tears down a ClearCase static view*/protected void tearViewDown(){mLogger.debug("tearViewDown");if ( !mSetUpErrorReported ){// no error setting the view up, so...// run ant on the AbtTearDown targetdeliverChange(null, "AbtTearDown", false);}}/**Checks the archive for the <packageName>/<packageVersion>/built.<machtype> existence*/protected boolean published(String archive, String packageName,String packageVersion, String machtype,String generic){mLogger.debug("published");boolean retVal = false;String fs = System.getProperty( "file.separator" );String destination = archive + fs + packageName + fs + packageVersion;mLogger.debug("published " + destination);String filename = "built.";if ( generic.compareTo("generic") == 0 ){filename += "generic";}else{filename += machtype;}mLogger.debug("published " + filename);File flag = new File( destination, filename );if ( flag.exists() ){retVal = true;}mLogger.debug("published returned " + retVal);return retVal;}}