Subversion Repositories DevTools

Rev

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