KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sourceforge > cruisecontrol > distributed > BuildAgentServiceImpl


1 /****************************************************************************
2 * CruiseControl, a Continuous Integration Toolkit
3 * Copyright (c) 2001, ThoughtWorks, Inc.
4 * 651 W Washington Ave. Suite 600
5 * Chicago, IL 60661 USA
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * + Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * + Redistributions in binary form must reproduce the above
16 * copyright notice, this list of conditions and the following
17 * disclaimer in the documentation and/or other materials provided
18 * with the distribution.
19 *
20 * + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
21 * names of its contributors may be used to endorse or promote
22 * products derived from this software without specific prior
23 * written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
29 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
30 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
31 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
32 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
33 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
34 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
35 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 ****************************************************************************/

37
38 package net.sourceforge.cruisecontrol.distributed;
39
40 import java.io.File JavaDoc;
41 import java.io.IOException JavaDoc;
42 import java.io.Serializable JavaDoc;
43 import java.net.InetAddress JavaDoc;
44 import java.net.UnknownHostException JavaDoc;
45 import java.net.URL JavaDoc;
46 import java.net.MalformedURLException JavaDoc;
47 import java.rmi.RemoteException JavaDoc;
48 import java.util.Map JavaDoc;
49 import java.util.Properties JavaDoc;
50 import java.util.Date JavaDoc;
51 import java.util.List JavaDoc;
52 import java.util.ArrayList JavaDoc;
53 import java.util.HashMap JavaDoc;
54
55 import net.sourceforge.cruisecontrol.Builder;
56 import net.sourceforge.cruisecontrol.CruiseControlException;
57 import net.sourceforge.cruisecontrol.PluginRegistry;
58 import net.sourceforge.cruisecontrol.PluginXMLHelper;
59 import net.sourceforge.cruisecontrol.distributed.util.PropertiesHelper;
60 import net.sourceforge.cruisecontrol.distributed.util.ZipUtil;
61 import net.sourceforge.cruisecontrol.util.FileUtil;
62 import net.sourceforge.cruisecontrol.util.Util;
63
64 import org.apache.log4j.Logger;
65 import org.jdom.Element;
66
67 import javax.jnlp.ServiceManager;
68 import javax.jnlp.BasicService;
69 import javax.jnlp.UnavailableServiceException;
70
71 /**
72  * Build Agent implementation.
73  */

