Subversion Repositories DevTools

Rev

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