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.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
 
92
  /**Logger
93
   * @attribute
94
   */
95
  private static final Logger mLogger = Logger.getLogger(BuildThread.class);
96
 
97
  /**constructor
98
   */
99
  BuildThread()
100
  {
101
    super(mThreadGroup, "");
102
    mLogger.debug("BuildThread");
103
    mRunLevel = RunLevel.IDLE;
104
    mSleep = false;
105
    mReleaseManager = new ReleaseManager();
106
 
107
    // no need to be synchronized - BuildThreads are instantiated in a single thread
108
    if ( mResumeTimerTask == null )
109
    {
110
      mResumeTimerTask = new ResumeTimerTask();
111
      mTimer = new Timer();
112
      mResumeTimerTask.setTimer(mTimer);
113
    }
114
  }
115
 
116
  /**initially changes the run level to IDLE
117
   * determines if the BuildThread is still configured
118
   * a) determines if the BuildThread is running in scheduled downtime
119
   * b) determines if the BuildThread is directed to pause
120
   * changes the run level to PAUSED if a) or b) are true
121
   * throws ExitException when not configured
122
   * otherwise changes the run level to WAITING and returns when allowed to proceed
123
   * implements the sequence diagrams allowed to proceed, not allowed to proceed, exit
124
   */
125
  protected void allowedToProceed() throws ExitException, SQLException, Exception
126
  {
127
    mLogger.debug("allowedToProceed");
128
 
129
    if (mSleep)
130
    {
131
      try
132
      {
133
        mLogger.warn("allowedToProceed sleep 5 mins no build requirement");
134
        Thread.sleep(300000);
135
        mLogger.info("allowedToProceed sleep returned");
136
      }
137
      catch(InterruptedException e)
138
      {
139
        mLogger.warn("allowedToProceed sleep caught InterruptedException");
140
      }
141
    }
142
 
143
    // IMPORTANT - this is done AFTER a Thread.sleep by design
144
    // In the case of an SQLException (signifying a database access issue)
145
    // avoid accessing the database immediately
146
    mRunLevel = RunLevel.IDLE;
147
    mRunLevel.persist(mReleaseManager, mRconId);
148
    boolean proceed = false;
149
 
150
    while ( !proceed )
151
    {
152
      mReleaseManager.connect();
153
      if ( !mReleaseManager.queryReleaseConfig(mRtagId, mRconId, BuildDaemon.mHostname, getMode(), mGbebuildfilter) )
154
      {
155
        mReleaseManager.disconnect();
156
        mLogger.fatal("allowedToProceed queryReleaseConfig failed");
157
        throw new ExitException();
158
      }
159
 
160
      Date resumeTime = new Date( 0 );
161
      if ( !mReleaseManager.queryRunLevelSchedule(resumeTime) )
162
      {
163
        mLogger.info("allowedToProceed scheduled downtime");
164
        mReleaseManager.disconnect();
165
        mRunLevel = RunLevel.PAUSED;
166
        mRunLevel.persist(mReleaseManager, mRconId);
167
 
168
        synchronized(mSynchroniser)
169
        {
170
          // contain the schedule and wait in the same synchronized block to prevent a deadlock
171
          // eg this thread calls schedule, timer thread calls notifyall, this thread calls wait (forever)
172
          try
173
          {
174
            if (mResumeTimerTask.isCancelled())
175
            {
176
              mResumeTimerTask = new ResumeTimerTask();
177
              mTimer = new Timer();
178
              mResumeTimerTask.setTimer(mTimer);
179
            }
180
            mLogger.warn("allowedToProceed schedule passed " + resumeTime.getTime());
181
            mTimer.schedule(mResumeTimerTask, resumeTime);
182
          }
183
          catch( IllegalStateException e )
184
          {
185
            // this may be thrown by schedule if already scheduled
186
            // it signifies another BuildThread has already scheduled the ResumeTimerTask
187
             mLogger.warn("allowedToProceed already scheduled");
188
          }
189
 
190
          try
191
          {
192
            mLogger.warn("allowedToProceed wait");
193
            mSynchroniser.wait();
194
            mLogger.warn("allowedToProceed wait returned");
195
 
196
            if ( mGbebuildfilter.compareTo("unit test not allowed to proceed") == 0 )
197
            {
198
              throw new ExitException();
199
            }
200
          }
201
          catch( InterruptedException e )
202
          {
203
            mLogger.warn("allowedToProceed caught InterruptedException");
204
          }
205
        }
206
 
207
      }
208
      else
209
      {
210
        if ( !mReleaseManager.queryDirectedRunLevel(mRconId) )
211
        {
212
          mLogger.info("allowedToProceed downtime");
213
          mReleaseManager.disconnect();
214
          try
215
          {
216
            // to do, sleep for periodicMs
217
            mLogger.warn("allowedToProceed sleep 5 mins directed downtime");
218
            Thread.sleep(300000);
219
            mLogger.info("allowedToProceed sleep returned");
220
          }
221
          catch (InterruptedException e)
222
          {
223
            mLogger.warn("allowedToProceed caught InterruptedException");
224
          }
225
        }
226
        else
227
        {
228
          mReleaseManager.disconnect();
229
          proceed = true;
230
        }
231
      }
232
    }
233
 
234
    mRunLevel = RunLevel.WAITING;
235
    mRunLevel.persist(mReleaseManager, mRconId);
236
 
237
  }
