Subversion Repositories DevTools

Rev

Rev 7489 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
6914 dpurdie 1
package com.erggroup.buildtool.daemon;
2
 
3
import com.erggroup.buildtool.abt.BuildToolException;
4
import com.erggroup.buildtool.abt.RemoteExecution;
5
import com.erggroup.buildtool.abt.RemoteExecution.LogOutput;
6
import com.erggroup.buildtool.daemon.BuildDaemon.NagiosInfo;
7
import com.erggroup.buildtool.daemon.ResumeTimerTask;
8
import com.erggroup.buildtool.ripple.Package;
9
import com.erggroup.buildtool.ripple.Phase;
10
import com.erggroup.buildtool.ripple.ReleaseManager;
7023 dpurdie 11
import com.erggroup.buildtool.ripple.DaemonInstruction;
6914 dpurdie 12
import com.erggroup.buildtool.ripple.ReportingData;
13
import com.erggroup.buildtool.ripple.RunLevel;
14
import com.erggroup.buildtool.smtp.CreateUrls;
15
import com.erggroup.buildtool.smtp.Smtpsend;
16
import com.erggroup.buildtool.utilities.MutableDate;
17
import com.erggroup.buildtool.utilities.utilities;
18
import com.erggroup.buildtool.ripple.RippleEngine;
19
import com.erggroup.buildtool.ripple.RunLevel.BuildState;
20
import com.erggroup.buildtool.daemon.BuildDaemon;
21
 
22
import java.io.BufferedReader;
23
import java.io.File;
24
import java.io.FileNotFoundException;
25
import java.io.FileWriter;
26
import java.io.FilenameFilter;
27
import java.io.IOException;
28
import java.io.PrintStream;
29
import java.io.StringReader;
30
import java.sql.SQLException;
31
import java.text.SimpleDateFormat;
32
import java.util.Date;
33
import java.util.LinkedHashMap;
34
import java.util.Map;
35
import java.util.Timer;
36
 
7033 dpurdie 37
import org.slf4j.Logger;
38
import org.slf4j.LoggerFactory;
6914 dpurdie 39
import org.apache.tools.ant.BuildException;
40
import org.apache.tools.ant.DefaultLogger;
41
import org.apache.tools.ant.Project;
42
import org.apache.tools.ant.ProjectHelper;
43
 
44
/**Build Thread sub component
45
 */
46
public abstract class BuildThread
47
  extends Thread
