Subversion Repositories DevTools

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
814 mhunt 1
package com.erggroup.buildtool.daemon;
2
 
3
import com.erggroup.buildtool.daemon.ResumeTimerTask;
4
import com.erggroup.buildtool.ripple.ReleaseManager;
5
 
6
import java.io.BufferedReader;
7
import java.io.File;
8
import java.io.FileNotFoundException;
9
import java.io.FileOutputStream;
10
import java.io.FileWriter;
11
import java.io.FilenameFilter;
12
 
13
import java.io.IOException;
14
import java.io.PrintStream;
15
import java.io.StringReader;
16
 
17
import java.sql.SQLException;
18
 
19
import java.util.Date;
20
import java.util.Timer;
21
 
22
import org.apache.log4j.Logger;
23
import org.apache.tools.ant.BuildException;
24
import org.apache.tools.ant.DefaultLogger;
25
import org.apache.tools.ant.Project;
26
import org.apache.tools.ant.ProjectHelper;
27
 
28
/**Build Thread sub component
29
 */
30
public abstract class BuildThread
31
  extends Thread
32
{
33
  /**baseline identifier (which release manager release this BuildThread is dealing with)
34
   * @attribute
35
   */
36
  protected int mRtagId = 0;
37
 
38
  /**unique identifier of this BuildThread
39
   * @attribute
40
   */
41
  protected int mRconId = 0;
42
 
43
  /**
44
   * @aggregation composite
45
   */
46
  protected RunLevel mRunLevel;
47
 
48
  /**
49
   * @aggregation composite
50
   */
51
  protected ReleaseManager mReleaseManager;
52
 
53
  /**configured GBEBUILDFILTER for this BuildThread
54
   * @attribute
55
   */
56
  protected String mGbebuildfilter = "";
57
 
58
  /**
59
   * @aggregation composite
60
   */
61
  protected static ResumeTimerTask mResumeTimerTask;
62
 
63
  /**
64
   * @aggregation composite
65
   */
66
  private static Timer mTimer;
67
 
68
  /**Synchroniser object
69
   * Use to Synchronize on by multiple threads
70
   * @attribute
71
   */
72
  static final Object mSynchroniser = new Object();
73
 
74
  /**BuildThread group
75
   * @attribute
76
   */
77
  public static final ThreadGroup mThreadGroup = new ThreadGroup("BuildThread");
78
 
79
  /**the advertised build file content when either
80
   * a) no package in the baseline has a build requirement
81
   * b) the next package in the baseline with a build requirement is generic
82
   * @attribute
83
   */
84
  protected static final String mDummyBuildFileContent = new String("<dummy/>");
85
 
86
  /**Set true when last build cycle was benign.
87
   * @attribute
88
   */
89
  protected boolean mSleep;
90
 
854 mhunt 91
  /**Set true when ant error reported on any target.
92
   * @attribute
93
   */
94
  private boolean mErrorReported;
95
 
96
  /**Set true when ant error reported on AbtSetUp target.
97
   * @attribute
98
   */
99
  private boolean mSetUpErrorReported;
100
 
814 mhunt 101
  /**Logger
102
   * @attribute
103
   */
104
  private static final Logger mLogger = Logger.getLogger(BuildThread.class);
105
 
106
  /**constructor
107
   */
108
  BuildThread()
109
  {
110
    super(mThreadGroup, "");
111
    mLogger.debug("BuildThread");
112
    mSleep = false;
854 mhunt 113
    mErrorReported = false;
114
    mSetUpErrorReported = false;
814 mhunt 115
    mReleaseManager = new ReleaseManager();
116
 
117
    // no need to be synchronized - BuildThreads are instantiated in a single thread
118
    if ( mResumeTimerTask == null )
119
    {
120
      mResumeTimerTask = new ResumeTimerTask();
121
      mTimer = new Timer();
122
      mResumeTimerTask.setTimer(mTimer);
123
    }
124
  }
125
 
126
  /**initially changes the run level to IDLE
127
   * determines if the BuildThread is still configured
128
   * a) determines if the BuildThread is running in scheduled downtime
129
   * b) determines if the BuildThread is directed to pause
130
   * changes the run level to PAUSED if a) or b) are true
131
   * throws ExitException when not configured
132
   * otherwise changes the run level to WAITING and returns when allowed to proceed
133
   * implements the sequence diagrams allowed to proceed, not allowed to proceed, exit
134
   */
135
  protected void allowedToProceed() throws ExitException, SQLException, Exception
136
  {
137
    mLogger.debug("allowedToProceed");
138
 
139
    if (mSleep)
140
    {
141
      try
142
      {
818 mhunt 143
        mRunLevel = RunLevel.IDLE;
144
        mLogger.warn("allowedToProceed changing run level to IDLE for rcon_id " + mRconId);
145
        mRunLevel.persist(mReleaseManager, mRconId);
146
        mReleaseManager.clearCurrentPackageBeingBuilt(mRconId);
147
 
814 mhunt 148
        mLogger.warn("allowedToProceed sleep 5 mins no build requirement");
149
        Thread.sleep(300000);
150
        mLogger.info("allowedToProceed sleep returned");
151
      }
818 mhunt 152
      catch(SQLException e)
153
      {
154
        mLogger.warn("allowedToProceed caught SQLException");
155
      }
814 mhunt 156
      catch(InterruptedException e)
157
      {
158
        mLogger.warn("allowedToProceed sleep caught InterruptedException");
159
      }
160
    }
161
 
162
    // IMPORTANT - this is done AFTER a Thread.sleep by design
163
    // In the case of an SQLException (signifying a database access issue)
164
    // avoid accessing the database immediately
165
    mRunLevel = RunLevel.IDLE;
816 mhunt 166
    mLogger.warn("allowedToProceed changing run level to IDLE for rcon_id " + mRconId);
814 mhunt 167
    mRunLevel.persist(mReleaseManager, mRconId);
168
    boolean proceed = false;
169
 
170
    while ( !proceed )
171
    {
172
      mReleaseManager.connect();
173
      if ( !mReleaseManager.queryReleaseConfig(mRtagId, mRconId, BuildDaemon.mHostname, getMode(), mGbebuildfilter) )
174
      {
175
        mReleaseManager.disconnect();
858 mhunt 176
        mLogger.warn("allowedToProceed queryReleaseConfig failed");
814 mhunt 177
        throw new ExitException();
178
      }
179
 
180
      Date resumeTime = new Date( 0 );
181
      if ( !mReleaseManager.queryRunLevelSchedule(resumeTime) )
182
      {
183
        mLogger.info("allowedToProceed scheduled downtime");
184
        mReleaseManager.disconnect();
185
        mRunLevel = RunLevel.PAUSED;
816 mhunt 186
        mLogger.warn("allowedToProceed changing run level to PAUSED for rcon_id " + mRconId);
814 mhunt 187
        mRunLevel.persist(mReleaseManager, mRconId);
188
 
189
        synchronized(mSynchroniser)
190
        {
191
          // contain the schedule and wait in the same synchronized block to prevent a deadlock
192
          // eg this thread calls schedule, timer thread calls notifyall, this thread calls wait (forever)
193
          try
194
          {
195
            if (mResumeTimerTask.isCancelled())
196
            {
197
              mResumeTimerTask = new ResumeTimerTask();
198
              mTimer = new Timer();
199
              mResumeTimerTask.setTimer(mTimer);
200
            }
201
            mLogger.warn("allowedToProceed schedule passed " + resumeTime.getTime());
202
            mTimer.schedule(mResumeTimerTask, resumeTime);
203
          }
204
          catch( IllegalStateException e )
205
          {
206
            // this may be thrown by schedule if already scheduled
207
            // it signifies another BuildThread has already scheduled the ResumeTimerTask
208
             mLogger.warn("allowedToProceed already scheduled");
209
          }
210
 
211
          try
212
          {
213
            mLogger.warn("allowedToProceed wait");
214
            mSynchroniser.wait();
215
            mLogger.warn("allowedToProceed wait returned");
216
 
217
            if ( mGbebuildfilter.compareTo("unit test not allowed to proceed") == 0 )
218
            {
219
              throw new ExitException();
220
            }
221
          }
222
          catch( InterruptedException e )
223
          {
224
            mLogger.warn("allowedToProceed caught InterruptedException");
225
          }
226
        }
227
 
228
      }
229
      else
230
      {
231
        if ( !mReleaseManager.queryDirectedRunLevel(mRconId) )
232
        {
233
          mLogger.info("allowedToProceed downtime");
234
          mReleaseManager.disconnect();
832 mhunt 235
          mRunLevel = RunLevel.PAUSED;
236
          mLogger.warn("allowedToProceed changing run level to PAUSED for rcon_id " + mRconId);
237
          mRunLevel.persist(mReleaseManager, mRconId);
814 mhunt 238
          try
239
          {
240
            // to do, sleep for periodicMs
241
            mLogger.warn("allowedToProceed sleep 5 mins directed downtime");
242
            Thread.sleep(300000);
243
            mLogger.info("allowedToProceed sleep returned");
244
          }
245
          catch (InterruptedException e)
246
          {
247
            mLogger.warn("allowedToProceed caught InterruptedException");
248
          }
249
        }
250
        else
251
        {
252
          mReleaseManager.disconnect();
253
          proceed = true;
254
        }
255
      }
256
    }
257
 
258
    mRunLevel = RunLevel.WAITING;
816 mhunt 259
    mLogger.warn("allowedToProceed changing run level to WAITING for rcon_id " + mRconId);
814 mhunt 260
    mRunLevel.persist(mReleaseManager, mRconId);
261
 
262
  }
263
 
264
  /**periodically 
265
   * a) performs disk housekeeping
266
   * b) determines if a minimum threshold of disk space is available
267
   * changes the run level to CANNOT_CONTINUE if insufficient disk space
268
   * otherwise changes the run level to ACTIVE and returns
269
   * implements the sequence diagram check environment
270
   */
271
  protected void checkEnvironment() throws Exception
272
  {
273
    mLogger.debug("checkEnvironment");
274
    boolean exit = false;
275
 
276
    while( !exit )
277
    {
278
      housekeep();
279
 
280
      // attempt to exit
281
      exit = true;
282
 
283
      if ( !hasSufficientDiskSpace() )
284
      {
285
        mLogger.warn("checkEnvironment below disk free threshold");
286
        exit = false;
287
        mRunLevel = RunLevel.CANNOT_CONTINUE;
816 mhunt 288
        mLogger.warn("checkEnvironment changing run level to CANNOT_CONTINUE for rcon_id " + mRconId);
814 mhunt 289
        mRunLevel.persist(mReleaseManager, mRconId);
290
        try
291
        {
292
          // to do, sleep for periodicMs
293
          if ( mGbebuildfilter.compareTo("unit test check environment") != 0 )
294
          {
295
            mLogger.warn("checkEnvironment sleep 5 mins below disk free threshold");
296
            Thread.sleep(300000);
297
            mLogger.info("checkEnvironment sleep returned");
298
          }
299
        }
300
        catch (InterruptedException e)
301
        {
302
          mLogger.warn("checkEnvironment caught InterruptedException");
303
        }
304
      }
305
    }
306
 
307
    mRunLevel = RunLevel.ACTIVE;    
816 mhunt 308
    mLogger.warn("checkEnvironment changing run level to ACTIVE for rcon_id " + mRconId);
814 mhunt 309
    mRunLevel.persist(mReleaseManager, mRconId);
310
  }
311
 
312
  /**performs disk housekeeping which involves deleting build directories > 5 days old
313
   * refer to the sequence diagram check environment
314
   */
315
  private void housekeep()
316
  {
317
    mLogger.debug("housekeep");
318
    FilenameFilter filter = new FilenameFilter()
319
    {
320
      public boolean accept(File file, String name)
321
      {
322
        mLogger.debug("accept " + name);
323
        boolean retVal = false;
324
 
325
        if ( file.isDirectory() && !name.startsWith( "." ) )
326
        {
327
          retVal = true;
328
        }
329
 
330
        mLogger.info("accept returned " + retVal);
331
        return retVal;
332
      }
333
    };
334
 
335
    try
336
    {
842 mhunt 337
      // DEVI 46729, 46730, solaris 10 core dumps implicate deleteDirectory
338
      // let each BuildThread look after its own housekeeping
854 mhunt 339
      File ocwd = new File( BuildDaemon.mGbeLog );
340
      File hcwd = new File( ocwd, BuildDaemon.mHostname );
341
      File cwd = new File( hcwd, String.valueOf( mRtagId ) );
842 mhunt 342
 
814 mhunt 343
      File[] children = cwd.listFiles( filter );
344
 
345
      if ( children != null )
346
      {
347
        for ( int child=0; child < children.length; child++ )
348
        {
854 mhunt 349
          // child is named uniquely to encapsulate a build
350
          // 5 days = 432,000,000 milliseconds
351
          if ( ( System.currentTimeMillis() - children[ child ].lastModified() ) > 432000000 )
814 mhunt 352
          {
854 mhunt 353
            // the directory is over 5 days old
354
            mLogger.warn("housekeep deleting directory " + children[ child ].getName());
355
            if ( mGbebuildfilter.compareTo("unit test check environment") != 0 )
814 mhunt 356
            {
854 mhunt 357
              deleteDirectory( children[ child ] );
814 mhunt 358
            }
359
          }
360
        }
361
      }
362
    }
363
    catch( SecurityException e )
364
    {
365
      // this can be thrown by lastModified
366
       mLogger.warn("housekeep caught SecurityException");
367
    }
368
 
369
  }
370
 
371
  /**returns true if free disk space > 10G
372
   * this may become configurable if the need arises
373
   * refer to the sequence diagram check environment
374
   */
375
  private boolean hasSufficientDiskSpace()
376
  {
377
    mLogger.debug("hasSufficientDiskSpace");
378
    boolean retVal = true;
379
    long freeSpace = 0;
380
 
381
    try
382
    {
383
      File cwd = new File( "." );
384
 
385
      // 5G = 5368709120 bytes
386
      if ( mGbebuildfilter.compareTo("unit test check environment") == 0 )
387
      {
864 mhunt 388
        if ( ReleaseManager.mPersistedRunLevelCollection.size() == 0 )
814 mhunt 389
        {
390
          retVal = false;
391
        }
392
        else
393
        {
394
          retVal = true;
395
        }
396
      }
397
      else
398
      {
816 mhunt 399
        freeSpace = cwd.getUsableSpace();
814 mhunt 400
 
401
        if ( freeSpace < 5368709120L )
402
        {
816 mhunt 403
          mLogger.warn("hasSufficientDiskSpace on " + cwd.getAbsolutePath() + " freeSpace " + freeSpace);
814 mhunt 404
          retVal = false;
405
        }
406
      }
407
    }
408
    catch( SecurityException e )
409
    {
410
      // this can be thrown by getFreeSpace
411
       mLogger.warn("hasSufficientDiskSpace caught SecurityException");
412
    }
413
 
414
    mLogger.info("hasSufficientDiskSpace returned " + retVal + " " + freeSpace);
415
    return retVal;
416
  }
417
 
418
  /**abstract method
419
   */
420
  public abstract void run();
421
 
422
  /**deletes directory and all its files
423
   */
424
  protected void deleteDirectory(File directory)
425
  {
426
    mLogger.debug("deleteDirectory " + directory.getName());
427
    try
428
    {
429
      if ( directory.exists() )
430
      {
431
        FilenameFilter filter = new FilenameFilter()
432
        {
433
          public boolean accept(File file, String name)
434
          {
435
            mLogger.debug("accept " + name);
436
            boolean retVal = false;
437
 
840 mhunt 438
            if ( name.compareTo(".") != 0 && ( name.compareTo("..") != 0 ) )
814 mhunt 439
            {
440
              retVal = true;
441
            }
442
 
443
            mLogger.info("accept returned " + retVal);
444
            return retVal;
445
          }
446
        };
447
 
448
        File[] children = directory.listFiles( filter );
449
 
450
        if ( children != null )
451
        {
452
          for ( int child=0; child < children.length; child++ )
453
          {
454
            if ( children[ child ].isDirectory() )
455
            {
456
              deleteDirectory( children[ child ] );
457
            }
458
            else
459
            {
460
              children[ child ].delete();
461
            }
462
          }
463
        }
464
        directory.delete();
465
      }
466
    }
467
    catch( SecurityException e )
468
    {
469
      // this can be thrown by exists and delete
470
       mLogger.warn("deleteDirectory caught SecurityException");
471
    }
472
  }
473
 
474
  /**abstract method
475
   */
476
  protected abstract char getMode();
477
 
478
  /**injects GBE_BUILDFILTER into the passed buildFileContent
479
   * builds a buildFile from the buildFileContent
480
   * triggers ant to operate on the buildFile
481
   */
854 mhunt 482
  protected void deliverChange(String buildFileContent, String target, boolean master)
814 mhunt 483
  {
484
    mLogger.debug("deliverChange");
854 mhunt 485
 
486
    if ( ( target == null || target.compareTo( "AbtPublish" ) == 0 ) && ( mErrorReported ) )
487
    {
488
      // previous error occurred processing the build file content
489
      return;
490
    }
491
 
814 mhunt 492
    File buildFile = new File(mRtagId + "build.xml");
862 mhunt 493
    boolean logError = true;
814 mhunt 494
 
495
    try
496
    {
497
      // clear the file contents
498
      if ( buildFileContent != null && target != null && target.compareTo("AbtSetUp") == 0 )
499
      {
500
        FileOutputStream buildFileOutputStream = new FileOutputStream(buildFile, false);
501
        buildFileOutputStream.close();
502
 
503
        StringReader buildFileContentStringReader = new StringReader(buildFileContent);
504
        BufferedReader buildFileBufferedReader = new BufferedReader(buildFileContentStringReader);
505
 
506
        // sanitise the buildFileContent
507
        // it may contain line.separators of "\n", "\r", or "\r\n" variety, depending on the location of the ripple engine
508
        String sanitisedBFC = new String();
509
        String lf = new String( System.getProperty("line.separator") );
510
        int lineCount = 0;
511
        String line = new String();
512
 
513
        while( ( line = buildFileBufferedReader.readLine() ) != null)
514
        {
515
          sanitisedBFC += line + lf;
516
          lineCount++;
517
 
518
          if ( lineCount == 2 )
519
          {
520
            // have read the first two lines
862 mhunt 521
            String inject = "<property name=\"abt_MASTER\" value=\"";
814 mhunt 522
 
523
            if ( master )
524
            {
525
              inject += "yes\"/>" + lf;
526
            }
527
            else
528
            {
529
              inject += "no\"/>" + lf;
530
            }
531
 
532
            // insert a GBE_BUILDFILTER property if necessary
533
            if ( mGbebuildfilter.length() > 0 )
534
            {
862 mhunt 535
              inject += "<property name=\"abt_GBE_BUILDFILTER\" value=\"" + mGbebuildfilter + "\"/>" + lf;
814 mhunt 536
            }
537
 
538
            mLogger.info("deliverChange injecting " + inject);
539
            sanitisedBFC += inject;
540
          }
541
        }
542
        buildFileBufferedReader.close();
543
        FileWriter buildFileWriter = new FileWriter(buildFile);
544
        buildFileWriter.write(sanitisedBFC);
545
        buildFileWriter.close();
546
      }
547
 
548
      Project p = new Project();
549
      p.setProperty("ant.file", buildFile.getAbsolutePath());
550
      DefaultLogger dl = new DefaultLogger();
551
      PrintStream ps = new PrintStream(mRtagId + ".log");
552
      dl.setOutputPrintStream(ps);
553
      dl.setMessageOutputLevel(Project.MSG_INFO);
554
      p.addBuildListener(dl);
555
      p.init();
556
      ProjectHelper pH = ProjectHelper.getProjectHelper();
557
      p.addReference("ant.projectHelper", pH);
862 mhunt 558
 
559
      // parse can throw BuildException, this is serious
814 mhunt 560
      pH.parse(p, buildFile);
561
      mLogger.warn("deliverChange ant launched on " + buildFile.getAbsolutePath());
562
 
563
      if ( target == null )
564
      {
565
        target = p.getDefaultTarget();
566
      }
567
      mLogger.warn("deliverChange ant launched against target " + target);
862 mhunt 568
 
569
      // executeTarget can throw BuildException, this is not serious
570
      logError = false;
814 mhunt 571
      p.executeTarget(target);
572
      mLogger.warn("deliverChange ant returned");
573
    }
574
    catch( BuildException e )
575
    {
862 mhunt 576
      if ( logError )
577
      {
578
        mLogger.error("deliverChange caught BuildException, the build failed " + e.getMessage());
579
      }
580
      else
581
      {
582
        mLogger.debug("deliverChange caught BuildException, big deal, the build failed " + e.getMessage());
583
      }
854 mhunt 584
 
585
      if ( target.compareTo( "AbtSetUp ") == 0 )
586
      {
587
        mSetUpErrorReported = true;
588
      }
589
 
590
      mErrorReported = true;
814 mhunt 591
    }
592
    catch( FileNotFoundException e )
593
    {
594
      mLogger.error("deliverChange caught FileNotFoundException");
595
    }
596
    catch( IOException e )
597
    {
598
      mLogger.error("deliverChange caught IOException");
599
    }
600
 
601
  }
602
 
603
  /**sets up a ClearCase static view
604
   */
854 mhunt 605
  protected void setViewUp(String content, boolean master)
814 mhunt 606
  {
607
    mLogger.debug("setViewUp");
854 mhunt 608
    mErrorReported = false;
609
    mSetUpErrorReported = false;
814 mhunt 610
    // run ant on the AbtSetUp target
611
    deliverChange(content, "AbtSetUp", master);
612
  }
613
 
614
  /**tears down a ClearCase static view
615
  */
854 mhunt 616
  protected void tearViewDown()
814 mhunt 617
  {
618
    mLogger.debug("tearViewDown");
854 mhunt 619
 
620
    if ( !mSetUpErrorReported )
621
    {
622
      // no error setting the view up, so...
623
      // run ant on the AbtTearDown target
624
      deliverChange(null, "AbtTearDown", false);
625
    }
814 mhunt 626
  }
627
 
628
  /**Checks the archive for the <packageName>/<packageVersion>/built.<machtype> existence
629
   */
630
  protected boolean published(String archive, String packageName, 
631
                            String packageVersion, String machtype,
632
                            String generic)
633
  {
634
    mLogger.debug("published");
635
    boolean retVal = false;
636
 
637
    String fs = System.getProperty( "file.separator" );
638
    String destination = archive + fs + packageName + fs + packageVersion;
639
 
640
    mLogger.debug("published " + destination);
641
    String filename = "built.";
642
 
643
    if ( generic.compareTo("generic") == 0 )
644
    {
645
      filename += "generic";
646
    }
647
    else
648
    {
649
      filename += machtype;
650
    }
651
 
652
    mLogger.debug("published " + filename);
653
    File flag = new File( destination, filename );
654
 
655
    if ( flag.exists() )
656
    {
657
      retVal = true;
658
    }
659
    mLogger.debug("published returned " + retVal);
660
    return retVal;
661
  }
662
}