238
 
239
  /**periodically 
240
   * a) performs disk housekeeping
241
   * b) determines if a minimum threshold of disk space is available
242
   * changes the run level to CANNOT_CONTINUE if insufficient disk space
243
   * otherwise changes the run level to ACTIVE and returns
244
   * implements the sequence diagram check environment
245
   */
246
  protected void checkEnvironment() throws Exception
247
  {
248
    mLogger.debug("checkEnvironment");
249
    boolean exit = false;
250
 
251
    while( !exit )
252
    {
253
      housekeep();
254
 
255
      // attempt to exit
256
      exit = true;
257
 
258
      if ( !hasSufficientDiskSpace() )
259
      {
260
        mLogger.warn("checkEnvironment below disk free threshold");
261
        exit = false;
262
        mRunLevel = RunLevel.CANNOT_CONTINUE;
263
        mRunLevel.persist(mReleaseManager, mRconId);
264
        try
265
        {
266
          // to do, sleep for periodicMs
267
          if ( mGbebuildfilter.compareTo("unit test check environment") != 0 )
268
          {
269
            mLogger.warn("checkEnvironment sleep 5 mins below disk free threshold");
270
            Thread.sleep(300000);
271
            mLogger.info("checkEnvironment sleep returned");
272
          }
273
        }
274
        catch (InterruptedException e)
275
        {
276
          mLogger.warn("checkEnvironment caught InterruptedException");
277
        }
278
      }
279
    }
280
 
281
    mRunLevel = RunLevel.ACTIVE;    
282
    mRunLevel.persist(mReleaseManager, mRconId);
283
  }
284
 
285
  /**performs disk housekeeping which involves deleting build directories > 5 days old
286
   * refer to the sequence diagram check environment
287
   */
288
  private void housekeep()
289
  {
290
    mLogger.debug("housekeep");
291
    FilenameFilter filter = new FilenameFilter()
292
    {
293
      public boolean accept(File file, String name)
294
      {
295
        mLogger.debug("accept " + name);
296
        boolean retVal = false;
297
 
298
        if ( file.isDirectory() && !name.startsWith( "." ) )
299
        {
300
          retVal = true;
301
        }
302
 
303
        mLogger.info("accept returned " + retVal);
304
        return retVal;
305
      }
306
    };
307
 
308
    try
309
    {
310
      File cwd = new File( "." );
311
 
312
      File[] children = cwd.listFiles( filter );
313
 
314
      if ( children != null )
315
      {
316
        for ( int child=0; child < children.length; child++ )
317
        {
318
          // child is named uniquely after an rtag_id
319
          File[] grandchildren = children[ child ].listFiles( filter );
320
 
321
          if ( grandchildren != null )
322
          {
323
            for ( int grandchild=0; grandchild < grandchildren.length; grandchild++ )
324
            {
325
              // child is named uniquely to encapsulate a build
326
              // 5 days = 432,000,000 milliseconds
327
              if ( ( System.currentTimeMillis() - grandchildren[ grandchild ].lastModified() ) > 432000000 )
328
              {
329
                // the directory is over 5 days old
330
                mLogger.warn("housekeep deleting directory " + grandchildren[ grandchild ].getName());
331
                if ( mGbebuildfilter.compareTo("unit test check environment") != 0 )
332
                {
333
                  deleteDirectory( grandchildren[ grandchild ] );
334
                }
335
              }
336
            }
337
          }
338
        }
339
      }
340
    }
341
    catch( SecurityException e )
342
    {
343
      // this can be thrown by lastModified
344
       mLogger.warn("housekeep caught SecurityException");
345
    }
346
 
347
  }