74 public class BuildAgentServiceImpl implements BuildAgentService, Serializable JavaDoc {
75
76     private static final Logger LOG = Logger.getLogger(BuildAgentServiceImpl.class);
77
78     private static final String JavaDoc CRUISE_BUILD_DIR = "cruise.build.dir";
79
80     static final String JavaDoc DEFAULT_AGENT_PROPERTIES_FILE = "agent.properties";
81     private String JavaDoc agentPropertiesFilename = DEFAULT_AGENT_PROPERTIES_FILE;
82
83     static final String JavaDoc DEFAULT_USER_DEFINED_PROPERTIES_FILE = "user-defined.properties";
84
85     /** Cache host name. */
86     private final String JavaDoc machineName;
87     
88     private final Date JavaDoc dateStarted;
89     private boolean isBusy;
90     private Date JavaDoc dateClaimed;
91     private boolean isPendingKill;
92     private Date JavaDoc pendingKillSince;
93     private boolean isPendingRestart;
94     private Date JavaDoc pendingRestartSince;
95
96     private Properties JavaDoc configProperties;
97     private final Map JavaDoc distributedAgentProps = new HashMap JavaDoc();
98     private String JavaDoc logDir;
99     private String JavaDoc outputDir;
100     private String JavaDoc buildRootDir;
101     private String JavaDoc logsFilePath;
102     private String JavaDoc outputFilePath;
103
104     private final List JavaDoc agentStatusListeners = new ArrayList JavaDoc();
105
106     private final String JavaDoc logMsgPrefix;
107     /**
108      * Prepends Agent machine name to error message. This is especially
109      * useful when combined with an "email logger" config for Log4j using a modified
110      * log4j.properties on build agents. For example:
111      * <pre>
112      * log4j.rootCategory=INFO,A1,FILE,Mail
113      *
114      * ...
115      *
116      * # Mail is set to be a SMTPAppender
117      * log4j.appender.Mail=org.apache.log4j.net.SMTPAppender
118      * log4j.appender.Mail.BufferSize=100
119      * log4j.appender.Mail.From=ccbuild@yourdomain.com
120      * log4j.appender.Mail.SMTPHost=yoursmtp.mailhost.com
121      * log4j.appender.Mail.Subject=CC has had an error!!!
122      * log4j.appender.Mail.To=youremail@yourdomain.com
123      * log4j.appender.Mail.layout=org.apache.log4j.PatternLayout
124      * log4j.appender.Mail.layout.ConversionPattern=%d{dd.MM.yyyy HH:mm:ss} %-5p [%x] [%c{3}] %m%n
125      *
126      * </pre>
127      *
128      * @param message the message to log (will be prefixed with machineName).
129      */

130     private final void logPrefixDebug(final Object JavaDoc message) {
131         LOG.debug(logMsgPrefix + message);
132     }
133     private final void logPrefixInfo(final Object JavaDoc message) {
134         LOG.info(logMsgPrefix + message);
135     }
136     private final void logPrefixError(final Object JavaDoc message) {
137         LOG.error(logMsgPrefix + message);
138     }
139     private final void logPrefixError(final Object JavaDoc message, final Throwable JavaDoc throwable) {
140         LOG.error(logMsgPrefix + message, throwable);
141     }
142     
143     
144     /** Constructor. */
145     public BuildAgentServiceImpl() {
146         dateStarted = new Date JavaDoc();
147         
148         try {
149             machineName = InetAddress.getLocalHost().getHostName();
150         } catch (UnknownHostException JavaDoc e) {
151             final String JavaDoc message = "Failed to get hostname";
152             // Don't call Log helper method here since hostname is not yet set
153
LOG.error(message, e);
154             System.err.println(message + " - " + e.getMessage());
155             throw new RuntimeException JavaDoc(message, e);
156         }
157         logMsgPrefix = "Agent Host: " + machineName + "; ";
158     }
159
160     /** @return the date this Build Agent started running (not when a specific build started). */
161     public Date JavaDoc getDateStarted() {
162         return dateStarted;
163     }
164
165     /** @return the module being built now, or null if no module is being built. */
166     public synchronized String JavaDoc getModule() {
167         return (String JavaDoc) distributedAgentProps.get(PropertiesHelper.DISTRIBUTED_MODULE);
168     }
169
170     void setAgentPropertiesFilename(final String JavaDoc filename) {
171         agentPropertiesFilename = filename;
172     }
173     private String JavaDoc getAgentPropertiesFilename() {
174         return agentPropertiesFilename;
175     }
176
177     private final String JavaDoc busyLock = new String JavaDoc("busyLock");
178     void setBusy(final boolean newIsBusy) {
179         if (!newIsBusy) { // means the claim is being released
180
if (isPendingRestart()) {
181                 // restart now
182
doRestart();
183             } else if (isPendingKill()) {
184                 // kill now
185
doKill();
186             }
187
188             // clear out distributed build agent props
189
distributedAgentProps.clear();
190             
191             dateClaimed = null;
192         } else {
193             dateClaimed = new Date JavaDoc();
194         }
195
196         synchronized (busyLock) {
197             isBusy = newIsBusy;
198         }
199
200         fireAgentStatusChanged();
201
202         logPrefixInfo("agent busy status changed to: " + newIsBusy);
203     }
204
205     public Element doBuild(final Element nestedBuilderElement, final Map JavaDoc projectPropertiesMap,
206                            final Map JavaDoc distributedAgentProperties) throws RemoteException JavaDoc {
207         synchronized (busyLock) {
208             if (!isBusy()) { // only reclaim if needed, since it resets the dateClaimed.
209
setBusy(true); // we could remove this, since claim() is called during lookup...
210
}
211         }
212         try {
213             logPrefixDebug("Build Agent Props: " + distributedAgentProperties.toString());
214             distributedAgentProps.putAll(distributedAgentProperties);
215             
216             final String JavaDoc infoMessage = "Building module: " + getModule()
217                     + "\n\tAgentLogDir: " + distributedAgentProps.get(
218                             PropertiesHelper.DISTRIBUTED_AGENT_LOGDIR)
219                     + "\n\tAgentOutputDir: " + distributedAgentProps.get(
220                             PropertiesHelper.DISTRIBUTED_AGENT_OUTPUTDIR);
221
222             System.out.println();
223             System.out.println(infoMessage);
224             logPrefixInfo(infoMessage);
225
226             logPrefixDebug("Build Agent Project Props: " + projectPropertiesMap.toString());
227             
228             // this is done only to update agent UI info regarding Module - which isn't available
229
// until projectPropertiesMap has been set.
230
fireAgentStatusChanged();
231
232             final Element buildResults;
233             final Builder nestedBuilder;
234             try {
235                 nestedBuilder = createBuilder(nestedBuilderElement);
236                 nestedBuilder.validate();
237             } catch (CruiseControlException e) {
238                 final String JavaDoc message = "Failed to configure nested Builder on agent";
239                 logPrefixError(message, e);
240                 System.err.println(message + " - " + e.getMessage());
241                 throw new RemoteException JavaDoc(message, e);
242             }
243
244             try {
245                 buildResults = nestedBuilder.build(projectPropertiesMap);
246             } catch (CruiseControlException e) {
247                 final String JavaDoc message = "Failed to complete build on agent";
248                 logPrefixError(message, e);
249                 System.err.println(message + " - " + e.getMessage());
250                 throw new RemoteException JavaDoc(message, e);
251             }
252             prepareLogsAndArtifacts();
253             return buildResults;
254         } catch (RemoteException JavaDoc e) {
255             logPrefixError("doBuild threw exception, setting busy to false.");
256             setBusy(false);
257             throw e; // rethrow original exception
258
}
259     }
260
261     private Builder createBuilder(final Element builderElement) throws CruiseControlException {
262
263         configProperties = (Properties JavaDoc) PropertiesHelper.loadRequiredProperties(
264                 getAgentPropertiesFilename());
265
266         final String JavaDoc overrideTarget
267                 = (String JavaDoc) distributedAgentProps.get(PropertiesHelper.DISTRIBUTED_OVERRIDE_TARGET);
268         final PluginXMLHelper pluginXMLHelper = PropertiesHelper.createPluginXMLHelper(overrideTarget);
269
270         final PluginRegistry plugins = PluginRegistry.createRegistry();
271         final Class JavaDoc pluginClass = plugins.getPluginClass(builderElement.getName());
272         final Builder builder = (Builder) pluginXMLHelper.configure(builderElement, pluginClass, false);
273
274         return builder;
275     }
276
277     /**
278      * Zip any build artifacts found in the logDir and/or outputDir.
279      */

280     private void prepareLogsAndArtifacts() {
281         final String JavaDoc buildDirProperty = configProperties.getProperty(CRUISE_BUILD_DIR);
282         try {
283             buildRootDir = new File JavaDoc(buildDirProperty).getCanonicalPath();
284         } catch (IOException JavaDoc e) {
285             final String JavaDoc message = "Couldn't create " + buildDirProperty;
286             logPrefixError(message, e);
287             System.err.println(message + " - " + e.getMessage());
288             throw new RuntimeException JavaDoc(message);
289         }
290
291         logDir = getAgentResultDir(PropertiesHelper.RESULT_TYPE_LOGS,
292                 PropertiesHelper.DISTRIBUTED_AGENT_LOGDIR);
293
294         outputDir = getAgentResultDir(PropertiesHelper.RESULT_TYPE_OUTPUT,
295                 PropertiesHelper.DISTRIBUTED_AGENT_OUTPUTDIR);
296
297
298         logsFilePath = buildRootDir + File.separator + PropertiesHelper.RESULT_TYPE_LOGS + ".zip";
299         ZipUtil.zipFolderContents(logsFilePath, logDir);
300         outputFilePath = buildRootDir + File.separator + PropertiesHelper.RESULT_TYPE_OUTPUT + ".zip";
301         ZipUtil.zipFolderContents(outputFilePath, outputDir);
302     }
303
304     private String JavaDoc getAgentResultDir(final String JavaDoc resultType, final String JavaDoc resultProperty) {
305         String JavaDoc resultDir;
306         resultDir = (String JavaDoc) distributedAgentProps.get(resultProperty);
307         logPrefixDebug("Result: " + resultType + "Prop value: " + resultDir);
308
309         if (resultDir == null || "".equals(resultDir)) {
310             // use canonical behavior if attribute is not set
311
resultDir = buildRootDir + File.separator + resultType + File.separator + getModule();
312         }
313         new File JavaDoc(resultDir).mkdirs();
314         return resultDir;
315     }
316
317     public String JavaDoc getMachineName() {
318         return machineName;
319     }
320
321     public void claim() {
322         // flag this agent as busy for now. Intended to prevent mulitple builds on same agent,
323
// when multiple master threads find the same agent, before any build thread has started.
324
synchronized (busyLock) {
325             if (isBusy()) {
326                 throw new IllegalStateException JavaDoc("Cannot claim agent on " + getMachineName()
327                         + " that is busy building module: "
328                         + getModule());
329             }
330             setBusy(true);
331         }
332     }
333
334     public boolean isBusy() {
335         synchronized (busyLock) {
336             logPrefixDebug("Is busy called. value: " + isBusy);
337             return isBusy;
338         }
339     }
340
341     public Date JavaDoc getDateClaimed() {
342         return dateClaimed;
343     }
344
345     private void setPendingKill(final boolean isPendingKill) {
346         synchronized (busyLock) {
347             this.isPendingKill = isPendingKill;
348             pendingKillSince = new Date JavaDoc();
349         }
350     }
351     public boolean isPendingKill() {
352         synchronized (busyLock) {
353             return isPendingKill;
354         }
355     }
356     public Date JavaDoc getPendingKillSince() {
357         return pendingKillSince;
358     }
359
360
361     private void setPendingRestart(final boolean isPendingRestart) {
362         synchronized (busyLock) {
363             this.isPendingRestart = isPendingRestart;
364             pendingRestartSince = new Date JavaDoc();
365         }
366     }
367     public boolean isPendingRestart() {
368         synchronized (busyLock) {
369             return isPendingRestart;
370         }
371     }
372     public Date JavaDoc getPendingRestartSince() {
373         return pendingRestartSince;
374     }
375
376
377     public boolean resultsExist(final String JavaDoc resultsType) throws RemoteException JavaDoc {
378         if (resultsType.equals(PropertiesHelper.RESULT_TYPE_LOGS)) {
379             return !(new File JavaDoc(logDir).list().length == 0);
380         } else if (resultsType.equals(PropertiesHelper.RESULT_TYPE_OUTPUT)) {
381             return !(new File JavaDoc(outputDir).list().length == 0);
382         } else {
383             return false;
384         }
385     }
386
387     public byte[] retrieveResultsAsZip(final String JavaDoc resultsType) throws RemoteException JavaDoc {
388
389         final String JavaDoc zipFilePath = buildRootDir + File.separator + resultsType + ".zip";
390
391         final byte[] response;
392         try {
393             response = FileUtil.getFileAsBytes(new File JavaDoc(zipFilePath));
394         } catch (IOException JavaDoc e) {
395             final String JavaDoc message = "Unable to get file " + zipFilePath;
396             logPrefixError(message, e);
397             System.err.println(message + " - " + e.getMessage());
398             throw new RuntimeException JavaDoc(message, e);
399         }
400         return response;
401     }
402
403     public void clearOutputFiles() {
404         try {
405             if (logDir != null) {
406                 logPrefixDebug("Deleting contents of " + logDir);
407                 Util.deleteFile(new File JavaDoc(logDir));
408             } else {
409                 logPrefixDebug("Skip delete agent logDir: " + logDir);
410             }
411             if (logsFilePath != null) {
412                 logPrefixDebug("Deleting log zip " + logsFilePath);
413                 Util.deleteFile(new File JavaDoc(logsFilePath));
414             } else {
415                 logPrefixError("Skipping delete of log zip, file path is null.");
416             }
417
418             if (outputDir != null) {
419                 logPrefixDebug("Deleting contents of " + outputDir);
420                 Util.deleteFile(new File JavaDoc(outputDir));
421             } else {
422                 logPrefixDebug("Skip delete agent outputDir: " + outputDir);
423             }
424             if (outputFilePath != null) {
425                 logPrefixDebug("Deleting output zip " + outputFilePath);
426                 Util.deleteFile(new File JavaDoc(outputFilePath));
427             } else {
428                 logPrefixError("Skipping delete of output zip, file path is null.");
429             }
430             
431             setBusy(false);
432             
433         } catch (RuntimeException JavaDoc e) {
434             logPrefixError("Error cleaning agent build files.", e);
435             throw e;
436         }
437     }
438
439
440     private void doRestart() {
441         logPrefixInfo("Attempting agent restart.");
442
443         synchronized (busyLock) {
444             if (!isBusy()) {
445                 // claim agent so no new build can start
446
claim();
447             }
448         }
449
450         final BasicService basicService;
451         try {
452             basicService = (BasicService) ServiceManager.lookup(BasicService.class.getName());
453         } catch (UnavailableServiceException e) {
454             final String JavaDoc errMsg = "Couldn't find webstart Basic Service. Is Agent running outside of webstart?";
455             logPrefixError(errMsg, e);
456             throw new RuntimeException JavaDoc(errMsg, e);
457         }
458         final URL JavaDoc codeBaseURL = basicService.getCodeBase();
459         logPrefixInfo("basicService.getCodeBase()=" + codeBaseURL.toString());
460
461         // relaunch via new browser session
462
// @todo How to close the browser after jnlp is relaunched?
463
final URL JavaDoc relaunchURL;
464         try {
465             relaunchURL = new URL JavaDoc(codeBaseURL, "agent.jnlp");
466         } catch (MalformedURLException JavaDoc e) {
467             final String JavaDoc errMsg = "Error building webstart relaunch URL from " + codeBaseURL.toString();
468             logPrefixError(errMsg, e);
469             throw new RuntimeException JavaDoc(errMsg, e);
470         }
471         if (basicService.showDocument(relaunchURL)) {
472             logPrefixInfo("Relaunched agent via URL: " + relaunchURL.toString() + ". Will kill current agent now.");
473             doKill(); // don't wait for build finish, since we've already relaunched at this point.
474
} else {
475             final String JavaDoc errMsg = "Failed to relaunch agent via URL: " + relaunchURL.toString();
476             logPrefixError(errMsg);
477             throw new RuntimeException JavaDoc(errMsg);
478         }
479     }
480
481     private void doKill() {
482         logPrefixInfo("Attempting agent kill.");
483         synchronized (busyLock) {
484             if (!isBusy()) {
485                 // claim agent so no new build can start
486
claim();
487             }
488         }
489         BuildAgent.kill();
490     }
491
492     public void kill(final boolean afterBuildFinished) throws RemoteException JavaDoc {
493         setPendingKill(true);
494
495         if (!afterBuildFinished // Kill now, don't waiting for build to finish.
496
|| !isBusy()) { // Not busy, so kill now.
497

498             doKill(); // calls back to this agent to terminate lookup stuff
499
} else if (isBusy()) {
500             ; // do nothing. When claim is released, setBusy(false) will perform the kill
501
}
502         fireAgentStatusChanged();
503     }
504
505     public void restart(final boolean afterBuildFinished) throws RemoteException JavaDoc {
506         setPendingRestart(true);
507
508         if (!afterBuildFinished // Restart now, don't waiting for build to finish.
509
|| !isBusy()) { // Not busy, so Restart now.
510

511             doRestart();
512         } else if (isBusy()) {
513             ; // do nothing. When claim is released, setBusy(false) will perform the Restart
514
}
515         fireAgentStatusChanged();
516     }
517
518     public String JavaDoc asString() {
519         final StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
520         sb.append("Machine Name: ");
521         sb.append(getMachineName());
522         sb.append(";\t");
523         sb.append("Started: ");
524         sb.append(getDateStarted());
525
526         sb.append("\n\tBusy: ");
527         sb.append(isBusy());
528         sb.append(";\tSince: ");
529         sb.append(getDateClaimed());
530         sb.append(";\tModule: ");
531         sb.append(getModule());
532
533         sb.append("\n\tPending Restart: ");
534         sb.append(isPendingRestart());
535         sb.append(";\tPending Restart Since: ");
536         sb.append(getPendingRestartSince());
537
538         sb.append("\n\tPending Kill: ");
539         sb.append(isPendingKill());
540         sb.append(";\tPending Kill Since: ");
541         sb.append(getPendingKillSince());
542
543         return sb.toString();
544     }
545
546     public void addAgentStatusListener(final BuildAgent.AgentStatusListener listener) {
547         agentStatusListeners.add(listener);
548     }
549     public void removeAgentStatusListener(final BuildAgent.AgentStatusListener listener) {
550         agentStatusListeners.remove(listener);
551     }
552     private void fireAgentStatusChanged() {
553         for (int i = 0; i < agentStatusListeners.size(); i++) {
554             ((BuildAgent.AgentStatusListener) agentStatusListeners.get(i)).statusChanged(this);
555         }
556     }
557 }
558
Popular Tags