Subversion Repositories DevTools

Rev

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