348
 
349
  /**returns true if free disk space > 10G
350
   * this may become configurable if the need arises
351
   * refer to the sequence diagram check environment
352
   */
353
  private boolean hasSufficientDiskSpace()
354
  {
355
    mLogger.debug("hasSufficientDiskSpace");
356
    boolean retVal = true;
357
    long freeSpace = 0;
358
 
359
    try
360
    {
361
      File cwd = new File( "." );
362
 
363
      // 5G = 5368709120 bytes
364
      if ( mGbebuildfilter.compareTo("unit test check environment") == 0 )
365
      {
366
        if ( mReleaseManager.mPersistedRunLevelCollection.size() == 0 )
367
        {
368
          retVal = false;
369
        }
370
        else
371
        {
372
          retVal = true;
373
        }
374
      }
375
      else
376
      {
377
        freeSpace = cwd.getFreeSpace();
378
 
379
        if ( freeSpace < 5368709120L )
380
        {
381
          retVal = false;
382
        }
383
      }
384
    }
385
    catch( SecurityException e )
386
    {
387
      // this can be thrown by getFreeSpace
388
       mLogger.warn("hasSufficientDiskSpace caught SecurityException");
389
    }
390
 
391
    mLogger.info("hasSufficientDiskSpace returned " + retVal + " " + freeSpace);
392
    return retVal;
393
  }
394
 
395
  /**abstract method
396
   */
397
  public abstract void run();
398
 
399
  /**deletes directory and all its files
400
   */
401
  protected void deleteDirectory(File directory)
402
  {
403
    mLogger.debug("deleteDirectory " + directory.getName());
404
    try
405
    {
406
      if ( directory.exists() )
407
      {
408
        FilenameFilter filter = new FilenameFilter()
409
        {
410
          public boolean accept(File file, String name)
411
          {
412
            mLogger.debug("accept " + name);
413
            boolean retVal = false;
414
 
415
            if ( !name.startsWith( "." ) )
416
            {
417
              retVal = true;
418
            }
419
 
420
            mLogger.info("accept returned " + retVal);
421
            return retVal;
422
          }
423
        };
424
 
425
        File[] children = directory.listFiles( filter );
426
 
427
        if ( children != null )
428
        {
429
          for ( int child=0; child < children.length; child++ )
430
          {
431
            if ( children[ child ].isDirectory() )
432
            {
433
              deleteDirectory( children[ child ] );
434
            }
435
            else
436
            {
437
              children[ child ].delete();
438
            }
439
          }
440
        }
441
        directory.delete();
442
      }
443
    }
444
    catch( SecurityException e )
445
    {
446
      // this can be thrown by exists and delete
447
       mLogger.warn("deleteDirectory caught SecurityException");
448
    }
449
  }
450
 
451
  /**abstract method
452
   */
453
  protected abstract char getMode();
454
 
455
  /**injects GBE_BUILDFILTER into the passed buildFileContent
456
   * builds a buildFile from the buildFileContent
457
   * triggers ant to operate on the buildFile
458
   */
459
  protected void deliverChange(String buildFileContent, String target, boolean master)
