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