File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\slaves\SlaveComputer.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Stephen Connolly
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson.slaves;
25
26 import hudson.model.*;
27 import hudson.util.IOException2;
28 import hudson.util.IOUtils;
29 import hudson.util.io.ReopenableRotatingFileOutputStream;
30 import jenkins.model.Jenkins.MasterComputer;
31 import hudson.remoting.Channel;
32 import hudson.remoting.VirtualChannel;
33 import hudson.remoting.Callable;
34 import hudson.util.StreamTaskListener;
35 import hudson.util.NullStream;
36 import hudson.util.RingBufferLogHandler;
37 import hudson.util.Futures;
38 import hudson.FilePath;
39 import hudson.Util;
40 import hudson.AbortException;
41 import hudson.remoting.Launcher;
42 import static hudson.slaves.SlaveComputer.LogHolder.SLAVE_LOG_HANDLER;
43 import hudson.slaves.OfflineCause.ChannelTermination;
44 import hudson.util.Secret;
45
46 import java.io.File;
47 import java.io.OutputStream;
48 import java.io.InputStream;
49 import java.io.IOException;
50 import java.io.PrintStream;
51 import java.security.SecureRandom;
52 import java.util.logging.Level;
53 import java.util.logging.LogRecord;
54 import java.util.logging.Logger;
55 import java.util.logging.Handler;
56 import java.util.List;
57 import java.util.Collections;
58 import java.util.ArrayList;
59 import java.nio.charset.Charset;
60 import java.util.concurrent.Future;
61 import java.security.Security;
62
63 import hudson.util.io.ReopenableFileOutputStream;
64 import java.io.ByteArrayOutputStream;
65 import java.io.PrintWriter;
66 import java.security.GeneralSecurityException;
67 import javax.crypto.Cipher;
68 import javax.crypto.SecretKey;
69 import javax.crypto.spec.IvParameterSpec;
70 import javax.crypto.spec.SecretKeySpec;
71 import javax.servlet.RequestDispatcher;
72 import jenkins.model.Jenkins;
73 import jenkins.slaves.JnlpSlaveAgentProtocol;
74 import org.kohsuke.stapler.StaplerRequest;
75 import org.kohsuke.stapler.StaplerResponse;
76 import org.kohsuke.stapler.QueryParameter;
77 import org.kohsuke.stapler.HttpResponse;
78 import org.kohsuke.stapler.HttpRedirect;
79
80 import javax.servlet.ServletException;
81 import javax.servlet.ServletOutputStream;
82 import javax.servlet.http.HttpServletResponseWrapper;
83 import org.kohsuke.stapler.ResponseImpl;
84 import org.kohsuke.stapler.WebMethod;
85 import org.kohsuke.stapler.compression.FilterServletOutputStream;
86
87 /**
88 * {@link Computer} for {@link Slave}s.
89 *
90 * @author Kohsuke Kawaguchi
91 */
92 public class SlaveComputer extends Computer {
93 private volatile Channel channel;
94 private volatile transient boolean acceptingTasks = true;
95 private Charset defaultCharset;
96 private Boolean isUnix;
97 /**
98 * Effective {@link ComputerLauncher} that hides the details of
99 * how we launch a slave agent on this computer.
100 *
101 * <p>
102 * This is normally the same as {@link Slave#getLauncher()} but
103 * can be different. See {@link #grabLauncher(Node)}.
104 */
105 private ComputerLauncher launcher;
106
107 /**
108 * Perpetually writable log file.
109 */
110 private final ReopenableFileOutputStream log;
111
112 /**
113 * {@link StreamTaskListener} that wraps {@link #log}, hence perpetually writable.
114 */
115 private final TaskListener taskListener;
116
117
118 /**
119 * Number of failed attempts to reconnect to this node
120 * (so that if we keep failing to reconnect, we can stop
121 * trying.)
122 */
123 private transient int numRetryAttempt;
124
125 /**
126 * Tracks the status of the last launch operation, which is always asynchronous.
127 * This can be used to wait for the completion, or cancel the launch activity.
128 */
129 private volatile Future<?> lastConnectActivity = null;
130
131 private Object constructed = new Object();
132
133 public SlaveComputer(Slave slave) {
134 super(slave);
135 this.log = new ReopenableRotatingFileOutputStream(getLogFile(),10);
136 this.taskListener = new StreamTaskListener(log);
137 }
138
139 /**
140 * {@inheritDoc}
141 */
142 @Override
143 public boolean isAcceptingTasks() {
144 return acceptingTasks;
145 }
146
147 /**
148 * @since 1.498
149 */
150 public String getJnlpMac() {
151 return JnlpSlaveAgentProtocol.SLAVE_SECRET.mac(getName());
152 }
153
154 /**
155 * Allows a {@linkplain hudson.slaves.ComputerLauncher} or a {@linkplain hudson.slaves.RetentionStrategy} to
156 * suspend tasks being accepted by the slave computer.
157 *
158 * @param acceptingTasks {@code true} if the slave can accept tasks.
159 */
160 public void setAcceptingTasks(boolean acceptingTasks) {
161 this.acceptingTasks = acceptingTasks;
162 }
163
164 /**
165 * True if this computer is a Unix machine (as opposed to Windows machine).
166 *
167 * @return
168 * null if the computer is disconnected and therefore we don't know whether it is Unix or not.
169 */
170 public Boolean isUnix() {
171 return isUnix;
172 }
173
174 @Override
175 public Slave getNode() {
176 return (Slave)super.getNode();
177 }
178
179 @Override
180 public String getIcon() {
181 Future<?> l = lastConnectActivity;
182 if(l!=null && !l.isDone())
183 return "computer-flash.gif";
184 return super.getIcon();
185 }
186
187 /**
188 * @deprecated since 2008-05-20.
189 */
190 @Deprecated @Override
191 public boolean isJnlpAgent() {
192 return launcher instanceof JNLPLauncher;
193 }
194
195 @Override
196 public boolean isLaunchSupported() {
197 return launcher.isLaunchSupported();
198 }
199
200 public ComputerLauncher getLauncher() {
201 return launcher;
202 }
203
204 protected Future<?> _connect(boolean forceReconnect) {
205 if(channel!=null) return Futures.precomputed(null);
206 if(!forceReconnect && isConnecting())
207 return lastConnectActivity;
208 if(forceReconnect && isConnecting())
209 logger.fine("Forcing a reconnect on "+getName());
210
211 closeChannel();
212 return lastConnectActivity = Computer.threadPoolForRemoting.submit(new java.util.concurrent.Callable<Object>() {
213 public Object call() throws Exception {
214 // do this on another thread so that the lengthy launch operation
215 // (which is typical) won't block UI thread.
216 try {
217 log.rewind();
218 try {
219 for (ComputerListener cl : ComputerListener.all())
220 cl.preLaunch(SlaveComputer.this, taskListener);
221
222 launcher.launch(SlaveComputer.this, taskListener);
223 } catch (AbortException e) {
224 taskListener.error(e.getMessage());
225 throw e;
226 } catch (IOException e) {
227 Util.displayIOException(e,taskListener);
228 e.printStackTrace(taskListener.error(Messages.ComputerLauncher_unexpectedError()));
229 throw e;
230 } catch (InterruptedException e) {
231 e.printStackTrace(taskListener.error(Messages.ComputerLauncher_abortedLaunch()));
232 throw e;
233 }
234 } finally {
235 if (channel==null) {
236 offlineCause = new OfflineCause.LaunchFailed();
237 for (ComputerListener cl : ComputerListener.all())
238 cl.onLaunchFailure(SlaveComputer.this, taskListener);
239 }
240 }
241
242 if (channel==null)
243 throw new IOException("Slave failed to connect, even though the launcher didn't report it. See the log output for details.");
244 return null;
245 }
246 });
247 }
248
249 /**
250 * {@inheritDoc}
251 */
252 @Override
253 public void taskAccepted(Executor executor, Queue.Task task) {
254 super.taskAccepted(executor, task);
255 if (launcher instanceof ExecutorListener) {
256 ((ExecutorListener)launcher).taskAccepted(executor, task);
257 }
258
259 //getNode() can return null at indeterminate times when nodes go offline
260 Slave node = getNode();
261 if (node != null && node.getRetentionStrategy() instanceof ExecutorListener) {
262 ((ExecutorListener)node.getRetentionStrategy()).taskAccepted(executor, task);
263 }
264 }
265
266 /**
267 * {@inheritDoc}
268 */
269 @Override
270 public void taskCompleted(Executor executor, Queue.Task task, long durationMS) {
271 super.taskCompleted(executor, task, durationMS);
272 if (launcher instanceof ExecutorListener) {
273 ((ExecutorListener)launcher).taskCompleted(executor, task, durationMS);
274 }
275 RetentionStrategy r = getRetentionStrategy();
276 if (r instanceof ExecutorListener) {
277 ((ExecutorListener) r).taskCompleted(executor, task, durationMS);
278 }
279 }
280
281 /**
282 * {@inheritDoc}
283 */
284 @Override
285 public void taskCompletedWithProblems(Executor executor, Queue.Task task, long durationMS, Throwable problems) {
286 super.taskCompletedWithProblems(executor, task, durationMS, problems);
287 if (launcher instanceof ExecutorListener) {
288 ((ExecutorListener)launcher).taskCompletedWithProblems(executor, task, durationMS, problems);
289 }
290 RetentionStrategy r = getRetentionStrategy();
291 if (r instanceof ExecutorListener) {
292 ((ExecutorListener) r).taskCompletedWithProblems(executor, task, durationMS, problems);
293 }
294 }
295
296 @Override
297 public boolean isConnecting() {
298 Future<?> l = lastConnectActivity;
299 return isOffline() && l!=null && !l.isDone();
300 }
301
302 public OutputStream openLogFile() {
303 try {
304 log.rewind();
305 return log;
306 } catch (IOException e) {
307 logger.log(Level.SEVERE, "Failed to create log file "+getLogFile(),e);
308 return new NullStream();
309 }
310 }
311
312 private final Object channelLock = new Object();
313
314 public void setChannel(InputStream in, OutputStream out, TaskListener taskListener, Channel.Listener listener) throws IOException, InterruptedException {
315 setChannel(in,out,taskListener.getLogger(),listener);
316 }
317
318 /**
319 * Creates a {@link Channel} from the given stream and sets that to this slave.
320 *
321 * @param in
322 * Stream connected to the remote "slave.jar". It's the caller's responsibility to do
323 * buffering on this stream, if that's necessary.
324 * @param out
325 * Stream connected to the remote peer. It's the caller's responsibility to do
326 * buffering on this stream, if that's necessary.
327 * @param launchLog
328 * If non-null, receive the portion of data in <tt>is</tt> before
329 * the data goes into the "binary mode". This is useful
330 * when the established communication channel might include some data that might
331 * be useful for debugging/trouble-shooting.
332 * @param listener
333 * Gets a notification when the channel closes, to perform clean up. Can be null.
334 * By the time this method is called, the cause of the termination is reported to the user,
335 * so the implementation of the listener doesn't need to do that again.
336 */
337 public void setChannel(InputStream in, OutputStream out, OutputStream launchLog, Channel.Listener listener) throws IOException, InterruptedException {
338 Channel channel = new Channel(nodeName,threadPoolForRemoting, Channel.Mode.NEGOTIATE, in,out, launchLog);
339 setChannel(channel,launchLog,listener);
340 }
341
342 /**
343 * Shows {@link Channel#classLoadingCount}.
344 * @since 1.495
345 */
346 public int getClassLoadingCount() throws IOException, InterruptedException {
347 return channel.call(new LoadingCount(false));
348 }
349
350 /**
351 * Shows {@link Channel#resourceLoadingCount}.
352 * @since 1.495
353 */
354 public int getResourceLoadingCount() throws IOException, InterruptedException {
355 return channel.call(new LoadingCount(true));
356 }
357
358 /**
359 * Shows {@link Channel#classLoadingTime}.
360 * @since 1.495
361 */
362 public long getClassLoadingTime() throws IOException, InterruptedException {
363 return channel.call(new LoadingTime(false));
364 }
365
366 /**
367 * Shows {@link Channel#resourceLoadingTime}.
368 * @since 1.495
369 */
370 public long getResourceLoadingTime() throws IOException, InterruptedException {
371 return channel.call(new LoadingTime(true));
372 }
373
374 static class LoadingCount implements Callable<Integer,RuntimeException> {
375 private final boolean resource;
376 LoadingCount(boolean resource) {
377 this.resource = resource;
378 }
379 @Override public Integer call() {
380 Channel c = Channel.current();
381 return resource ? c.resourceLoadingCount.get() : c.classLoadingCount.get();
382 }
383 }
384
385 static class LoadingTime implements Callable<Long,RuntimeException> {
386 private final boolean resource;
387 LoadingTime(boolean resource) {
388 this.resource = resource;
389 }
390 @Override public Long call() {
391 Channel c = Channel.current();
392 return resource ? c.resourceLoadingTime.get() : c.classLoadingTime.get();
393 }
394 }
395
396 /**
397 * Sets up the connection through an exsting channel.
398 *
399 * @since 1.444
400 */
401 public void setChannel(Channel channel, OutputStream launchLog, Channel.Listener listener) throws IOException, InterruptedException {
402 if(this.channel!=null)
403 throw new IllegalStateException("Already connected");
404
405 final TaskListener taskListener = new StreamTaskListener(launchLog);
406 PrintStream log = taskListener.getLogger();
407
408 channel.addListener(new Channel.Listener() {
409 @Override
410 public void onClosed(Channel c, IOException cause) {
411 // Orderly shutdown will have null exception
412 if (cause!=null) {
413 offlineCause = new ChannelTermination(cause);
414 cause.printStackTrace(taskListener.error("Connection terminated"));
415 } else {
416 taskListener.getLogger().println("Connection terminated");
417 }
418 closeChannel();
419 launcher.afterDisconnect(SlaveComputer.this, taskListener);
420 }
421 });
422 if(listener!=null)
423 channel.addListener(listener);
424
425 String slaveVersion = channel.call(new SlaveVersion());
426 log.println("Slave.jar version: " + slaveVersion);
427
428 boolean _isUnix = channel.call(new DetectOS());
429 log.println(_isUnix? hudson.model.Messages.Slave_UnixSlave():hudson.model.Messages.Slave_WindowsSlave());
430
431 String defaultCharsetName = channel.call(new DetectDefaultCharset());
432
433 String remoteFs = getNode().getRemoteFS();
434 if(_isUnix && !remoteFs.contains("/") && remoteFs.contains("\\"))
435 log.println("WARNING: "+remoteFs+" looks suspiciously like Windows path. Maybe you meant "+remoteFs.replace('\\','/')+"?");
436 FilePath root = new FilePath(channel,getNode().getRemoteFS());
437
438 // reference counting problem is known to happen, such as JENKINS-9017, and so as a preventive measure
439 // we pin the base classloader so that it'll never get GCed. When this classloader gets released,
440 // it'll have a catastrophic impact on the communication.
441 channel.pinClassLoader(getClass().getClassLoader());
442
443 channel.call(new SlaveInitializer());
444 for (ComputerListener cl : ComputerListener.all())
445 cl.preOnline(this,channel,root,taskListener);
446
447 offlineCause = null;
448
449 // update the data structure atomically to prevent others from seeing a channel that's not properly initialized yet
450 synchronized(channelLock) {
451 if(this.channel!=null) {
452 // check again. we used to have this entire method in a big sycnhronization block,
453 // but Channel constructor blocks for an external process to do the connection
454 // if CommandLauncher is used, and that cannot be interrupted because it blocks at InputStream.
455 // so if the process hangs, it hangs the thread in a lock, and since Hudson will try to relaunch,
456 // we'll end up queuing the lot of threads in a pseudo deadlock.
457 // This implementation prevents that by avoiding a lock. HUDSON-1705 is likely a manifestation of this.
458 channel.close();
459 throw new IllegalStateException("Already connected");
460 }
461 isUnix = _isUnix;
462 numRetryAttempt = 0;
463 this.channel = channel;
464 defaultCharset = Charset.forName(defaultCharsetName);
465
466 synchronized (statusChangeLock) {
467 statusChangeLock.notifyAll();
468 }
469 }
470 for (ComputerListener cl : ComputerListener.all())
471 cl.onOnline(this,taskListener);
472 log.println("Slave successfully connected and online");
473 Jenkins.getInstance().getQueue().scheduleMaintenance();
474 }
475
476 @Override
477 public Channel getChannel() {
478 return channel;
479 }
480
481 public Charset getDefaultCharset() {
482 return defaultCharset;
483 }
484
485 public List<LogRecord> getLogRecords() throws IOException, InterruptedException {
486 if(channel==null)
487 return Collections.emptyList();
488 else
489 return channel.call(new Callable<List<LogRecord>,RuntimeException>() {
490 public List<LogRecord> call() {
491 return new ArrayList<LogRecord>(SLAVE_LOG_HANDLER.getView());
492 }
493 });
494 }
495
496 public HttpResponse doDoDisconnect(@QueryParameter String offlineMessage) throws IOException, ServletException {
497 if (channel!=null) {
498 //does nothing in case computer is already disconnected
499 checkPermission(DISCONNECT);
500 offlineMessage = Util.fixEmptyAndTrim(offlineMessage);
501 disconnect(OfflineCause.create(Messages._SlaveComputer_DisconnectedBy(
502 Jenkins.getAuthentication().getName(),
503 offlineMessage!=null ? " : " + offlineMessage : "")
504 ));
505 }
506 return new HttpRedirect(".");
507 }
508
509 @Override
510 public Future<?> disconnect(OfflineCause cause) {
511 super.disconnect(cause);
512 return Computer.threadPoolForRemoting.submit(new Runnable() {
513 public void run() {
514 // do this on another thread so that any lengthy disconnect operation
515 // (which could be typical) won't block UI thread.
516 launcher.beforeDisconnect(SlaveComputer.this, taskListener);
517 closeChannel();
518 launcher.afterDisconnect(SlaveComputer.this, taskListener);
519 }
520 });
521 }
522
523 public void doLaunchSlaveAgent(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
524 if(channel!=null) {
525 req.getView(this,"already-launched.jelly").forward(req, rsp);
526 return;
527 }
528
529 connect(true);
530
531 // TODO: would be nice to redirect the user to "launching..." wait page,
532 // then spend a few seconds there and poll for the completion periodically.
533 rsp.sendRedirect("log");
534 }
535
536 public void tryReconnect() {
537 numRetryAttempt++;
538 if(numRetryAttempt<6 || (numRetryAttempt%12)==0) {
539 // initially retry several times quickly, and after that, do it infrequently.
540 logger.info("Attempting to reconnect "+nodeName);
541 connect(true);
542 }
543 }
544
545 /**
546 * Serves jar files for JNLP slave agents.
547 *
548 * @deprecated since 2008-08-18.
549 * This URL binding is no longer used and moved up directly under to {@link jenkins.model.Jenkins},
550 * but it's left here for now just in case some old JNLP slave agents request it.
551 */
552 public Slave.JnlpJar getJnlpJars(String fileName) {
553 return new Slave.JnlpJar(fileName);
554 }
555
556 @WebMethod(name="slave-agent.jnlp")
557 public void doSlaveAgentJnlp(StaplerRequest req, StaplerResponse res) throws IOException, ServletException {
558 RequestDispatcher view = req.getView(this, "slave-agent.jnlp.jelly");
559 if ("true".equals(req.getParameter("encrypt"))) {
560 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
561 StaplerResponse temp = new ResponseImpl(req.getStapler(), new HttpServletResponseWrapper(res) {
562 @Override public ServletOutputStream getOutputStream() throws IOException {
563 return new FilterServletOutputStream(baos);
564 }
565 @Override public PrintWriter getWriter() throws IOException {
566 throw new IllegalStateException();
567 }
568 });
569 view.forward(req, temp);
570
571 byte[] iv = new byte[128/8];
572 new SecureRandom().nextBytes(iv);
573
574 byte[] jnlpMac = JnlpSlaveAgentProtocol.SLAVE_SECRET.mac(getName().getBytes("UTF-8"));
575 SecretKey key = new SecretKeySpec(jnlpMac, 0, /* export restrictions */ 128 / 8, "AES");
576 byte[] encrypted;
577 try {
578 Cipher c = Secret.getCipher("AES/CFB8/NoPadding");
579 c.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
580 encrypted = c.doFinal(baos.toByteArray());
581 } catch (GeneralSecurityException x) {
582 throw new IOException2(x);
583 }
584 res.setContentType("application/octet-stream");
585 res.getOutputStream().write(iv);
586 res.getOutputStream().write(encrypted);
587 } else {
588 checkPermission(CONNECT);
589 view.forward(req, res);
590 }
591 }
592
593 @Override
594 protected void kill() {
595 super.kill();
596 closeChannel();
597 IOUtils.closeQuietly(log);
598 }
599
600 public RetentionStrategy getRetentionStrategy() {
601 Slave n = getNode();
602 return n==null ? RetentionStrategy.INSTANCE : n.getRetentionStrategy();
603 }
604
605 /**
606 * If still connected, disconnect.
607 */
608 private void closeChannel() {
609 // TODO: race condition between this and the setChannel method.
610 Channel c = channel;
611 channel = null;
612 isUnix = null;
613 if (c != null) {
614 try {
615 c.close();
616 } catch (IOException e) {
617 logger.log(Level.SEVERE, "Failed to terminate channel to " + getDisplayName(), e);
618 }
619 for (ComputerListener cl : ComputerListener.all())
620 cl.onOffline(this);
621 }
622 }
623
624 @Override
625 protected void setNode(Node node) {
626 super.setNode(node);
627 launcher = grabLauncher(node);
628
629 // maybe the configuration was changed to relaunch the slave, so try to re-launch now.
630 // "constructed==null" test is an ugly hack to avoid launching before the object is fully
631 // constructed.
632 if(constructed!=null) {
633 if (node instanceof Slave)
634 ((Slave)node).getRetentionStrategy().check(this);
635 else
636 connect(false);
637 }
638 }
639
640 /**
641 * Grabs a {@link ComputerLauncher} out of {@link Node} to keep it in this {@link Computer}.
642 * The returned launcher will be set to {@link #launcher} and used to carry out the actual launch operation.
643 *
644 * <p>
645 * Subtypes that needs to decorate {@link ComputerLauncher} can do so by overriding this method.
646 * This is useful for {@link SlaveComputer}s for clouds for example, where one normally needs
647 * additional pre-launch step (such as waiting for the provisioned node to become available)
648 * before the user specified launch step (like SSH connection) kicks in.
649 *
650 * @see ComputerLauncherFilter
651 */
652 protected ComputerLauncher grabLauncher(Node node) {
653 return ((Slave)node).getLauncher();
654 }
655
656 /**
657 * Get the slave version
658 */
659 public String getSlaveVersion() throws IOException, InterruptedException {
660 return channel.call(new SlaveVersion());
661 }
662
663 /**
664 * Get the OS description.
665 */
666 public String getOSDescription() throws IOException, InterruptedException {
667 return channel.call(new DetectOS()) ? "Unix" : "Windows";
668 }
669
670 private static final Logger logger = Logger.getLogger(SlaveComputer.class.getName());
671
672 private static final class SlaveVersion implements Callable<String,IOException> {
673 public String call() throws IOException {
674 try { return Launcher.VERSION; }
675 catch (Throwable ex) { return "< 1.335"; } // Older slave.jar won't have VERSION
676 }
677 }
678 private static final class DetectOS implements Callable<Boolean,IOException> {
679 public Boolean call() throws IOException {
680 return File.pathSeparatorChar==':';
681 }
682 }
683
684 private static final class DetectDefaultCharset implements Callable<String,IOException> {
685 public String call() throws IOException {
686 return Charset.defaultCharset().name();
687 }
688 }
689
690 /**
691 * Puts the {@link #SLAVE_LOG_HANDLER} into a separate class so that loading this class
692 * in JVM doesn't end up loading tons of additional classes.
693 */
694 static final class LogHolder {
695 /**
696 * This field is used on each slave node to record log records on the slave.
697 */
698 static final RingBufferLogHandler SLAVE_LOG_HANDLER = new RingBufferLogHandler();
699 }
700
701 private static class SlaveInitializer implements Callable<Void,RuntimeException> {
702 public Void call() {
703 // avoid double installation of the handler. JNLP slaves can reconnect to the master multiple times
704 // and each connection gets a different RemoteClassLoader, so we need to evict them by class name,
705 // not by their identity.
706 for (Handler h : LOGGER.getHandlers()) {
707 if (h.getClass().getName().equals(SLAVE_LOG_HANDLER.getClass().getName()))
708 LOGGER.removeHandler(h);
709 }
710 LOGGER.addHandler(SLAVE_LOG_HANDLER);
711
712 // remove Sun PKCS11 provider if present. See http://wiki.jenkins-ci.org/display/JENKINS/Solaris+Issue+6276483
713 try {
714 Security.removeProvider("SunPKCS11-Solaris");
715 } catch (SecurityException e) {
716 // ignore this error.
717 }
718
719 Channel.current().setProperty("slave",Boolean.TRUE); // indicate that this side of the channel is the slave side.
720
721 return null;
722 }
723 private static final long serialVersionUID = 1L;
724 private static final Logger LOGGER = Logger.getLogger("hudson");
725 }
726
727 /**
728 * Obtains a {@link VirtualChannel} that allows some computation to be performed on the master.
729 * This method can be called from any thread on the master, or from slave (more precisely,
730 * it only works from the remoting request-handling thread in slaves, which means if you've started
731 * separate thread on slaves, that'll fail.)
732 *
733 * @return null if the calling thread doesn't have any trace of where its master is.
734 * @since 1.362
735 */
736 public static VirtualChannel getChannelToMaster() {
737 if (Jenkins.getInstance()!=null)
738 return MasterComputer.localChannel;
739
740 // if this method is called from within the slave computation thread, this should work
741 Channel c = Channel.current();
742 if (c!=null && Boolean.TRUE.equals(c.getProperty("slave")))
743 return c;
744
745 return null;
746 }
747 }
748