Java tutorial
/* * JBoss, Home of Professional Open Source. * Copyright 2012, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package builder.smartfrog; import hudson.Launcher; import hudson.Proc; import hudson.Util; import hudson.model.Action; import hudson.model.BuildListener; import hudson.model.StreamBuildListener; import hudson.model.AbstractBuild; import hudson.model.Run; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.Reader; import java.nio.charset.Charset; import java.util.Vector; import java.util.concurrent.TimeUnit; import java.util.zip.GZIPInputStream; import javax.servlet.ServletException; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.framework.io.LargeText; import org.kohsuke.stapler.interceptor.RequirePOST; import builder.smartfrog.util.ConsoleLogger; import builder.smartfrog.util.Functions; import builder.smartfrog.util.LineFilterOutputStream; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.GZIPOutputStream; import org.apache.commons.io.IOUtils; /** * * @author Dominik Pospisil * @author vjuranek */ public class SmartFrogAction implements Action, Runnable { private static final String NL = System.getProperty("line.separator"); private final String host; private State state; private AbstractBuild<?, ?> build; private String prefixId; private transient SmartFrogBuilder builder; private transient Proc proc; private transient Thread execThread; private transient Vector<SmartFrogActionListener> listeners = new Vector<SmartFrogActionListener>(); private transient Launcher launcher; private transient ConsoleLogger console; private transient BuildListener log; private transient String readableLogSize; public SmartFrogAction(SmartFrogBuilder builder, String host) { this.builder = builder; this.host = host; this.state = State.STARTING; this.prefixId = ""; } public SmartFrogAction(SmartFrogBuilder builder, String host, String prefixId) { this.builder = builder; this.host = host; this.state = State.STARTING; this.prefixId = prefixId; } protected Object readResolve() { if (prefixId == null) prefixId = ""; return this; } public String getHost() { return host; } public String getReadableLogSize() { if (readableLogSize == null) readableLogSize = hudson.Functions.humanReadableByteSize(getLogFile().length()); return readableLogSize; } public void perform(final AbstractBuild<?, ?> build, final Launcher launcher, final ConsoleLogger console) throws IOException, InterruptedException { this.build = build; this.launcher = launcher; this.console = console; String[] cl = builder.buildDaemonCommandLine(host, Functions.convertWsToCanonicalPath(build.getWorkspace())); logUpstream("[SmartFrog] INFO: Starting daemon on host " + host); logUpstream("[SmartFrog] INFO: Start command is " + Functions.cmdArrayToString(cl)); log = new StreamBuildListener(new PrintStream(new SFFilterOutputStream(new FileOutputStream(getLogFile()))), Charset.defaultCharset()); proc = launcher.launch().cmds(cl).envs(build.getEnvironment(log)).pwd(build.getWorkspace()).stdout(log) .start(); execThread = new Thread(this, "SFDaemon - " + host); execThread.start(); } /** * For testing purposes * @param build */ protected void setBuild(AbstractBuild<?, ?> build) { this.build = build; } public void run() { // wait for process to finish int status = 1; //by default fail try { if (proc.isAlive()) status = proc.join(); } catch (IOException ex) { status = 1; setState(State.FAILED); } catch (InterruptedException ex) { status = 1; setState(State.FAILED); } finally { //TODO reliable kill here - JBQA 2006 log.getLogger().close(); } if (status != 0) { logUpstream("[SmartFrog] INFO: Daemon on host " + host + " failed"); setState(State.FAILED); return; } logUpstream("[SmartFrog] INFO: Daemon on host " + host + " finished"); setState(State.FINISHED); } public void interrupt() { String[] cl = builder.buildStopDaemonCommandLine(host); logUpstream("[SmartFrog] INFO: Trying to interrupt daemon on host " + host); logUpstream("[SmartFrog] INFO: Interrupt command is " + Functions.cmdArrayToString(cl)); try { //TODO possible concurrent writing into log (from interrupt() as well as from run())!! (however synchronization could lead to livelock) Proc killProc = launcher.launch().cmds(cl).envs(build.getEnvironment(log)).pwd(build.getWorkspace()) .stdout(log).start(); int exitCode = killProc.joinWithTimeout(5, TimeUnit.MINUTES, console.getListener()); if (exitCode != 0) { // something went wrong, let's try hard kill, also with timeout as whole machine can be unresponsive // TODO replace with something more sophisticated/universal than bash script String[] cmd = builder.buildKilleThemAllCommandLine(host); Proc killThemAll = launcher.launch().cmds(cmd).envs(build.getEnvironment(log)) .pwd(build.getWorkspace()).stdout(log).start(); // still can take some time if e.g. server is under heavy load due to deadlock or something like this, so let wait few minutes too exitCode = killProc.joinWithTimeout(3, TimeUnit.MINUTES, console.getListener()); if (exitCode != 0 && killThemAll.isAlive()) { logUpstream( "[SmartFrog] ERROR: Hard kill timeout, giving up. Machine is probably unresponsive, plase check it manually."); } } } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } public State getState() { return state; } private void setState(State s) { if (this.getState() == s) return; this.state = s; logUpstream("[SmartFrog] INFO: Deamon on host " + host + " has changed state to " + state.toString()); for (SmartFrogActionListener l : listeners) l.stateChanged(); } public void addStateListener(SmartFrogActionListener l) { listeners.add(l); } public void doProgressiveLog(StaplerRequest req, StaplerResponse rsp) throws IOException { new LargeText(getLogFile(), !isBuilding()).doProgressText(req, rsp); } public File getLogFile() { String logFileName = host; if (!prefixId.isEmpty()) logFileName = prefixId + "_" + logFileName; File gzipLogFile = new File(build.getRootDir(), logFileName + ".log.gz"); if (gzipLogFile.isFile()) return gzipLogFile; return new File(build.getRootDir(), logFileName + ".log"); } private void logUpstream(String message) { console.logAnnot(message); //console.log(message); } public boolean isBuilding() { return (state != State.FAILED) && (state != State.FINISHED); } public String getIconFileName() { return "/plugin/smartfrog-plugin/icons/smartfrog24.png"; } public String getDisplayName() { if (prefixId.isEmpty()) return "sfDaemon - " + host; return "sfDaemon - " + host + " (" + prefixId + ")"; } public String getUrlName() { if (prefixId.isEmpty()) return "console-" + Util.rawEncode(host); return prefixId + "-console-" + Util.rawEncode(host); } // required by index.jelly public AbstractBuild<?, ?> getOwnerBuild() { return build; } // required by consoleText.jelly public Reader getLogReader() throws IOException { return new InputStreamReader(getLogInputStream()); } public InputStream getLogInputStream() throws IOException { File logFile = getLogFile(); if (logFile != null && logFile.exists()) { FileInputStream fis = new FileInputStream(logFile); if (logFile.getName().endsWith(".gz")) { return new GZIPInputStream(fis); } else { return fis; } } String message = "No such file: " + logFile; return new ByteArrayInputStream(message.getBytes()); } @RequirePOST public void doDoDelete(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException { build.checkPermission(Run.DELETE); File log = getLogFile(); if (log != null) if (log.delete()) { build.getActions().remove(this); build.save(); } rsp.sendRedirect2(req.getContextPath() + '/' + build.getUrl()); } public void compressLog() { File logFile = getLogFile(); File compressedLog = new File(logFile.getParentFile(), logFile.getName() + ".gz"); GZIPOutputStream gzos = null; FileInputStream ist = null; try { gzos = new GZIPOutputStream(new FileOutputStream(compressedLog)); ist = new FileInputStream(logFile); IOUtils.copy(ist, gzos); gzos.finish(); } catch (IOException e) { Logger.getLogger(SmartFrogBuildListener.class.getName()).log(Level.WARNING, "was not able to compress log file " + logFile.getAbsolutePath(), e); if (ist != null) IOUtils.closeQuietly(ist); if (gzos != null) IOUtils.closeQuietly(gzos); return; } logFile.delete(); } private class SFFilterOutputStream extends LineFilterOutputStream { private OutputStreamWriter os; public SFFilterOutputStream(OutputStream out) { super(out); os = new OutputStreamWriter(out); } protected void writeLine(String line) { if (line.startsWith("SmartFrog ready")) setState(State.RUNNING); int idx = line.indexOf("[TerminateHook]"); if (idx > -1) { String compName = line.substring(line.indexOf('[', idx + 15) + 1); compName = compName.substring(0, compName.indexOf(']')); if (compName.endsWith(builder.getSfScriptSource().getScriptName())) { //TODO keep this info locally? builder.componentTerminated(!line.contains("ABNORMAL")); } } try { os.write(line); os.write(NL); os.flush(); } catch (IOException ioe) { } } } public enum State { STARTING, RUNNING, FINISHED, FAILED }; }