1 package hudson.model; 2 3 import hudson.CloseProofOutputStream; 4 import hudson.FilePath; 5 import hudson.Launcher; 6 import hudson.Launcher.LocalLauncher; 7 import hudson.Proc; 8 import hudson.Proc.RemoteProc; 9 import hudson.Util; 10 import hudson.maven.agent.Main; 11 import hudson.maven.agent.PluginManagerInterceptor; 12 import hudson.model.Descriptor.FormException; 13 import hudson.remoting.Callable; 14 import hudson.remoting.Channel; 15 import hudson.remoting.Channel.Listener; 16 import hudson.remoting.RemoteInputStream; 17 import hudson.remoting.RemoteOutputStream; 18 import hudson.remoting.VirtualChannel; 19 import hudson.remoting.Pipe; 20 import hudson.remoting.Which; 21 import hudson.util.NullStream; 22 import hudson.util.StreamCopyThread; 23 import hudson.util.StreamTaskListener; 24 import org.kohsuke.stapler.StaplerRequest; 25 import org.kohsuke.stapler.StaplerResponse; 26 27 import javax.servlet.ServletException ; 28 import javax.servlet.http.HttpServletResponse ; 29 import java.io.File ; 30 import java.io.FileNotFoundException ; 31 import java.io.FileOutputStream ; 32 import java.io.IOException ; 33 import java.io.InputStream ; 34 import java.io.OutputStream ; 35 import java.io.Serializable ; 36 import java.io.PrintWriter ; 37 import java.net.URL ; 38 import java.net.URLConnection ; 39 import java.util.logging.Level ; 40 import java.util.logging.Logger ; 41 42 47 public final class Slave implements Node, Serializable { 48 51 protected final String name; 52 53 56 private final String description; 57 58 62 protected final String remoteFS; 63 64 67 private int numExecutors = 2; 68 69 72 private Mode mode; 73 74 78 private String agentCommand; 79 80 83 public Slave(String name, String description, String command, String remoteFS, int numExecutors, Mode mode) throws FormException { 84 this.name = name; 85 this.description = description; 86 this.numExecutors = numExecutors; 87 this.mode = mode; 88 this.agentCommand = command; 89 this.remoteFS = remoteFS; 90 91 if (name.equals("")) 92 throw new FormException("Invalid slave configuration. PluginName is empty", null); 93 94 if (remoteFS.equals("")) 100 throw new FormException("Invalid slave configuration for " + name + ". No remote directory given", null); 101 } 102 103 public String getCommand() { 104 return agentCommand; 105 } 106 107 public String getRemoteFS() { 108 return remoteFS; 109 } 110 111 public String getNodeName() { 112 return name; 113 } 114 115 public String getNodeDescription() { 116 return description; 117 } 118 119 122 public FilePath getFilePath() { 123 return new FilePath(getComputer().getChannel(),remoteFS); 124 } 125 126 public int getNumExecutors() { 127 return numExecutors; 128 } 129 130 public Mode getMode() { 131 return mode; 132 } 133 134 142 public long getClockDifference() throws IOException { 143 VirtualChannel channel = getComputer().getChannel(); 144 if(channel==null) return 0; 146 try { 147 long startTime = System.currentTimeMillis(); 148 long slaveTime = channel.call(new Callable<Long ,RuntimeException >() { 149 public Long call() { 150 return System.currentTimeMillis(); 151 } 152 }); 153 long endTime = System.currentTimeMillis(); 154 155 return (startTime+endTime)/2 - slaveTime; 156 } catch (InterruptedException e) { 157 return 0; } 159 } 160 161 162 165 public String getClockDifferenceString() { 166 try { 167 long diff = getClockDifference(); 168 if(-1000<diff && diff <1000) 169 return "In sync"; 171 long abs = Math.abs(diff); 172 173 String s = Util.getTimeSpanString(abs); 174 if(diff<0) 175 s += " ahead"; 176 else 177 s += " behind"; 178 179 if(abs>100*60) s = "<span class='error'>"+s+"</span>"; 181 182 return s; 183 } catch (IOException e) { 184 return "<span class='error'>Unable to check</span>"; 185 } 186 } 187 188 public Computer createComputer() { 189 return new ComputerImpl(this); 190 } 191 192 public FilePath getWorkspaceFor(TopLevelItem item) { 193 return getWorkspaceRoot().child(item.getName()); 194 } 195 196 199 public FilePath getWorkspaceRoot() { 200 return getFilePath().child("workspace"); 201 } 202 203 public static final class ComputerImpl extends Computer { 204 private volatile Channel channel; 205 206 209 private File getLogFile() { 210 return new File (Hudson.getInstance().getRootDir(),"slave-"+nodeName+".log"); 211 } 212 213 private ComputerImpl(Slave slave) { 214 super(slave); 215 } 216 217 public Slave getNode() { 218 return (Slave)super.getNode(); 219 } 220 221 @Override 222 public boolean isJnlpAgent() { 223 return getNode().getCommand().length()==0; 224 } 225 226 229 private void launch(final Slave slave) { 230 closeChannel(); 231 232 OutputStream os; 233 try { 234 os = new FileOutputStream (getLogFile()); 235 } catch (FileNotFoundException e) { 236 logger.log(Level.SEVERE, "Failed to create log file "+getLogFile(),e); 237 os = new NullStream(); 238 } 239 final OutputStream launchLog = os; 240 241 if(slave.agentCommand.length()>0) { 242 threadPoolForRemoting.execute(new Runnable () { 244 public void run() { 247 final StreamTaskListener listener = new StreamTaskListener(launchLog); 248 try { 249 listener.getLogger().println("Launching slave agent"); 250 listener.getLogger().println("$ "+slave.agentCommand); 251 final Process proc = Runtime.getRuntime().exec(slave.agentCommand); 252 253 new StreamCopyThread("stderr copier for remote agent on "+slave.getNodeName(), 256 proc.getErrorStream(), launchLog).start(); 257 258 setChannel(proc.getInputStream(),proc.getOutputStream(),launchLog,new Listener() { 259 public void onClosed(Channel channel, IOException cause) { 260 if(cause!=null) 261 cause.printStackTrace(listener.error("slave agent was terminated")); 262 proc.destroy(); 263 } 264 }); 265 266 logger.info("slave agent launched for "+slave.getNodeName()); 267 268 } catch (InterruptedException e) { 269 e.printStackTrace(listener.error("aborted")); 270 } catch (IOException e) { 271 Util.displayIOException(e,listener); 272 273 String msg = Util.getWin32ErrorMessage(e); 274 if(msg==null) msg=""; 275 else msg=" : "+msg; 276 msg = "Unable to launch the slave agent for " + slave.getNodeName() + msg; 277 logger.log(Level.SEVERE,msg,e); 278 e.printStackTrace(listener.error(msg)); 279 } 280 } 281 }); 282 } 283 } 284 285 private final Object channelLock = new Object (); 286 287 290 public void setChannel(InputStream in, OutputStream out, OutputStream launchLog, Listener listener) throws IOException , InterruptedException { 291 synchronized(channelLock) { 292 if(this.channel!=null) 293 throw new IllegalStateException ("Already connected"); 294 295 Channel channel = new Channel(nodeName,threadPoolForRemoting, 296 in,out, launchLog); 297 channel.addListener(new Listener() { 298 public void onClosed(Channel c,IOException cause) { 299 ComputerImpl.this.channel = null; 300 } 301 }); 302 channel.addListener(listener); 303 304 { PrintWriter log = new PrintWriter (launchLog,true); 307 FilePath dst = new FilePath(channel,getNode().getRemoteFS()); 308 new FilePath(Which.jarFile(Main.class)).copyTo(dst.child("maven-agent.jar")); 309 log.println("Copied maven-agent.jar"); 310 new FilePath(Which.jarFile(PluginManagerInterceptor.class)).copyTo(dst.child("maven-interceptor.jar")); 311 log.println("Copied maven-interceptor.jar"); 312 } 313 314 this.channel = channel; 316 } 317 Hudson.getInstance().getQueue().scheduleMaintenance(); 318 } 319 320 @Override 321 public VirtualChannel getChannel() { 322 return channel; 323 } 324 325 public void doLaunchSlaveAgent(StaplerRequest req, StaplerResponse rsp) throws IOException , ServletException { 326 if(channel!=null) { 327 rsp.sendError(HttpServletResponse.SC_NOT_FOUND); 328 return; 329 } 330 331 launch(getNode()); 332 333 rsp.sendRedirect("log"); 336 } 337 338 341 public String getLog() throws IOException { 342 return Util.loadFile(getLogFile()); 343 } 344 345 348 public void doProgressiveLog( StaplerRequest req, StaplerResponse rsp) throws IOException { 349 new LargeText(getLogFile(),false).doProgressText(req,rsp); 350 } 351 352 355 public JnlpJar getJnlpJars(String fileName) { 356 return new JnlpJar(fileName); 357 } 358 359 @Override 360 protected void kill() { 361 super.kill(); 362 closeChannel(); 363 } 364 365 private void closeChannel() { 366 Channel c = channel; 367 channel = null; 368 if(c!=null) 369 try { 370 c.close(); 371 } catch (IOException e) { 372 logger.log(Level.SEVERE, "Failed to terminate channel to "+getDisplayName(),e); 373 } 374 } 375 376 @Override 377 protected void setNode(Node node) { 378 super.setNode(node); 379 if(channel==null) 380 launch((Slave)node); 382 } 383 384 private static final Logger logger = Logger.getLogger(ComputerImpl.class.getName()); 385 } 386 387 390 public static final class JnlpJar { 391 private final String fileName; 392 393 public JnlpJar(String fileName) { 394 this.fileName = fileName; 395 } 396 397 public void doIndex( StaplerRequest req, StaplerResponse rsp) throws IOException , ServletException { 398 URL res = req.getServletContext().getResource("/WEB-INF/" + fileName); 399 if(res==null) { 400 res = new URL (new File (".").getAbsoluteFile().toURL(),"target/generated-resources/WEB-INF/"+fileName); 402 } 403 404 URLConnection con = res.openConnection(); 405 InputStream in = con.getInputStream(); 406 rsp.serveFile(req, in, con.getLastModified(), con.getContentLength(), "*.jar" ); 407 in.close(); 408 } 409 410 } 411 412 public Launcher createLauncher(TaskListener listener) { 413 return new Launcher(listener, getComputer().getChannel()) { 414 public Proc launch(final String [] cmd, final String [] env, InputStream _in, OutputStream _out, FilePath _workDir) throws IOException { 415 printCommandLine(cmd,_workDir); 416 417 final OutputStream out = new RemoteOutputStream(new CloseProofOutputStream(_out)); 418 final InputStream in = _in==null ? null : new RemoteInputStream(_in); 419 final String workDir = _workDir==null ? null : _workDir.getRemote(); 420 421 return new RemoteProc(getChannel().callAsync(new RemoteLaunchCallable(cmd, env, in, out, workDir))); 422 } 423 424 public Channel launchChannel(String [] cmd, OutputStream err, FilePath _workDir) throws IOException , InterruptedException { 425 printCommandLine(cmd, _workDir); 426 427 Pipe out = Pipe.createRemoteToLocal(); 428 final String workDir = _workDir==null ? null : _workDir.getRemote(); 429 430 OutputStream os = getChannel().call(new RemoteChannelLaunchCallable(cmd, out, err, workDir)); 431 432 return new Channel("remotely launched channel on "+getNodeName(), 433 Computer.threadPoolForRemoting, out.getIn(), os); 434 } 435 436 @Override 437 public boolean isUnix() { 438 return remoteFS.indexOf("\\")==-1; 441 } 442 }; 443 } 444 445 448 public Computer getComputer() { 449 return Hudson.getInstance().getComputer(getNodeName()); 450 } 451 452 public boolean equals(Object o) { 453 if (this == o) return true; 454 if (o == null || getClass() != o.getClass()) return false; 455 456 final Slave that = (Slave) o; 457 458 return name.equals(that.name); 459 } 460 461 public int hashCode() { 462 return name.hashCode(); 463 } 464 465 468 private Object readResolve() { 469 if(command!=null && agentCommand==null) { 471 if(command.length()>0) command += ' '; 472 agentCommand = command+"java -jar ~/bin/slave.jar"; 473 } 474 return this; 475 } 476 477 487 private File localFS; 488 489 495 private transient String command; 496 497 private static class RemoteLaunchCallable implements Callable<Integer ,IOException > { 498 private final String [] cmd; 499 private final String [] env; 500 private final InputStream in; 501 private final OutputStream out; 502 private final String workDir; 503 504 public RemoteLaunchCallable(String [] cmd, String [] env, InputStream in, OutputStream out, String workDir) { 505 this.cmd = cmd; 506 this.env = env; 507 this.in = in; 508 this.out = out; 509 this.workDir = workDir; 510 } 511 512 public Integer call() throws IOException { 513 Proc p = new LocalLauncher(TaskListener.NULL).launch(cmd, env, in, out, 514 workDir ==null ? null : new FilePath(new File (workDir))); 515 return p.join(); 516 } 517 518 private static final long serialVersionUID = 1L; 519 } 520 521 private static class RemoteChannelLaunchCallable implements Callable<OutputStream,IOException > { 522 private final String [] cmd; 523 private final Pipe out; 524 private final String workDir; 525 private final OutputStream err; 526 527 public RemoteChannelLaunchCallable(String [] cmd, Pipe out, OutputStream err, String workDir) { 528 this.cmd = cmd; 529 this.out = out; 530 this.err = new RemoteOutputStream(err); 531 this.workDir = workDir; 532 } 533 534 public OutputStream call() throws IOException { 535 Process p = Runtime.getRuntime().exec(cmd, null, workDir == null ? null : new File (workDir)); 536 537 new StreamCopyThread("stdin copier for remote agent on "+cmd, 538 p.getInputStream(), out.getOut()).start(); 539 new StreamCopyThread("stderr copier for remote agent on "+cmd, 540 p.getErrorStream(), err).start(); 541 542 544 return new RemoteOutputStream(p.getOutputStream()); 545 } 546 547 private static final long serialVersionUID = 1L; 548 } 549 } 550 | Popular Tags |