48
{
49
  /**baseline identifier (which release manager release this BuildThread is dealing with)
50
   * @attribute
51
   */
52
  protected int mRtagId = 0;
53
 
54
  /**unique identifier of this BuildThread
55
   * @attribute
56
   */
57
  protected int mRconId = 0;
58
 
59
  /**
60
   * Used as a monitor for:
61
   * Slave: Indication that a command has been issued by the master
62
   * Master: Not yet used
63
   */
64
  protected Object mActiveBuildMonitor = new Object();
65
 
66
 
67
  /**
68
   * @aggregation composite
69
   */
70
  protected RunLevel mRunLevel = new RunLevel(BuildState.DB_IDLE);
71
 
72
  /**
73
   * @aggregation composite
74
   */
75
  protected ReleaseManager mReleaseManager;
76
 
77
  /**
78
   * 
79
   */
80
  protected RippleEngine mRippleEngine;
81
 
82
  /** Parsed ANT project
83
   * 
84
   */
85
  Project mProject = null;
86
 
87
  /**
88
   * Indicate the start time of the thread
89
   * Not nanosecond accurate, just an indication of when it was started
90
   */
91
  protected final long mStartTime = System.currentTimeMillis();
92
 
93
  /**unit test support
94
   * @attribute
95
   */
96
  protected String mUnitTest = "";
97
 
98
  /**
99
   * @aggregation composite
100
   */
101
  protected ResumeTimerTask mResumeTimerTask;
102
 
103
  /**
104
   * @aggregation composite
105
   */
106
  private Timer mTimer;
107
 
108
  /**Synchroniser object
109
   * Use to Synchronize on by multiple threads
110
   * @attribute
111
   */
112
  static final Object mSynchroniser = new Object();
113
 
114
  /**BuildThread group
115
   * @attribute
116
   */
117
  public static final ThreadGroup mThreadGroup = new ThreadGroup("BuildThread");
118
 
119
  /**the advertised build file content when either
120
   * a) no package in the baseline has a build requirement
121
   * b) the next package in the baseline with a build requirement is generic
122
   * @attribute
123
   */
124
  protected static final String mDummyBuildFileContent = "<dummy/>";
125
 
126
  /**Set true when last build cycle was benign.
127
   * @attribute
128
   */
129
  protected boolean mLastBuildWasBenign;
130
 
131
  /** Last retrieved release sequence number
132
   *  Used to determine if the Release Content has been changed by a user
133
   *  The sequence number does not take into account changes by the build system
134
   */
135
  protected int mReleaseSeqNum = -1;
136
 
137
  /** Holds the current buildfile 
138
   * 
139
   */
140
  protected File mBuildFile = new File("");
141
 
7489 dpurdie 142
  /** Holds the current buildInfo file
143
   * 
144
   */
145
  protected File mBuildInfoFile = new File("");
146
 
147
  /** Holds the current build metrics file
148
   * 
149
   */
150
  protected File mAbtMetricsFile = new File("");
151
 
6914 dpurdie 152
  /**Set true when last build cycle caught SQLException or Exception.
153
   * @attribute
154
   */
155
  protected boolean mException;
156
 
157
  /**Set true when ant error reported on any target.
158
   * @attribute
159
   */
160
  protected boolean mErrorReported;
161
 
162
  /**Logger
163
   * @attribute
164
   */
7033 dpurdie 165
  private static final Logger mLogger = LoggerFactory.getLogger(BuildThread.class);
6914 dpurdie 166
 
167
  /** Data about the package that is being built
168
   * @attribute
169
   */
170
  protected ReportingData mReporting = new ReportingData();
171
 
172
  /**Indicate the type of indefinite pause being processed
173
   * If true, then it is one that the daemon threads can recover from
174
   * If false, then wait for RM Database to indicate that its OK to proceed
175
   * @attribute
176
   */
177
  protected boolean mRecoverable;
178
 
179
  /**Logger for the entire build process
180
  * @attribute
181
  */
182
  DefaultLogger mBuildLogger ;
183
 
184
  /**Nagios Monitoring
185
   * @attribute
186
   */
187
  Phase mPhase = new Phase();
188
 
189
 
190
  /**constructor
7033 dpurdie 191
   * @param rtagId          - Identifies the Release
6914 dpurdie 192
   * @param rconId          - Identifies the Controlling entry
193
   * @param releaseManager  - A ReleaseManager entry to use
194
   * @param unitTest        - Unit Test data 
195
   */
196
  BuildThread(int rtagId, int rconId, ReleaseManager releaseManager, String unitTest)
197
  {
198
    super(mThreadGroup, "");
199
    mLogger.debug("BuildThread");
200
    mLastBuildWasBenign = true;
201
    mException = false;
202
    mErrorReported = false;
203
    mRecoverable = false;
204
 
205
    mRtagId = rtagId;
206
    mRconId = rconId;
207
    mReleaseManager = releaseManager;
208
    mBuildFile = new File(mRtagId + "build.xml");
209
    mUnitTest = ( unitTest == null ) ? "" : unitTest;
210
 
211
    // no need to be synchronized - BuildThreads are instantiated in a single thread
212
    if ( mResumeTimerTask == null )
213
    {
214
      mResumeTimerTask = new ResumeTimerTask();
215
      mTimer = new Timer();
216
      mResumeTimerTask.setTimer(mTimer);
217
    }
218
  }
219
 
220
  /**Flags that a new build cycle is about to start
221
  *  Create a new logger to capture all the complete build log
222
  *  even though its done in stages we want a complete log.
223
  */
224
  void flagStartBuildCycle()
225
  {
226
    try
227
    {
228
      mBuildLogger = new DefaultLogger();
229
      PrintStream ps = new PrintStream(mRtagId + ".log");
230
      mBuildLogger.setOutputPrintStream(ps);
231
      mBuildLogger.setMessageOutputLevel(Project.MSG_INFO);
232
    }
233
    catch( FileNotFoundException e )
234
    {
235
      mLogger.error("BuildThread caught FileNotFoundException");
236
    }
237
  }
238
 
239
  /**
240
   *    Sleeps when mException is set
241
   *    Called at the top of the run() loop, just after exception processing
242
   */
243
  protected void sleepCheck()
244
  {
245
    if (mException && ! BuildDaemon.mShutDown)
246
    {
247
        Integer sleepTime = 5 * 60;
7033 dpurdie 248
        mLogger.warn("sleepCheck sleep {} secs", sleepTime);
6914 dpurdie 249
        BuildDaemon.daemonSleepSecs(sleepTime);
250
        mLogger.info("sleepCheck sleep returned");
251
 
252
        // Force planning cycle when recovering from an exception
253
        mReleaseSeqNum = -1;
254
    }
255
    mException = false;
256
  }
257
 
258
  /**initially changes the run level to IDLE
259
   * <br>Determines if the BuildThread is still configured
260
   * <br>Detects shutdown requests
261
   * <br>a) Determines if the BuildThread is running in scheduled down time
262
   * <br>b) Determines if the BuildThread is directed to pause
263
   * <br>Changes the run level to PAUSED if a) or b) are true
264
   * <br>throws ExitException when not configured or shutdown request
265
   * 
266
   * @param     master  True: Master Daemon
267
   * @exception ExitException
268
   * @exception SQLException
269
   * @exception Exception
270
   */
271
  protected void allowedToProceed(boolean master) throws ExitException, SQLException, Exception
272
  {
273
    mLogger.debug("allowedToProceed");
274
 
275
    //  Release Manager Database operations
276
    //      Change the run level to IDLE
277
    //      Discard any reserved version numbers
278
    //      Reset the current package being built
279
    try
280
    {
7176 dpurdie 281
      mLogger.warn("allowedToProceed changing run level to IDLE for rcon_id {}", mRconId);
6914 dpurdie 282
      mRunLevel.persist(mReleaseManager, mRconId, BuildState.DB_IDLE);
283
    }
284
    catch(SQLException e)
285
    {
286
      mLogger.warn("allowedToProceed caught SQLException");
287
    }
288
 
289
    //  Sleep for the required time - if the last build cycle was benign
290
    //      Slaves sleep for 3 seconds
291
    //      Masters will sleep for up to 60 minutes
292
    if (mLastBuildWasBenign && !BuildDaemon.mShutDown)
293
    {
294
        if (master)
295
        {
296
            long startMasterWait = System.currentTimeMillis();
297
            while(!BuildDaemon.mShutDown)
298
            {
299
                int masterCount = mReleaseManager.queryMasterCount( mRtagId );
300
                if ( masterCount != 1)
301
                {
7033 dpurdie 302
                    mLogger.warn("allowedToProceed Invalid MasterCount: {}", masterCount);
6914 dpurdie 303
                    throw new ExitException();   
304
                }
305
 
306
                int seqNum = mReleaseManager.queryReleaseSeqNum(mRtagId, mRconId, BuildDaemon.mHostname);
307
                if ( seqNum < 0 )
308
                {
309
                    mLogger.warn("allowedToProceed Not part of buildset");
310
                    throw new ExitException();
311
                }
312
                if ( seqNum != mReleaseSeqNum)
313
                {
314
                    //  Release Content has been modified
315
                    mReleaseSeqNum = seqNum;
7176 dpurdie 316
                    mLogger.warn("allowedToProceed. Release Sequence changed");
6914 dpurdie 317
                    break;
318
                }
319
 
7023 dpurdie 320
                //  Check daemon instructions that need to be processed
6914 dpurdie 321
                //
7023 dpurdie 322
                DaemonInstruction di = new DaemonInstruction(mRtagId, -1, true);
323
                if ( mReleaseManager.getDaemonInst( di ) )
324
                {
325
                    // At least one daemon instruction has expired
7176 dpurdie 326
                    mLogger.warn("allowedToProceed. Deamon instruction to be scheduled");
7023 dpurdie 327
                    break;
328
                }
329
 
330
                //
6914 dpurdie 331
                //    Backup plan
332
                //    Force a planning session every 60 minutes
333
                //
334
                if ( System.currentTimeMillis() - startMasterWait > 60 * 60 * 1000)
335
                {
7176 dpurdie 336
                    mLogger.warn("allowedToProceed. Force periodic plan");
6914 dpurdie 337
                    break;
338
                }
339
 
340
                //  Have no reason to suspect that we need to plan a release
341
                //  Persist idle state to indicate the master is polling
342
                //  Wait 60 seconds and test again
343
                //
344
                mLogger.warn("allowedToProceed sleep 60 secs no build requirement");
345
                mRunLevel.persist(mReleaseManager, mRconId, BuildState.DB_IDLE);
346
                BuildDaemon.daemonSleepSecs(60);
347
                mLogger.info("allowedToProceed sleep returned");
348
            }
349
        }
350
        else
351
        {
352
            //  Slave - Wait 3 seconds
353
            mLogger.warn("allowedToProceed sleep 3 secs no build requirement");
354
            BuildDaemon.daemonSleepSecs(3);
355
            mLogger.info("allowedToProceed sleep returned");
356
        }
357
    }
358
 
359
    if (BuildDaemon.mShutDown)
360
    {
361
        mLogger.warn("allowedToProceed ShutDown requested");
362
        throw new ExitException();
363
    }
364
 
365
    //  Loop until this daemon can proceed - or no longer configured as a part of the build set
366
    //  Things that will hold it up:
367
    //      Indefinite pause
368
    //      Scheduled down time
369
    //      Waiting for recoverable error (dpkg_archive access)
370
 
371
    boolean proceed = false;
372
    try
373
    {
374
      while ( !proceed &&  !BuildDaemon.mShutDown )
375
      {
7176 dpurdie 376
        mLogger.warn("allowedToProceed calling mReleaseManager.connect");                      
6914 dpurdie 377
        mReleaseManager.connect();
378
 
379
        //  Ensure that the machine is a part of the current build set
7176 dpurdie 380
        mLogger.warn("allowedToProceed calling mReleaseManager.queryReleaseConfig");                      
6914 dpurdie 381
        if ( !mReleaseManager.queryReleaseConfig(mRtagId, mRconId, BuildDaemon.mHostname, getMode(), mStartTime) )
382
        {
383
            mLogger.warn("allowedToProceed queryReleaseConfig failed");
384
            mReleaseManager.disconnect();
385
            throw new ExitException();
386
        }
387
 
388
        //  If dpkg_archive has been flagged as unavailable, then check for its recovery
389
        //
390
        if ( mRecoverable )
391
        {
392
            if ( Package.recover() )
393
            {
394
                // Package archive(s) exist
395
                // clear the indefinite pause condition
396
                mReleaseManager.resumeIndefinitePause();
397
                notifyIndefiniteRecovery();
398
                mRecoverable = false;
399
            }
400
        }
401
 
402
        if ( mUnitTest.compareTo("unit test not allowed to proceed") == 0 )
403
        {
404
          mRunLevel.persist(mReleaseManager, mRconId, BuildState.DB_PAUSED);
405
          throw new ExitException();
406
        }
407
 
408
        //  Check for scheduled downtime or indefinite pause
409
        MutableDate resumeTime = new MutableDate();
7176 dpurdie 410
        mLogger.warn("allowedToProceed calling mReleaseManager.queryRunLevelSchedule");                      
6914 dpurdie 411
        if ( !mReleaseManager.queryRunLevelSchedule(resumeTime) )
412
        {
413
          mLogger.info("allowedToProceed scheduled downtime");
414
          mReleaseManager.disconnect();
415
 
7176 dpurdie 416
          mLogger.warn("allowedToProceed changing run level to PAUSED for rcon_id {}", mRconId);
6914 dpurdie 417
          mRunLevel.persist(mReleaseManager, mRconId, BuildState.DB_PAUSED);
418
 
419
          //
420
          //    Limit the pause to 10 minutes at a time
421
          //    Allows early get out for the daemons
422
          long  tenMinutesFromNow = System.currentTimeMillis() + 10 * 60 * 1000;
423
          if (resumeTime.value.getTime() > tenMinutesFromNow)
424
          {
425
              resumeTime.value.setTime(tenMinutesFromNow);
426
              mLogger.warn("allowedToProceed delay limited to 10 minutes");
427
          }
428
 
429
          synchronized(mSynchroniser)
430
          {
431
            // contain the schedule and wait in the same synchronized block to prevent a deadlock
432
            // eg this thread calls schedule, timer thread calls notifyall, this thread calls wait (forever)
433
            try
434
            {
435
              if (mResumeTimerTask.isCancelled())
436
              {
437
                mResumeTimerTask = new ResumeTimerTask();
438
                mTimer = new Timer();
439
                mResumeTimerTask.setTimer(mTimer);
440
              }
7033 dpurdie 441
              mLogger.warn("allowedToProceed schedule passed {}", resumeTime.value.getTime());
6914 dpurdie 442
              mTimer.schedule(mResumeTimerTask, resumeTime.value);
443
            }
444
            catch( IllegalStateException e )
445
            {
446
              // this may be thrown by schedule if already scheduled
447
              // it signifies another BuildThread has already scheduled the ResumeTimerTask
448
               mLogger.warn("allowedToProceed already scheduled");
449
            }
450
 
451
            try
452
            {
453
              mLogger.warn("allowedToProceed wait");
454
              mSynchroniser.wait();
455
              mLogger.warn("allowedToProceed wait returned");
456
            }
457
            catch( InterruptedException e )
458
            {
459
              mLogger.warn("allowedToProceed caught InterruptedException");
460
              Thread.currentThread().interrupt();
461
            }
462
          }
463
 
464
        }
465
        else
466
        {
467
            //
468
            //  If commanded to pause, then wait around until the command has been removed.
469
            //
7176 dpurdie 470
            mLogger.warn("allowedToProceed calling mReleaseManager.queryDirectedRunLevel");                      
6914 dpurdie 471
            if ( !mReleaseManager.queryDirectedRunLevel(mRconId) && !BuildDaemon.mShutDown )
472
            {
7176 dpurdie 473
                mLogger.warn("allowedToProceed changing run level to PAUSED for rcon_id {}", mRconId);
6914 dpurdie 474
                mReleaseManager.disconnect();
475
                mRunLevel.persist(mReleaseManager, mRconId, BuildState.DB_PAUSED);
476
 
477
                //  Sleep for periodicMs
478
                mLogger.warn("allowedToProceed sleep 1 mins directed downtime");
479
                BuildDaemon.daemonSleepSecs(60);
480
                mLogger.info("allowedToProceed sleep returned");
481
            }
482
            else
483
            {
484
                mReleaseManager.disconnect();
485
                proceed = true;
486
            }
487
        }
488
      }
489
    }
490
    finally
491
    {
492
      // this block is executed regardless of what happens in the try block
493
      // even if an exception is thrown
494
      // ensure disconnect
7176 dpurdie 495
      mLogger.warn("allowedToProceed calling mReleaseManager.disconnect");                      
6914 dpurdie 496
      mReleaseManager.disconnect();
497
    }
498
 
499
    if (BuildDaemon.mShutDown)
500
    {
501
        mLogger.warn("allowedToProceed ShutDown requested");
502
        throw new ExitException();
503
    }
504
 
505
  }
506
 
507
/**periodically 
508
   * <br>a) performs disk housekeeping
509
   * <br>b) determines if a minimum threshold of disk space is available
510
   * <br>c) determines if a file can be touched
511
   * <br>d) Master: Checks that a ssh connection can be established to the package server
512
   * <br>changes the run level to CANNOT_CONTINUE if insufficient disk space
513
   * <br>otherwise changes the run level to ACTIVE and returns
514
   * <p>This function does not return until all check have passed
515
   * 
516
   * @return	true - The task was delayed until all checks passed. The caller may 
517
   * 				   wish to reevaluate the ability to continue the current task.
518
   * 
519
   */
520
  protected boolean checkEnvironment() throws Exception
521
  {
522
    mLogger.debug("checkEnvironment");
523
    boolean exit = false;
524
    boolean waitPerformed = false;
525
    String  reason;
526
 
527
    while( !exit )
528
    {
529
      mPhase.setPhase("checkEnvironment");
530
      exit = false;
531
      reason = "No Reason";
532
 
533
      //  Ugly Unit Testing Hook
534
      if ( mUnitTest.startsWith("unit test") )
535
      {
536
          if ( mUnitTest.startsWith("unit test check environment") )
537
          {
538
              if (mUnitTest.endsWith("Pass2"))
539
              {
540
                  exit = true;
541
              }
542
              else
543
              {
544
                  mUnitTest += ".Pass2";
545
                  reason = "Unit Test Check";
546
              }
547
          }
548
          else
549
          {
550
              exit= true;
551
          }
552
      }
553
      else
554
      {
555
          housekeep();
556
 
557
          //	Perform checks in order
558
          //	Failure of the first one terminates the check
559
          //
560
 
561
          if ( ! hasSufficientDiskSpace() )
562
          {
563
              mLogger.warn("checkEnvironment below disk free threshold");
564
              mPhase.setPhase("checkEnvironment:Wait for diskspace");
565
              reason = "Insufficient disk space";
566
          }
567
          else if (!touch() ) 
568
          {
569
              mLogger.warn("checkEnvironment read only file system detected");
570
              mPhase.setPhase("checkEnvironment:Wait for writable files system");
571
              reason = "Readonly file system";
572
          }
573
          else if ( !checkRemoteExecution() ) 
574
          {
575
              mLogger.warn("checkEnvironment Remote connection not established");
576
              mPhase.setPhase("checkEnvironment:Wait for remote execution");
577
              reason = "No remote connection";
578
          }
579
          else
580
          {
581
              exit = true;
582
              mPhase.setPhase("checkEnvironment");
583
              reason = "No Reason";
584
          }
585
      }
586
      if ( !exit )
587
      {
588
    	waitPerformed = true;
7033 dpurdie 589
        mLogger.error("checkEnvironment changing run level to CANNOT_CONTINUE for rcon_id {}", mRconId);
6914 dpurdie 590
        mRunLevel.persistFault(mReleaseManager, mRconId, reason);
591
 
592
        if ( !mUnitTest.startsWith("unit test check environment") )
593
        {
594
          mLogger.warn("checkEnvironment sleep 5 mins");
595
          exit = BuildDaemon.daemonSleepSecs(5 * 60);
596
          mLogger.info("checkEnvironment sleep returned");
597
        }
598
      }
599
    }
600
 
7176 dpurdie 601
    mLogger.warn("checkEnvironment changing run level to ACTIVE for rcon_id {}", mRconId);
6914 dpurdie 602
    mRunLevel.persist(mReleaseManager, mRconId, BuildState.DB_ACTIVE);
603
 
604
    return waitPerformed;
605
  }
606
 
607
  /**	Perform check of the remote execution target machine
608
   * 	Used so that we know at the start of a build that the target machine can be contacted
609
   * 	Check is only done on the master
610
   * 
611
   * @return	true - the check has passed
612
   * 
613
   */
614
  private boolean checkRemoteExecution()
615
  {
616
	  if ( getMode() != 'M' || mUnitTest.startsWith("unit test") )
617
	  {
618
		  // Don't check if
619
		  //	This thread is not a master
620
		  //	This is a unit test
621
		  return true;
622
	  }
623
 
624
	  RemoteExecution mRemoteExecution = new RemoteExecution(new LogOutput(){
625
 
626
			@Override
627
			public void data(String message) {
628
				mLogger.info(message);
629
			}
630
 
631
			@Override
632
			public void fatal(String message) {
7033 dpurdie 633
				mLogger.error(message);
6914 dpurdie 634
			}
635
 
636
			@Override
637
			public void info(String message) {
638
				mLogger.info(message);
639
			}});
640
 
641
	  return mRemoteExecution.testConnection();
642
 
643
  }
644
 
645
 
646
  /**performs disk housekeeping which involves deleting build directories > 5 days old
647
   * refer to the sequence diagram check environment
648
   */
649
  private void housekeep()
650
  {
651
    mLogger.debug("housekeep");
652
    FilenameFilter filter = new FilenameFilter()
653
    {
654
 
655
      // Filter function to process the files processed within this routine
656
      public boolean accept(File file, String name)
657
      {
7033 dpurdie 658
        mLogger.debug("accept {}", name);
6914 dpurdie 659
        boolean retVal = false;
660
 
661
        if ( file.isDirectory() && !name.startsWith( "." ) )
662
        {
663
          retVal = true;
664
        }
665
 
7176 dpurdie 666
        mLogger.debug("accept returned {}", retVal);
6914 dpurdie 667
        return retVal;
668
      }
669
    };
670
 
671
    try
672
    {
673
      // DEVI 46729, 46730, solaris 10 core dumps implicate deleteDirectory
674
      // let each BuildThread look after its own housekeeping
675
      String cwdPath = utilities.catDir(BuildDaemon.mGbeLog ,BuildDaemon.mHostname ,String.valueOf( mRtagId )); 
676
      File cwd = utilities.freshFile(cwdPath);
677
 
678
      File[] children = cwd.listFiles( filter );
679
 
680
      if ( children != null )
681
      {
682
        for ( int child=0; child < children.length; child++ )
683
        {
684
          // child is named uniquely to encapsulate a build
685
          // 5 days = 432,000,000 milliseconds
7253 dpurdie 686
          // Currently 10 days
687
          if ( ( System.currentTimeMillis() - children[ child ].lastModified() ) > (10 * 24 * 60 * 60 * 1000) )
6914 dpurdie 688
          {
7253 dpurdie 689
            // the directory is over xx days old
7033 dpurdie 690
            mLogger.warn("housekeep deleting directory {}", children[ child ].getName());
6914 dpurdie 691
            deleteDirectory( children[ child ] );
7253 dpurdie 692
            mReleaseManager.removeBuildRef(mRtagId,children[ child ].getName());
6914 dpurdie 693
          }
694
        }
695
      }
696
    }
697
    catch( SecurityException e )
698
    {
699
      // this can be thrown by lastModified
700
      mLogger.warn("housekeep caught SecurityException");
701
    }
702
 
703
  }
704
 
705
  /**returns true if a file exists and can be deleted,
706
   * created and exists in the file system
707
   * this is to guard against read-only file systems
708
   */
709
  private boolean touch()
710
  {
711
    mLogger.debug("touch");
712
    boolean retVal = true;
7489 dpurdie 713
    File touch = new File( String.valueOf( mRtagId ) + "touch" );
6914 dpurdie 714
 
715
    try
716
    {
717
      if ( touch.exists() )
718
      {
719
        // delete it
720
        retVal = touch.delete();
721
      }
722
 
723
      if ( retVal )
724
      {
725
        // file does not exist
726
        retVal = touch.createNewFile();
727
      }
728
    }
729
    catch( SecurityException e )
730
    {
731
      // this can be thrown by exists, delete, createNewFile
732
      retVal = false;
733
      mLogger.warn("touch caught SecurityException");
734
    }
735
    catch( IOException e )
736
    {
737
      // this can be thrown by createNewFile
738
      retVal = false;
739
      mLogger.warn("touch caught IOException");
740
    }
741
 
7489 dpurdie 742
    if ( touch.exists() ) {
743
      // Now clean it up
744
      touch.delete();
745
    }
746
 
7176 dpurdie 747
    mLogger.debug("touch returned {}", retVal);
6914 dpurdie 748
    return retVal;
749
  }
750
 
751
  /**returns true if free disk space > 5G
752
   * this may become configurable if the need arises
753
   * refer to the sequence diagram check environment
754
   */
755
  private boolean hasSufficientDiskSpace()
756
  {
757
      mLogger.debug("hasSufficientDiskSpace");
758
      boolean retVal = true;
759
      long freeSpace = 0;
760
 
761
      try
762
      {
763
          File cwd = new File( "." );
764
 
765
          // 5G = 5368709120 bytes
766
          // 1G = 1073741824 bytes - useful for testing
767
          freeSpace = cwd.getUsableSpace();
768
 
769
          if ( freeSpace < 5368709120L )
770
          {
7033 dpurdie 771
              mLogger.warn("hasSufficientDiskSpace failed: {} freespace {}", cwd.getAbsolutePath() , freeSpace);
6914 dpurdie 772
              retVal = false;
773
          }
774
      }
775
      catch( SecurityException e )
776
      {
777
          // this can be thrown by getFreeSpace
778
          mLogger.warn("hasSufficientDiskSpace caught SecurityException");
779
      }
780
 
7033 dpurdie 781
      mLogger.info("hasSufficientDiskSpace returned {} {}", retVal, freeSpace);
6914 dpurdie 782
      return retVal;
783
  }
784
 
785
  /**abstract method
786
   */
787
  @Override
788
  public abstract void run();
789
 
790
  /**   deletes directory and all its files
791
   *    Will set files and directories to be writable in case the user has messed with
792
   *    file permissions
793
   */
794
  FilenameFilter deleteDirectoryFilter = new FilenameFilter()
795
  {
796
    public boolean accept(File file, String name)
797
    {
7033 dpurdie 798
      mLogger.debug("accept {}", name);
6914 dpurdie 799
      boolean retVal = false;
800
 
801
      if ( name.compareTo(".") != 0 && ( name.compareTo("..") != 0 ) )
802
      {
803
        retVal = true;
804
      }
805
 
7176 dpurdie 806
      mLogger.debug("accept returned {}", retVal);
6914 dpurdie 807
      return retVal;
808
    }
809
  };
810
 
811
 
812
  protected void deleteDirectory(File directory)
813
  {
7033 dpurdie 814
    mLogger.debug("deleteDirectory {}", directory.getName());
6914 dpurdie 815
    try
816
    {
817
      if ( directory.exists() )
818
      {
819
 
820
        //
821
        directory.setWritable(true);
822
        File[] children = directory.listFiles( deleteDirectoryFilter );
823
 
824
        if ( children != null )
825
        {
826
          for ( int child=0; child < children.length; child++ )
827
          {
828
            if ( children[ child ].isDirectory() )
829
            {
830
              deleteDirectory( children[ child ] );
831
            }
832
            else
833
            {
834
                children[ child ].setWritable(true);
835
                children[ child ].delete();
836
            }
837
          }
838
        }
839
        directory.delete();
840
      }
841
    }
842
    catch( SecurityException e )
843
    {
844
      // this can be thrown by exists and delete
845
       mLogger.warn("deleteDirectory caught SecurityException");
846
    }
847
  }
848
 
849
  /**
850
   * abstract method. Re-implemented by each class
851
   * Returns the Mode in which the thread is operating.
852
   * 
853
   * @return M if Master, S if Slave
854
   */
855
  protected abstract char getMode();
856
 
857
  /** Save the buildfile to disk
858
   *  @param buildFileContent The generated build file that will control this run
859
 * @throws Exception 
860
   * 
861
   */
862
  protected void saveBuildFile(String buildFileContent) throws BuildSystemException
863
  {
864
      mReporting.buildFailureLogFile = null;
865
      mErrorReported = false;
866
 
867
      mBuildFile.delete();
868
      FileWriter buildFileWriter = null;
869
      BufferedReader buildFileBufferedReader = null;
870
      boolean errorSeen = false;
871
 
872
      if ( buildFileContent != null ) {
873
 
874
          try {
875
 
876
              StringReader buildFileContentStringReader = new StringReader(buildFileContent);
877
              buildFileBufferedReader = new BufferedReader(buildFileContentStringReader);
878
 
879
              // Sanitize the buildFileContent
880
              //      it may contain line.separators of "\n", "\r", or "\r\n" variety, 
881
              //      depending on the location of the ripple engine
882
              StringBuilder  sanitisedBFC = new StringBuilder ();
883
              String lf = System.getProperty("line.separator") ;
884
              String line;
885
 
886
              while( ( line = buildFileBufferedReader.readLine() ) != null)
887
              {
888
                  sanitisedBFC.append (line).append(lf);
889
              }
890
              buildFileBufferedReader.close();
891
              buildFileBufferedReader = null;
892
 
893
              buildFileWriter = new FileWriter(mBuildFile);
894
              buildFileWriter.write(sanitisedBFC.toString());
895
              buildFileWriter.close();
896
              buildFileWriter = null;
897
 
898
          }
899
          catch( IOException e )
900
          {
901
              mLogger.error("SaveBuildFile caught IOException");
902
              errorSeen = true;
903
 
904
          }
905
          finally
906
          {
907
              if ( buildFileWriter!= null ) {
908
                  try {
909
                    buildFileWriter.close();
910
                } catch (IOException e) {
911
                    errorSeen = true;
912
 
913
                }
914
              }
915
              if ( buildFileBufferedReader != null) {
916
                  try {
917
                    buildFileBufferedReader.close();
918
                } catch (IOException e) {
919
                    errorSeen = true;
920
                }
921
              }
922
          }
923
      }
924
 
925
      if (errorSeen) {
926
          mBuildFile.delete();
927
          throw new BuildSystemException("SaveBuildFile caught FileNotFoundException");    
928
      }
929
  }
930
 
7489 dpurdie 931
  /** Delete the build file and other associated files
6914 dpurdie 932
   * 
933
   */
934
  protected void deleteBuildFile()
935
  {
7489 dpurdie 936
      if ( mBuildFile.exists() ) {
6914 dpurdie 937
          mBuildFile.delete(); //NOSONAR
7489 dpurdie 938
      }
7514 dpurdie 939
  }
940
 
941
  /** Delete miscellaneous files created during the build process
942
   *  Only used at the end or start of a build.
943
   * 
944
   */
945
  protected void deleteMiscBuildFiles()
946
  {
947
      if ( mBuildFile.exists() ) {
948
          mBuildFile.delete(); //NOSONAR
949
      }
7489 dpurdie 950
 
951
      if ( mBuildInfoFile.exists() ) {
952
          mBuildInfoFile.delete(); //NOSONAR
953
      }
954
 
955
      if ( mAbtMetricsFile.exists() ) {
956
          mAbtMetricsFile.delete(); //NOSONAR
957
      } 
6914 dpurdie 958
  }
959
 
960
  /** Parse the ANT project
961
   *  Done once per build - useful information is extracted for use within the build
962
 * @param updateReporting 
963
   */
964
  protected void parseBuildFile(boolean updateReporting)
965
  {
966
      mLogger.debug("parseBuildFile");
967
 
968
      //try
969
      {
970
          mProject = null;
971
          if (updateReporting) {
972
              mReporting.resetData();
973
          }
974
 
975
          if ( mBuildFile.exists() )
976
          {
977
              mProject = new Project();
978
              mProject.setProperty("ant.file", mBuildFile.getAbsolutePath());
979
 
980
              // Add listener for logging the complete build process
981
              // If the daemon has been restarted, then the listener will not have been
982
              // set up - so don't add it. Perhaps we need a way to open an existing
7033 dpurdie 983
              // log file to append.
6914 dpurdie 984
              if ( mBuildLogger != null )               {
985
                  mProject.addBuildListener(mBuildLogger);
986
              }
987
 
988
              // parse can throw BuildException, this is serious
989
              //  Generate a general exception to force an indefinite pause
990
              //  Delete build file to prevent attempts to use it 
991
              try {
992
                  mProject.init();
993
                  ProjectHelper pH = ProjectHelper.getProjectHelper();
994
                  mProject.addReference("ant.projectHelper", pH);
995
                  pH.parse(mProject, mBuildFile);
996
              }
997
              catch (BuildException be)
998
              {
7033 dpurdie 999
                  mLogger.error("Parse Error: {}", be );
6914 dpurdie 1000
 
1001
                  mBuildFile.delete(); //NOSONAR
1002
                  throw new BuildToolException("Error parsing build file:"+ be.getCause());
1003
              }
1004
 
1005
              if (updateReporting) {
1006
                  // set up project properties for reporting purposes
1007
                  // this first group are hard coded in the build file
1008
 
1009
                  mReporting.rtagId = mRtagId;
1010
                  mReporting.buildRef = mProject.getProperty("abt_daemon");
1011
                  mReporting.packageName = mProject.getProperty("abt_package_name");
1012
                  mReporting.packageVersion = mProject.getProperty("abt_package_version");
1013
                  mReporting.packageExtension = mProject.getProperty("abt_package_extension");
1014
                  mReporting.packageLocation = mProject.getProperty("basedir") + mProject.getProperty("abt_package_location");
1015
                  mReporting.packageDepends = mProject.getProperty("abt_package_depends");
1016
                  mReporting.packageOwners = mProject.getProperty("abt_package_ownerlist");
1017
                  mReporting.packageBuildInfo = mProject.getProperty("abt_package_build_info");
1018
                  mReporting.isRipple = ReportingData.toBool(mProject.getProperty("abt_is_ripple"));
1019
                  mReporting.buildReason = ReportingData.toBuildReason(mProject.getProperty("abt_build_reason"));
1020
                  mReporting.packageVersionId = ReportingData.toInt(mProject.getProperty("abt_package_version_id"), -1);
1021
                  mReporting.doesNotRequireSourceControlInteraction = ReportingData.toBool(mProject.getProperty("abt_does_not_require_source_control_interaction"));
1022
                  mReporting.isaTestBuild = ReportingData.toBool (mProject.getProperty("abt_test_build_instruction"));
7155 dpurdie 1023
                  mReporting.testBuildId = ReportingData.toInt (mProject.getProperty("abt_test_build_instruction"), -1);
1024
                  mReporting.vcsTag = mProject.getProperty("abt_vcs_tag");
6914 dpurdie 1025
              }
1026
              //
1027
              //  Log the abt_daemon value so that we can trace the build log
1028
              //  Build logs from all machines are stored with this number
1029
              //  Only do it at the start of the build
1030
 
7176 dpurdie 1031
              mLogger.warn("BuildRef: {}", mReporting.buildRef );    
6914 dpurdie 1032
 
1033
          }
1034
      }
1035
  }
1036
 
1037
 
1038
  /**
1039
   * builds a buildFile from the buildFileContent
1040
   * triggers ant to operate on the buildFile
1041
   * 
1042
   * @param target           The command to be run. This is one of the commands known to the ABT class
1043
   * @param updateReporting  True to update the mReportingData from the buildfiles properties
1044
   * @param forceOpr         True - force operation on errors
1045
   * @throws Exception
1046
   * @throws BuildException 
1047
   * @return Success - False on error             
1048
   */
1049
  protected boolean deliverChange(String target, boolean updateReporting, boolean forceOpr) throws Exception
1050
  {
1051
      mLogger.debug("deliverChange");
1052
 
1053
      //  Bypass the operation if an error is being propagated
1054
      //      Always perform a AbtSetUp and AbtTearDown
1055
      //
1056
      if (!forceOpr) {
1057
          if ( mErrorReported ) {
1058
              // SaveBuildFile, AbtTestPath, AbtSetUp or the build failed
1059
              // the default target will inevitably fail and will generate further email if allowed to proceed
1060
              // do not mask the root cause
1061
              mLogger.debug("deliverChange - Error already detected");
1062
              return false;    
1063
          }
1064
      }
1065
 
1066
      //  Parse the build file
1067
      parseBuildFile(updateReporting);
1068
      if (mProject != null)
1069
      {
1070
 
1071
          if ( target == null )     {
1072
              target = mProject.getDefaultTarget();
1073
          }
7033 dpurdie 1074
          mLogger.warn("deliverChange ant launched: {} Target: {}", mBuildFile.getAbsolutePath() , target);
6914 dpurdie 1075
 
1076
          try {
1077
              mProject.executeTarget(target);
1078
              mLogger.warn("deliverChange ant returned");
1079
          }
1080
 
1081
          //   
1082
          //  Do not catch ALL exceptions. The MasterThread::run and SlaveThread::run
1083
          //  relies on exceptions propagating upwards for the indefinite pause feature
1084
          //
1085
          catch( BuildException e )
1086
          {
1087
 
1088
              if ( mReporting.buildFailureLogFile == null )
1089
              {
1090
                  mReporting.buildFailureLogFile = e.getMessage();
1091
              }
7033 dpurdie 1092
              mLogger.debug("deliverChange caught BuildException, big deal, the build failed {}", mReporting.buildFailureLogFile);
6914 dpurdie 1093
              mErrorReported = true;
1094
          }
1095
 
1096
 
1097
          if (updateReporting )
1098
          {
1099
              // this group is set at run time (by the AbtPublish target only)
1100
              // they will be null for every other target,
1101
              // and null if an error occurs in the AbtPublish target
1102
              mReporting.isFullyPublished = ReportingData.toBool(mProject.getProperty("abt_fully_published"));
1103
              mReporting.newVcsTag = mProject.getProperty("abt_new_vcstag");
1104
          }
1105
      }
1106
 
1107
      return !mErrorReported;
1108
  }
1109
 
1110
   /**
1111
    * eMail an indefinite pause notification
1112
    * 
1113
    * @param cause  Reason for the pause
1114
    */
1115
   protected void notifyIndefinitePause(String cause)
1116
   {
1117
     mLogger.debug("indefinitePause");
1118
 
1119
     String body =
1120
     "Hostname: " + BuildDaemon.mHostname + "<p>" +
1121
     "Release: " + mRippleEngine.mBaselineName + "<p>" +
1122
     "Cause: " + cause + "<p>" +
1123
     "RmRef: "   + CreateUrls.generateReleaseUrl(mRippleEngine.getRtagId()) +"<p>";
1124
 
1125
     try
1126
     {
1127
       Smtpsend.send(
1128
       mRippleEngine.getMailServer(),       // mailServer
1129
       mRippleEngine.getMailSender(),       // source
1130
       mRippleEngine.getMailGlobalTarget(), // target (list)
7033 dpurdie 1131
       null,                                // cc
1132
       null,                                // bcc
1133
       "BUILD DAEMON Indefinite Pause",     // subject
1134
       body,                                // body
1135
       null                                 // attachment
6914 dpurdie 1136
       );
1137
     }
1138
     catch( Exception e )
1139
     {
1140
     }
1141
   }
1142
 
1143
 
1144
   /**
1145
    * eMail an indefinite pause recovery notification
1146
    */
1147
   private void notifyIndefiniteRecovery()
1148
   {
1149
       mLogger.debug("indefiniteRecovery");
1150
 
1151
       String body =
1152
       "Hostname: " + BuildDaemon.mHostname + "<p>" +
1153
       "Release: "  + mRippleEngine.mBaselineName + "<p>" +
1154
       "Cause: "    + "Recovery of previous condition" + "<p>" +
1155
       "RmRef: "    + CreateUrls.generateReleaseUrl(mRippleEngine.getRtagId()) +"<p>";
1156
 
1157
       try
1158
       {
1159
         Smtpsend.send(
1160
         mRippleEngine.getMailServer(),                 // mailServer
1161
         mRippleEngine.getMailSender(),                 // source
1162
         mRippleEngine.getMailGlobalTarget(),           // target (list)
7033 dpurdie 1163
         null,                                          // cc
1164
         null,                                          // bcc
1165
         "BUILD DAEMON Indefinite Pause Recovery",      // subject
1166
         body,                                          // body
1167
         null                                           // attachment
6914 dpurdie 1168
         );
1169
       }
1170
       catch( Exception e )
1171
       {
1172
       }
1173
 
1174
   }
1175
 
1176
 
1177
    /**
1178
     *  Nagios interface
1179
     *      Returns true if the thread looks OK
1180
     * @param nagInfo 
1181
    */
1182
    boolean checkThread(NagiosInfo nagInfo)
1183
    {
1184
      boolean retVal = true;
1185
      if ( mRunLevel.mBuildState == BuildState.DB_CANNOT_CONTINUE ) {
1186
        retVal = false;
1187
        nagInfo.extendedReason.add("[" + mRtagId + "] " + mRunLevel.toString());
1188
      } else  {
1189
        retVal = checkThreadExtended(nagInfo);
1190
      }
1191
 
1192
      if (this.getMode() == 'M') {
1193
          nagInfo.masterCount++;
1194
      } else {
1195
          nagInfo.slaveCount++;
1196
      }
1197
 
7033 dpurdie 1198
      mLogger.warn("checkThread[{}] returned {}",mRtagId, retVal);
6914 dpurdie 1199
      return retVal;
1200
    }
1201
 
1202
    /**
1203
     * Nagios interface extension
1204
     * This method should be overridden by classes that extend this class
1205
     * If not overridden then the test indicates OK
1206
     *
1207
     *      Returns true if the thread looks OK
1208
    */
1209
    boolean checkThreadExtended(NagiosInfo nagInfo)
1210
    {
1211
        boolean retVal = mPhase.isHappy();
1212
        if ( ! retVal) {
1213
            nagInfo.extendedReason.add("[" + mRtagId + "] " + mPhase.happyText());
1214
        }
7033 dpurdie 1215
        mLogger.info("checkThreadExtended: {}:{}",retVal, mPhase.toStringSecs() );
6914 dpurdie 1216
        return retVal;
1217
    }
1218
 
1219
 
1220
    /**
1221
     * Nagios interface extension
1222
     * This method should be overridden by classes that extend this class
1223
     * If not overridden then the test indicates OK
1224
     *
1225
     *      Returns true if the thread looks OK
1226
     * @param wr Output buffer
1227
     * @param nagInfo 
1228
     * @param estatus 
1229
     * @throws IOException 
1230
    */
1231
    public void extendedStatus(NagiosInfo nagInfo, Map<String, Object> estatus)
1232
    {
1233
        LinkedHashMap<String, Object> data = new LinkedHashMap<String, Object>();
1234
        estatus.put(String.valueOf(mRtagId), data);
1235
 
1236
        data.put("rtagId", Integer.valueOf(mRtagId));
1237
        data.put("rconId", Integer.valueOf(mRconId));
1238
        data.put("Thread Start Text", new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").format(new Date(mStartTime)));
1239
        data.put("Thread Start", Long.valueOf(mStartTime));
1240
        data.put("Thread Alive:", Boolean.valueOf(this.isAlive() ));
1241
        data.put("Thread Check:", Boolean.valueOf(checkThread(nagInfo) ));
1242
        data.put("Mode" , String.valueOf(this.getMode() ));
1243
        data.put("mRunLevel" , mRunLevel.toString() );
1244
        data.put("mLastBuildWasBenign" , Boolean.valueOf(mLastBuildWasBenign));
1245
        data.put("mException" , Boolean.valueOf(mException));
1246
        data.put("mErrorReported" , Boolean.valueOf(mErrorReported));
1247
        data.put("mRecoverable", Boolean.valueOf(mRecoverable));
1248
        data.put("mPhase" ,  mPhase.sText);
1249
        data.put("mPhaseDelta" ,  mPhase.getDeltaSecs());
1250
        if ( mReleaseManager != null)
1251
            data.put("Rm Mutex" , mReleaseManager.mMutexState );
1252
        else
1253
            data.put("Rm Mutex" , null );
1254
    }
1255
 
1256
}