460
  {
461
    mLogger.debug("deliverChange");
462
    File buildFile = new File(mRtagId + "build.xml");
463
 
464
    try
465
    {
466
      // clear the file contents
467
      if ( buildFileContent != null && target != null && target.compareTo("AbtSetUp") == 0 )
468
      {
469
        FileOutputStream buildFileOutputStream = new FileOutputStream(buildFile, false);
470
        buildFileOutputStream.close();
471
 
472
        StringReader buildFileContentStringReader = new StringReader(buildFileContent);
473
        BufferedReader buildFileBufferedReader = new BufferedReader(buildFileContentStringReader);
474
 
475
        // sanitise the buildFileContent
476
        // it may contain line.separators of "\n", "\r", or "\r\n" variety, depending on the location of the ripple engine
477
        String sanitisedBFC = new String();
478
        String lf = new String( System.getProperty("line.separator") );
479
        int lineCount = 0;
480
        String line = new String();
481
 
482
        while( ( line = buildFileBufferedReader.readLine() ) != null)
483
        {
484
          sanitisedBFC += line + lf;
485
          lineCount++;
486
 
487
          if ( lineCount == 2 )
488
          {
489
            // have read the first two lines
490
            String inject = "<property name=\"MASTER\" value=\"";
491
 
492
            if ( master )
493
            {
494
              inject += "yes\"/>" + lf;
495
            }
496
            else
497
            {
498
              inject += "no\"/>" + lf;
499
            }
500
 
501
            // insert a GBE_BUILDFILTER property if necessary
502
            if ( mGbebuildfilter.length() > 0 )
503
            {
504
              inject += "<property name=\"GBE_BUILDFILTER\" value=\"" + mGbebuildfilter + "\"/>" + lf;
505
            }
506
 
507
            mLogger.info("deliverChange injecting " + inject);
508
            sanitisedBFC += inject;
509
          }
510
        }
511
        buildFileBufferedReader.close();
512
        FileWriter buildFileWriter = new FileWriter(buildFile);
513
        buildFileWriter.write(sanitisedBFC);
514
        buildFileWriter.close();
515
      }
516
 
517
      Project p = new Project();
518
      p.setProperty("ant.file", buildFile.getAbsolutePath());
519
      DefaultLogger dl = new DefaultLogger();
520
      PrintStream ps = new PrintStream(mRtagId + ".log");
521
      dl.setOutputPrintStream(ps);
522
      dl.setMessageOutputLevel(Project.MSG_INFO);
523
      p.addBuildListener(dl);
524
      p.init();
525
      ProjectHelper pH = ProjectHelper.getProjectHelper();
526
      p.addReference("ant.projectHelper", pH);
527
      pH.parse(p, buildFile);
528
      mLogger.warn("deliverChange ant launched on " + buildFile.getAbsolutePath());
529
 
530
      if ( target == null )
531
      {
532
        target = p.getDefaultTarget();
533
      }
534
      mLogger.warn("deliverChange ant launched against target " + target);
535
      p.executeTarget(target);
536
      mLogger.warn("deliverChange ant returned");
537
    }
538
    catch( BuildException e )
539
    {
540
      mLogger.debug("deliverChange caught BuildException, big deal, the build failed " + e.getMessage());
541
    }
542
    catch( FileNotFoundException e )
543
    {
544
      mLogger.error("deliverChange caught FileNotFoundException");
545
    }
546
    catch( IOException e )
547
    {
548
      mLogger.error("deliverChange caught IOException");
549
    }
550
 
551
  }
552
 
553
  /**sets up a ClearCase static view
554
   */
555
  protected void setViewUp(String content, boolean master)
556
  {
557
    mLogger.debug("setViewUp");
558
    // run ant on the AbtSetUp target
559
    deliverChange(content, "AbtSetUp", master);
560
  }
561
 
562
  /**tears down a ClearCase static view
563
  */
564
  protected void tearViewDown()
565
  {
566
    mLogger.debug("tearViewDown");
567
    // to do run ant on the AbtTearDown target
568
    deliverChange(null, "AbtTearDown", false);
569
  }
570
 
571
  /**Checks the archive for the <packageName>/<packageVersion>/built.<machtype> existence
572
   */
573
  protected boolean published(String archive, String packageName, 
574
                            String packageVersion, String machtype,
575
                            String generic)
576
  {
577
    mLogger.debug("published");
578
    boolean retVal = false;
579
 
580
    String fs = System.getProperty( "file.separator" );
581
    String destination = archive + fs + packageName + fs + packageVersion;
582
 
583
    mLogger.debug("published " + destination);
584
    String filename = "built.";
585
 
586
    if ( generic.compareTo("generic") == 0 )
587
    {
588
      filename += "generic";
589
    }
590
    else
591
    {
592
      filename += machtype;
593
    }
594
 
595
    mLogger.debug("published " + filename);
596
    File flag = new File( destination, filename );
597
 
598
    if ( flag.exists() )
599
    {
600
      retVal = true;
601
    }
602
    mLogger.debug("published returned " + retVal);
603
    return retVal;
604
  }
605
}