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

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi,
5 * Red Hat, Inc., Seiji Sogabe, Stephen Connolly, Thomas J. Black, Tom Huybrechts, CloudBees, Inc.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE.
24 */
25 package hudson.model;
26
27 import hudson.EnvVars;
28 import hudson.Launcher.ProcStarter;
29 import hudson.Util;
30 import hudson.cli.declarative.CLIMethod;
31 import hudson.cli.declarative.CLIResolver;
32 import hudson.console.AnnotatedLargeText;
33 import hudson.init.Initializer;
34 import hudson.model.Descriptor.FormException;
35 import hudson.model.labels.LabelAtom;
36 import hudson.model.queue.WorkUnit;
37 import hudson.node_monitors.NodeMonitor;
38 import hudson.remoting.Channel;
39 import hudson.remoting.VirtualChannel;
40 import hudson.remoting.Callable;
41 import hudson.security.ACL;
42 import hudson.security.AccessControlled;
43 import hudson.security.Permission;
44 import hudson.security.PermissionGroup;
45 import hudson.security.PermissionScope;
46 import hudson.slaves.ComputerLauncher;
47 import hudson.slaves.ComputerListener;
48 import hudson.slaves.NodeProperty;
49 import hudson.slaves.RetentionStrategy;
50 import hudson.slaves.WorkspaceList;
51 import hudson.slaves.OfflineCause;
52 import hudson.slaves.OfflineCause.ByCLI;
53 import hudson.tasks.BuildWrapper;
54 import hudson.tasks.Publisher;
55 import hudson.util.DaemonThreadFactory;
56 import hudson.util.EditDistance;
57 import hudson.util.ExceptionCatchingThreadFactory;
58 import hudson.util.RemotingDiagnostics;
59 import hudson.util.RemotingDiagnostics.HeapDump;
60 import hudson.util.RunList;
61 import hudson.util.Futures;
62 import jenkins.model.Jenkins;
63 import org.kohsuke.args4j.Argument;
64 import org.kohsuke.args4j.CmdLineException;
65 import org.kohsuke.stapler.StaplerRequest;
66 import org.kohsuke.stapler.StaplerResponse;
67 import org.kohsuke.stapler.QueryParameter;
68 import org.kohsuke.stapler.HttpResponses;
69 import org.kohsuke.stapler.HttpResponse;
70 import org.kohsuke.stapler.HttpRedirect;
71 import org.kohsuke.stapler.WebMethod;
72 import org.kohsuke.stapler.export.Exported;
73 import org.kohsuke.stapler.export.ExportedBean;
74 import org.kohsuke.args4j.Option;
75 import org.kohsuke.stapler.interceptor.RequirePOST;
76
77 import javax.servlet.ServletException;
78 import java.io.File;
79 import java.io.FilenameFilter;
80 import java.io.IOException;
81 import java.io.PrintWriter;
82 import java.io.StringWriter;
83 import java.util.*;
84 import java.util.concurrent.CopyOnWriteArrayList;
85 import java.util.concurrent.ExecutorService;
86 import java.util.concurrent.Executors;
87 import java.util.concurrent.Future;
88 import java.util.concurrent.ExecutionException;
89 import java.util.logging.LogRecord;
90 import java.util.logging.Level;
91 import java.util.logging.Logger;
92 import java.nio.charset.Charset;
93 import java.net.InetAddress;
94 import java.net.NetworkInterface;
95 import java.net.Inet4Address;
96 import java.util.regex.Matcher;
97 import java.util.regex.Pattern;
98 import javax.annotation.CheckForNull;
99 import javax.annotation.Nullable;
100
101 import static javax.servlet.http.HttpServletResponse.*;
102
103 /**
104 * Represents the running state of a remote computer that holds {@link Executor}s.
105 *
106 * <p>
107 * {@link Executor}s on one {@link Computer} are transparently interchangeable
108 * (that is the definition of {@link Computer}.)
109 *
110 * <p>
111 * This object is related to {@link Node} but they have some significant difference.
112 * {@link Computer} primarily works as a holder of {@link Executor}s, so
113 * if a {@link Node} is configured (probably temporarily) with 0 executors,
114 * you won't have a {@link Computer} object for it.
115 *
116 * Also, even if you remove a {@link Node}, it takes time for the corresponding
117 * {@link Computer} to be removed, if some builds are already in progress on that
118 * node. Or when the node configuration is changed, unaffected {@link Computer} object
119 * remains intact, while all the {@link Node} objects will go away.
120 *
121 * <p>
122 * This object also serves UI (since {@link Node} is an interface and can't have
123 * related side pages.)
124 *
125 * @author Kohsuke Kawaguchi
126 */
127 @ExportedBean
128 public /*transient*/ abstract class Computer extends Actionable implements AccessControlled, ExecutorListener {
129
130 private final CopyOnWriteArrayList<Executor> executors = new CopyOnWriteArrayList<Executor>();
131 // TODO:
132 private final CopyOnWriteArrayList<OneOffExecutor> oneOffExecutors = new CopyOnWriteArrayList<OneOffExecutor>();
133
134 private int numExecutors;
135
136 /**
137 * Contains info about reason behind computer being offline.
138 */
139 protected volatile OfflineCause offlineCause;
140
141 private long connectTime = 0;
142
143 /**
144 * True if Jenkins shouldn't start new builds on this node.
145 */
146 private boolean temporarilyOffline;
147
148 /**
149 * {@link Node} object may be created and deleted independently
150 * from this object.
151 */
152 protected String nodeName;
153
154 /**
155 * @see #getHostName()
156 */
157 private volatile String cachedHostName;
158 private volatile boolean hostNameCached;
159
160 private final WorkspaceList workspaceList = new WorkspaceList();
161
162 protected transient List<Action> transientActions;
163
164 protected final Object statusChangeLock = new Object();
165
166 public Computer(Node node) {
167 assert node.getNumExecutors()!=0 : "Computer created with 0 executors";
168 setNode(node);
169 }
170
171 /**
172 * Returns list of all boxes {@link ComputerPanelBox}s.
173 */
174 public List<ComputerPanelBox> getComputerPanelBoxs(){
175 return ComputerPanelBox.all(this);
176 }
177
178 /**
179 * Returns the transient {@link Action}s associated with the computer.
180 */
181 public List<Action> getActions() {
182 List<Action> result = new ArrayList<Action>();
183 result.addAll(super.getActions());
184 synchronized (this) {
185 if (transientActions == null) {
186 transientActions = TransientComputerActionFactory.createAllFor(this);
187 }
188 result.addAll(transientActions);
189 }
190 return result;
191 }
192
193 @Override
194 public void addAction(Action a) {
195 if(a==null) throw new IllegalArgumentException();
196 super.getActions().add(a);
197 }
198
199 /**
200 * This is where the log from the remote agent goes.
201 *
202 * @see #relocateOldLogs()
203 */
204 protected File getLogFile() {
205 File dir = new File(Jenkins.getInstance().getRootDir(),"logs/slaves/"+nodeName);
206 dir.mkdirs();
207 return new File(dir,"slave.log");
208 }
209
210 /**
211 * Gets the object that coordinates the workspace allocation on this computer.
212 */
213 public WorkspaceList getWorkspaceList() {
214 return workspaceList;
215 }
216
217 /**
218 * Gets the string representation of the slave log.
219 */
220 public String getLog() throws IOException {
221 return Util.loadFile(getLogFile());
222 }
223
224 /**
225 * Used to URL-bind {@link AnnotatedLargeText}.
226 */
227 public AnnotatedLargeText<Computer> getLogText() {
228 return new AnnotatedLargeText<Computer>(getLogFile(), Charset.defaultCharset(), false, this);
229 }
230
231 public ACL getACL() {
232 return Jenkins.getInstance().getAuthorizationStrategy().getACL(this);
233 }
234
235 public void checkPermission(Permission permission) {
236 getACL().checkPermission(permission);
237 }
238
239 public boolean hasPermission(Permission permission) {
240 return getACL().hasPermission(permission);
241 }
242
243 /**
244 * If the computer was offline (either temporarily or not),
245 * this method will return the cause.
246 *
247 * @return
248 * null if the system was put offline without given a cause.
249 */
250 @Exported
251 public OfflineCause getOfflineCause() {
252 return offlineCause;
253 }
254
255 /**
256 * If the computer was offline (either temporarily or not),
257 * this method will return the cause as a string (without user info).
258 *
259 * @return
260 * empty string if the system was put offline without given a cause.
261 */
262 @Exported
263 public String getOfflineCauseReason() {
264 if (offlineCause == null) {
265 return "";
266 }
267 // fetch the localized string for "Disconnected By"
268 String gsub_base = hudson.slaves.Messages.SlaveComputer_DisconnectedBy("","");
269 // regex to remove commented reason base string
270 String gsub1 = "^" + gsub_base + "[\\w\\W]* \\: ";
271 // regex to remove non-commented reason base string
272 String gsub2 = "^" + gsub_base + "[\\w\\W]*";
273
274 String newString = offlineCause.toString().replaceAll(gsub1, "");
275 return newString.replaceAll(gsub2, "");
276 }
277
278 /**
279 * Gets the channel that can be used to run a program on this computer.
280 *
281 * @return
282 * never null when {@link #isOffline()}==false.
283 */
284 public abstract @Nullable VirtualChannel getChannel();
285
286 /**
287 * Gets the default charset of this computer.
288 *
289 * @return
290 * never null when {@link #isOffline()}==false.
291 */
292 public abstract Charset getDefaultCharset();
293
294 /**
295 * Gets the logs recorded by this slave.
296 */
297 public abstract List<LogRecord> getLogRecords() throws IOException, InterruptedException;
298
299 /**
300 * If {@link #getChannel()}==null, attempts to relaunch the slave agent.
301 */
302 public abstract void doLaunchSlaveAgent( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException;
303
304 /**
305 * @deprecated since 2009-01-06. Use {@link #connect(boolean)}
306 */
307 public final void launch() {
308 connect(true);
309 }
310
311 /**
312 * Do the same as {@link #doLaunchSlaveAgent(StaplerRequest, StaplerResponse)}
313 * but outside the context of serving a request.
314 *
315 * <p>
316 * If already connected or if this computer doesn't support proactive launching, no-op.
317 * This method may return immediately
318 * while the launch operation happens asynchronously.
319 *
320 * @see #disconnect()
321 *
322 * @param forceReconnect
323 * If true and a connect activity is already in progress, it will be cancelled and
324 * the new one will be started. If false, and a connect activity is already in progress, this method
325 * will do nothing and just return the pending connection operation.
326 * @return
327 * A {@link Future} representing pending completion of the task. The 'completion' includes
328 * both a successful completion and a non-successful completion (such distinction typically doesn't
329 * make much sense because as soon as {@link Computer} is connected it can be disconnected by some other threads.)
330 */
331 public final Future<?> connect(boolean forceReconnect) {
332 connectTime = System.currentTimeMillis();
333 return _connect(forceReconnect);
334 }
335
336 /**
337 * Allows implementing-classes to provide an implementation for the connect method.
338 *
339 * <p>
340 * If already connected or if this computer doesn't support proactive launching, no-op.
341 * This method may return immediately
342 * while the launch operation happens asynchronously.
343 *
344 * @see #disconnect()
345 *
346 * @param forceReconnect
347 * If true and a connect activity is already in progress, it will be cancelled and
348 * the new one will be started. If false, and a connect activity is already in progress, this method
349 * will do nothing and just return the pending connection operation.
350 * @return
351 * A {@link Future} representing pending completion of the task. The 'completion' includes
352 * both a successful completion and a non-successful completion (such distinction typically doesn't
353 * make much sense because as soon as {@link Computer} is connected it can be disconnected by some other threads.)
354 */
355 protected abstract Future<?> _connect(boolean forceReconnect);
356
357 /**
358 * CLI command to reconnect this node.
359 */
360 @CLIMethod(name="connect-node")
361 public void cliConnect(@Option(name="-f",usage="Cancel any currently pending connect operation and retry from scratch") boolean force) throws ExecutionException, InterruptedException {
362 checkPermission(CONNECT);
363 connect(force).get();
364 }
365
366 /**
367 * Gets the time (since epoch) when this computer connected.
368 *
369 * @return The time in ms since epoch when this computer last connected.
370 */
371 public final long getConnectTime() {
372 return connectTime;
373 }
374
375 /**
376 * Disconnect this computer.
377 *
378 * If this is the master, no-op. This method may return immediately
379 * while the launch operation happens asynchronously.
380 *
381 * @param cause
382 * Object that identifies the reason the node was disconnected.
383 *
384 * @return
385 * {@link Future} to track the asynchronous disconnect operation.
386 * @see #connect(boolean)
387 * @since 1.320
388 */
389 public Future<?> disconnect(OfflineCause cause) {
390 offlineCause = cause;
391 if (Util.isOverridden(Computer.class,getClass(),"disconnect"))
392 return disconnect(); // legacy subtypes that extend disconnect().
393
394 connectTime=0;
395 return Futures.precomputed(null);
396 }
397
398 /**
399 * Equivalent to {@code disconnect(null)}
400 *
401 * @deprecated as of 1.320.
402 * Use {@link #disconnect(OfflineCause)} and specify the cause.
403 */
404 public Future<?> disconnect() {
405 if (Util.isOverridden(Computer.class,getClass(),"disconnect",OfflineCause.class))
406 // if the subtype already derives disconnect(OfflineCause), delegate to it
407 return disconnect(null);
408
409 connectTime=0;
410 return Futures.precomputed(null);
411 }
412
413 /**
414 * CLI command to disconnects this node.
415 */
416 @CLIMethod(name="disconnect-node")
417 public void cliDisconnect(@Option(name="-m",usage="Record the note about why you are disconnecting this node") String cause) throws ExecutionException, InterruptedException {
418 checkPermission(DISCONNECT);
419 disconnect(new ByCLI(cause)).get();
420 }
421
422 /**
423 * CLI command to mark the node offline.
424 */
425 @CLIMethod(name="offline-node")
426 public void cliOffline(@Option(name="-m",usage="Record the note about why you are disconnecting this node") String cause) throws ExecutionException, InterruptedException {
427 checkPermission(DISCONNECT);
428 setTemporarilyOffline(true,new ByCLI(cause));
429 }
430
431 @CLIMethod(name="online-node")
432 public void cliOnline() throws ExecutionException, InterruptedException {
433 checkPermission(CONNECT);
434 setTemporarilyOffline(false,null);
435 }
436
437 /**
438 * Number of {@link Executor}s that are configured for this computer.
439 *
440 * <p>
441 * When this value is decreased, it is temporarily possible
442 * for {@link #executors} to have a larger number than this.
443 */
444 // ugly name to let EL access this
445 @Exported
446 public int getNumExecutors() {
447 return numExecutors;
448 }
449
450 /**
451 * Returns {@link Node#getNodeName() the name of the node}.
452 */
453 public String getName() {
454 return nodeName;
455 }
456
457 /**
458 * Returns the {@link Node} that this computer represents.
459 *
460 * @return
461 * null if the configuration has changed and the node is removed, yet the corresponding {@link Computer}
462 * is not yet gone.
463 */
464 public @CheckForNull Node getNode() {
465 if(nodeName==null)
466 return Jenkins.getInstance();
467 return Jenkins.getInstance().getNode(nodeName);
468 }
469
470 @Exported
471 public LoadStatistics getLoadStatistics() {
472 return LabelAtom.get(nodeName != null ? nodeName : "").loadStatistics;
473 }
474
475 public BuildTimelineWidget getTimeline() {
476 return new BuildTimelineWidget(getBuilds());
477 }
478
479 /**
480 * {@inheritDoc}
481 */
482 public void taskAccepted(Executor executor, Queue.Task task) {
483 // dummy implementation
484 }
485
486 /**
487 * {@inheritDoc}
488 */
489 public void taskCompleted(Executor executor, Queue.Task task, long durationMS) {
490 // dummy implementation
491 }
492
493 /**
494 * {@inheritDoc}
495 */
496 public void taskCompletedWithProblems(Executor executor, Queue.Task task, long durationMS, Throwable problems) {
497 // dummy implementation
498 }
499
500 @Exported
501 public boolean isOffline() {
502 return temporarilyOffline || getChannel()==null;
503 }
504
505 public final boolean isOnline() {
506 return !isOffline();
507 }
508
509 /**
510 * This method is called to determine whether manual launching of the slave is allowed at this point in time.
511 * @return {@code true} if manual launching of the slave is allowed at this point in time.
512 */
513 @Exported
514 public boolean isManualLaunchAllowed() {
515 return getRetentionStrategy().isManualLaunchAllowed(this);
516 }
517
518
519 /**
520 * Is a {@link #connect(boolean)} operation in progress?
521 */
522 public abstract boolean isConnecting();
523
524 /**
525 * Returns true if this computer is supposed to be launched via JNLP.
526 * @deprecated since 2008-05-18.
527 * See {@linkplain #isLaunchSupported()} and {@linkplain ComputerLauncher}
528 */
529 @Exported
530 @Deprecated
531 public boolean isJnlpAgent() {
532 return false;
533 }
534
535 /**
536 * Returns true if this computer can be launched by Hudson proactively and automatically.
537 *
538 * <p>
539 * For example, JNLP slaves return {@code false} from this, because the launch process
540 * needs to be initiated from the slave side.
541 */
542 @Exported
543 public boolean isLaunchSupported() {
544 return true;
545 }
546
547 /**
548 * Returns true if this node is marked temporarily offline by the user.
549 *
550 * <p>
551 * In contrast, {@link #isOffline()} represents the actual online/offline
552 * state. For example, this method may return false while {@link #isOffline()}
553 * returns true if the slave agent failed to launch.
554 *
555 * @deprecated
556 * You should almost always want {@link #isOffline()}.
557 * This method is marked as deprecated to warn people when they
558 * accidentally call this method.
559 */
560 @Exported
561 public boolean isTemporarilyOffline() {
562 return temporarilyOffline;
563 }
564
565 /**
566 * @deprecated as of 1.320.
567 * Use {@link #setTemporarilyOffline(boolean, OfflineCause)}
568 */
569 public void setTemporarilyOffline(boolean temporarilyOffline) {
570 setTemporarilyOffline(temporarilyOffline,null);
571 }
572
573 /**
574 * Marks the computer as temporarily offline. This retains the underlying
575 * {@link Channel} connection, but prevent builds from executing.
576 *
577 * @param cause
578 * If the first argument is true, specify the reason why the node is being put
579 * offline.
580 */
581 public void setTemporarilyOffline(boolean temporarilyOffline, OfflineCause cause) {
582 offlineCause = temporarilyOffline ? cause : null;
583 this.temporarilyOffline = temporarilyOffline;
584 Node node = getNode();
585 if (node != null) {
586 node.setTemporaryOfflineCause(offlineCause);
587 }
588 Jenkins.getInstance().getQueue().scheduleMaintenance();
589 synchronized (statusChangeLock) {
590 statusChangeLock.notifyAll();
591 }
592 for (ComputerListener cl : ComputerListener.all()) {
593 if (temporarilyOffline) cl.onTemporarilyOffline(this,cause);
594 else cl.onTemporarilyOnline(this);
595 }
596 }
597
598 @Exported
599 public String getIcon() {
600 if(isOffline())
601 return "computer-x.png";
602 else
603 return "computer.png";
604 }
605
606 public String getIconAltText() {
607 if(isOffline())
608 return "[offline]";
609 else
610 return "[online]";
611 }
612
613 @Exported
614 public String getDisplayName() {
615 return nodeName;
616 }
617
618 public String getCaption() {
619 return Messages.Computer_Caption(nodeName);
620 }
621
622 public String getUrl() {
623 return "computer/"+getDisplayName()+"/";
624 }
625
626 /**
627 * Returns projects that are tied on this node.
628 */
629 public List<AbstractProject> getTiedJobs() {
630 Node node = getNode();
631 return (node != null) ? node.getSelfLabel().getTiedJobs() : Collections.EMPTY_LIST;
632 }
633
634 public RunList getBuilds() {
635 return new RunList(Jenkins.getInstance().getAllItems(Job.class)).node(getNode());
636 }
637
638 /**
639 * Called to notify {@link Computer} that its corresponding {@link Node}
640 * configuration is updated.
641 */
642 protected void setNode(Node node) {
643 assert node!=null;
644 if(node instanceof Slave)
645 this.nodeName = node.getNodeName();
646 else
647 this.nodeName = null;
648
649 setNumExecutors(node.getNumExecutors());
650 if (this.temporarilyOffline) {
651 // When we get a new node, push our current temp offline
652 // status to it (as the status is not carried across
653 // configuration changes that recreate the node).
654 // Since this is also called the very first time this
655 // Computer is created, avoid pushing an empty status
656 // as that could overwrite any status that the Node
657 // brought along from its persisted config data.
658 node.setTemporaryOfflineCause(this.offlineCause);
659 }
660 }
661
662 /**
663 * Called by {@link Jenkins#updateComputerList()} to notify {@link Computer} that it will be discarded.
664 */
665 protected void kill() {
666 setNumExecutors(0);
667 }
668
669 private synchronized void setNumExecutors(int n) {
670 if(numExecutors==n) return; // no-op
671
672 int diff = n-numExecutors;
673 this.numExecutors = n;
674
675 if(diff<0) {
676 // send signal to all idle executors to potentially kill them off
677 for( Executor e : executors )
678 if(e.isIdle())
679 e.interrupt();
680 } else {
681 // if the number is increased, add new ones
682 addNewExecutorIfNecessary();
683 }
684 }
685
686 private void addNewExecutorIfNecessary() {
687 Set<Integer> availableNumbers = new HashSet<Integer>();
688 for (int i = 0; i < numExecutors; i++)
689 availableNumbers.add(i);
690
691 for (Executor executor : executors)
692 availableNumbers.remove(executor.getNumber());
693
694 for (Integer number : availableNumbers) {
695 Executor e = new Executor(this, number);
696 e.start();
697 executors.add(e);
698 }
699 }
700
701 /**
702 * Returns the number of idle {@link Executor}s that can start working immediately.
703 */
704 public int countIdle() {
705 int n = 0;
706 for (Executor e : executors) {
707 if(e.isIdle())
708 n++;
709 }
710 return n;
711 }
712
713 /**
714 * Returns the number of {@link Executor}s that are doing some work right now.
715 */
716 public final int countBusy() {
717 return countExecutors()-countIdle();
718 }
719
720 /**
721 * Returns the current size of the executor pool for this computer.
722 * This number may temporarily differ from {@link #getNumExecutors()} if there
723 * are busy tasks when the configured size is decreased. OneOffExecutors are
724 * not included in this count.
725 */
726 public final int countExecutors() {
727 return executors.size();
728 }
729
730 /**
731 * Gets the read-only snapshot view of all {@link Executor}s.
732 */
733 @Exported
734 public List<Executor> getExecutors() {
735 return new ArrayList<Executor>(executors);
736 }
737
738 /**
739 * Gets the read-only snapshot view of all {@link OneOffExecutor}s.
740 */
741 @Exported
742 public List<OneOffExecutor> getOneOffExecutors() {
743 return new ArrayList<OneOffExecutor>(oneOffExecutors);
744 }
745
746 /**
747 * Returns true if all the executors of this computer are idle.
748 */
749 @Exported
750 public final boolean isIdle() {
751 if (!oneOffExecutors.isEmpty())
752 return false;
753 for (Executor e : executors)
754 if(!e.isIdle())
755 return false;
756 return true;
757 }
758
759 /**
760 * Returns true if this computer has some idle executors that can take more workload.
761 */
762 public final boolean isPartiallyIdle() {
763 for (Executor e : executors)
764 if(e.isIdle())
765 return true;
766 return false;
767 }
768
769 /**
770 * Returns the time when this computer last became idle.
771 *
772 * <p>
773 * If this computer is already idle, the return value will point to the
774 * time in the past since when this computer has been idle.
775 *
776 * <p>
777 * If this computer is busy, the return value will point to the
778 * time in the future where this computer will be expected to become free.
779 */
780 public final long getIdleStartMilliseconds() {
781 long firstIdle = Long.MIN_VALUE;
782 for (Executor e : oneOffExecutors) {
783 firstIdle = Math.max(firstIdle, e.getIdleStartMilliseconds());
784 }
785 for (Executor e : executors) {
786 firstIdle = Math.max(firstIdle, e.getIdleStartMilliseconds());
787 }
788 return firstIdle;
789 }
790
791 /**
792 * Returns the time when this computer first became in demand.
793 */
794 public final long getDemandStartMilliseconds() {
795 long firstDemand = Long.MAX_VALUE;
796 for (Queue.BuildableItem item : Jenkins.getInstance().getQueue().getBuildableItems(this)) {
797 firstDemand = Math.min(item.buildableStartMilliseconds, firstDemand);
798 }
799 return firstDemand;
800 }
801
802 /**
803 * Called by {@link Executor} to kill excessive executors from this computer.
804 */
805 /*package*/ synchronized void removeExecutor(Executor e) {
806 executors.remove(e);
807 addNewExecutorIfNecessary();
808 if(!isAlive())
809 {
810 AbstractCIBase ciBase = Jenkins.getInstance();
811 ciBase.removeComputer(this);
812 }
813 }
814
815 /**
816 * Returns true if any of the executors are functioning.
817 *
818 * Note that if an executor dies, we'll leave it in {@link #executors} until
819 * the administrator yanks it out, so that we can see why it died.
820 */
821 private boolean isAlive() {
822 for (Executor e : executors)
823 if (e.isAlive())
824 return true;
825 return false;
826 }
827
828 /**
829 * Interrupt all {@link Executor}s.
830 */
831 public void interrupt() {
832 for (Executor e : executors) {
833 e.interrupt();
834 }
835 }
836
837 public String getSearchUrl() {
838 return "computer/"+nodeName;
839 }
840
841 /**
842 * {@link RetentionStrategy} associated with this computer.
843 *
844 * @return
845 * never null. This method return {@code RetentionStrategy<? super T>} where
846 * {@code T=this.getClass()}.
847 */
848 public abstract RetentionStrategy getRetentionStrategy();
849
850 /**
851 * Expose monitoring data for the remote API.
852 */
853 @Exported(inline=true)
854 public Map<String/*monitor name*/,Object> getMonitorData() {
855 Map<String,Object> r = new HashMap<String, Object>();
856 for (NodeMonitor monitor : NodeMonitor.getAll())
857 r.put(monitor.getClass().getName(),monitor.data(this));
858 return r;
859 }
860
861 /**
862 * Gets the system properties of the JVM on this computer.
863 * If this is the master, it returns the system property of the master computer.
864 */
865 public Map<Object,Object> getSystemProperties() throws IOException, InterruptedException {
866 return RemotingDiagnostics.getSystemProperties(getChannel());
867 }
868
869 /**
870 * @deprecated as of 1.292
871 * Use {@link #getEnvironment()} instead.
872 */
873 public Map<String,String> getEnvVars() throws IOException, InterruptedException {
874 return getEnvironment();
875 }
876
877 /**
878 * Gets the environment variables of the JVM on this computer.
879 * If this is the master, it returns the system property of the master computer.
880 */
881 public EnvVars getEnvironment() throws IOException, InterruptedException {
882 return EnvVars.getRemote(getChannel());
883 }
884
885 /**
886 * Creates an environment variable override to be used for launching processes on this node.
887 *
888 * @see ProcStarter#envs(Map)
889 * @since 1.489
890 */
891 public EnvVars buildEnvironment(TaskListener listener) throws IOException, InterruptedException {
892 EnvVars env = new EnvVars();
893
894 Node node = getNode();
895 if (node==null) return env; // bail out
896
897 for (NodeProperty nodeProperty: Jenkins.getInstance().getGlobalNodeProperties()) {
898 nodeProperty.buildEnvVars(env,listener);
899 }
900
901 for (NodeProperty nodeProperty: node.getNodeProperties()) {
902 nodeProperty.buildEnvVars(env,listener);
903 }
904
905 // TODO: hmm, they don't really belong
906 String rootUrl = Hudson.getInstance().getRootUrl();
907 if(rootUrl!=null) {
908 env.put("HUDSON_URL", rootUrl); // Legacy.
909 env.put("JENKINS_URL", rootUrl);
910 }
911
912 return env;
913 }
914
915 /**
916 * Gets the thread dump of the slave JVM.
917 * @return
918 * key is the thread name, and the value is the pre-formatted dump.
919 */
920 public Map<String,String> getThreadDump() throws IOException, InterruptedException {
921 return RemotingDiagnostics.getThreadDump(getChannel());
922 }
923
924 /**
925 * Obtains the heap dump.
926 */
927 public HeapDump getHeapDump() throws IOException {
928 return new HeapDump(this,getChannel());
929 }
930
931 /**
932 * This method tries to compute the name of the host that's reachable by all the other nodes.
933 *
934 * <p>
935 * Since it's possible that the slave is not reachable from the master (it may be behind a firewall,
936 * connecting to master via JNLP), this method may return null.
937 *
938 * It's surprisingly tricky for a machine to know a name that other systems can get to,
939 * especially between things like DNS search suffix, the hosts file, and YP.
940 *
941 * <p>
942 * So the technique here is to compute possible interfaces and names on the slave,
943 * then try to ping them from the master, and pick the one that worked.
944 *
945 * <p>
946 * The computation may take some time, so it employs caching to make the successive lookups faster.
947 *
948 * @since 1.300
949 * @return
950 * null if the host name cannot be computed (for example because this computer is offline,
951 * because the slave is behind the firewall, etc.)
952 */
953 public String getHostName() throws IOException, InterruptedException {
954 if(hostNameCached)
955 // in the worst case we end up having multiple threads computing the host name simultaneously, but that's not harmful, just wasteful.
956 return cachedHostName;
957
958 VirtualChannel channel = getChannel();
959 if(channel==null) return null; // can't compute right now
960
961 for( String address : channel.call(new ListPossibleNames())) {
962 try {
963 InetAddress ia = InetAddress.getByName(address);
964 if(!(ia instanceof Inet4Address)) {
965 LOGGER.fine(address+" is not an IPv4 address");
966 continue;
967 }
968 if(!ComputerPinger.checkIsReachable(ia, 3)) {
969 LOGGER.fine(address+" didn't respond to ping");
970 continue;
971 }
972 cachedHostName = ia.getCanonicalHostName();
973 hostNameCached = true;
974 return cachedHostName;
975 } catch (IOException e) {
976 // if a given name fails to parse on this host, we get this error
977 LOGGER.log(Level.FINE, "Failed to parse "+address,e);
978 }
979 }
980
981 // allow the administrator to manually specify the host name as a fallback. HUDSON-5373
982 cachedHostName = channel.call(new GetFallbackName());
983 hostNameCached = true;
984 return cachedHostName;
985 }
986
987 /**
988 * Starts executing a fly-weight task.
989 */
990 /*package*/ final void startFlyWeightTask(WorkUnit p) {
991 OneOffExecutor e = new OneOffExecutor(this, p);
992 e.start();
993 oneOffExecutors.add(e);
994 }
995
996 /*package*/ final void remove(OneOffExecutor e) {
997 oneOffExecutors.remove(e);
998 }
999
1000 private static class ListPossibleNames implements Callable<List<String>,IOException> {
1001 public List<String> call() throws IOException {
1002 List<String> names = new ArrayList<String>();
1003
1004 Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
1005 while (nis.hasMoreElements()) {
1006 NetworkInterface ni = nis.nextElement();
1007 LOGGER.fine("Listing up IP addresses for "+ni.getDisplayName());
1008 Enumeration<InetAddress> e = ni.getInetAddresses();
1009 while (e.hasMoreElements()) {
1010 InetAddress ia = e.nextElement();
1011 if(ia.isLoopbackAddress()) {
1012 LOGGER.fine(ia+" is a loopback address");
1013 continue;
1014 }
1015
1016 if(!(ia instanceof Inet4Address)) {
1017 LOGGER.fine(ia+" is not an IPv4 address");
1018 continue;
1019 }
1020
1021 LOGGER.fine(ia+" is a viable candidate");
1022 names.add(ia.getHostAddress());
1023 }
1024 }
1025 return names;
1026 }
1027 private static final long serialVersionUID = 1L;
1028 }
1029
1030 private static class GetFallbackName implements Callable<String,IOException> {
1031 public String call() throws IOException {
1032 return System.getProperty("host.name");
1033 }
1034 private static final long serialVersionUID = 1L;
1035 }
1036
1037 public static final ExecutorService threadPoolForRemoting = Executors.newCachedThreadPool(new ExceptionCatchingThreadFactory(new DaemonThreadFactory()));
1038
1039 //
1040 //
1041 // UI
1042 //
1043 //
1044 public void doRssAll( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1045 rss(req, rsp, " all builds", getBuilds());
1046 }
1047 public void doRssFailed( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1048 rss(req, rsp, " failed builds", getBuilds().failureOnly());
1049 }
1050 private void rss(StaplerRequest req, StaplerResponse rsp, String suffix, RunList runs) throws IOException, ServletException {
1051 RSS.forwardToRss(getDisplayName()+ suffix, getUrl(),
1052 runs.newBuilds(), Run.FEED_ADAPTER, req, rsp );
1053 }
1054
1055 public HttpResponse doToggleOffline(@QueryParameter String offlineMessage) throws IOException, ServletException {
1056 if(!temporarilyOffline) {
1057 checkPermission(DISCONNECT);
1058 offlineMessage = Util.fixEmptyAndTrim(offlineMessage);
1059 setTemporarilyOffline(!temporarilyOffline,
1060 OfflineCause.create(hudson.slaves.Messages._SlaveComputer_DisconnectedBy(
1061 Jenkins.getAuthentication().getName(),
1062 offlineMessage!=null ? " : " + offlineMessage : "")));
1063 } else {
1064 checkPermission(CONNECT);
1065 setTemporarilyOffline(!temporarilyOffline,null);
1066 }
1067 return HttpResponses.redirectToDot();
1068 }
1069
1070 public HttpResponse doChangeOfflineCause(@QueryParameter String offlineMessage) throws IOException, ServletException {
1071 checkPermission(DISCONNECT);
1072 offlineMessage = Util.fixEmptyAndTrim(offlineMessage);
1073 setTemporarilyOffline(true,
1074 OfflineCause.create(hudson.slaves.Messages._SlaveComputer_DisconnectedBy(
1075 Jenkins.getAuthentication().getName(),
1076 offlineMessage!=null ? " : " + offlineMessage : "")));
1077 return HttpResponses.redirectToDot();
1078 }
1079
1080 public Api getApi() {
1081 return new Api(this);
1082 }
1083
1084 /**
1085 * Dumps the contents of the export table.
1086 */
1087 public void doDumpExportTable( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, InterruptedException {
1088 // this is a debug probe and may expose sensitive information
1089 checkPermission(Jenkins.ADMINISTER);
1090
1091 rsp.setContentType("text/plain");
1092 PrintWriter w = new PrintWriter(rsp.getCompressedWriter(req));
1093 VirtualChannel vc = getChannel();
1094 if (vc instanceof Channel) {
1095 w.println("Master to slave");
1096 ((Channel)vc).dumpExportTable(w);
1097 w.flush(); // flush here once so that even if the dump from the slave fails, the client gets some useful info
1098
1099 w.println("\n\n\nSlave to master");
1100 w.print(vc.call(new DumpExportTableTask()));
1101 } else {
1102 w.println(Messages.Computer_BadChannel());
1103 }
1104 w.close();
1105 }
1106
1107 private static final class DumpExportTableTask implements Callable<String,IOException> {
1108 public String call() throws IOException {
1109 StringWriter sw = new StringWriter();
1110 PrintWriter pw = new PrintWriter(sw);
1111 Channel.current().dumpExportTable(pw);
1112 pw.close();
1113 return sw.toString();
1114 }
1115 }
1116
1117 /**
1118 * For system diagnostics.
1119 * Run arbitrary Groovy script.
1120 */
1121 public void doScript(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
1122 _doScript(req, rsp, "_script.jelly");
1123 }
1124
1125 /**
1126 * Run arbitrary Groovy script and return result as plain text.
1127 */
1128 public void doScriptText(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
1129 _doScript(req, rsp, "_scriptText.jelly");
1130 }
1131
1132 protected void _doScript( StaplerRequest req, StaplerResponse rsp, String view) throws IOException, ServletException {
1133 // ability to run arbitrary script is dangerous
1134 checkPermission(Jenkins.RUN_SCRIPTS);
1135
1136 String text = req.getParameter("script");
1137 if(text!=null) {
1138 try {
1139 req.setAttribute("output",
1140 RemotingDiagnostics.executeGroovy(text,getChannel()));
1141 } catch (InterruptedException e) {
1142 throw new ServletException(e);
1143 }
1144 }
1145
1146 req.getView(this,view).forward(req, rsp);
1147 }
1148
1149 /**
1150 * Accepts the update to the node configuration.
1151 */
1152 @RequirePOST
1153 public void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
1154 checkPermission(CONFIGURE);
1155
1156 String name = Util.fixEmptyAndTrim(req.getSubmittedForm().getString("name"));
1157 Jenkins.checkGoodName(name);
1158
1159 Node node = getNode();
1160 if (node == null) {
1161 throw new ServletException("No such node " + nodeName);
1162 }
1163 Node result = node.reconfigure(req, req.getSubmittedForm());
1164 replaceBy(result);
1165
1166 // take the user back to the slave top page.
1167 rsp.sendRedirect2("../" + result.getNodeName() + '/');
1168 }
1169
1170 /**
1171 * Accepts <tt>config.xml</tt> submission, as well as serve it.
1172 */
1173 @WebMethod(name = "config.xml")
1174 public void doConfigDotXml(StaplerRequest req, StaplerResponse rsp)
1175 throws IOException, ServletException {
1176 checkPermission(Jenkins.ADMINISTER);
1177 if (req.getMethod().equals("GET")) {
1178 // read
1179 rsp.setContentType("application/xml");
1180 Jenkins.XSTREAM2.toXML(getNode(), rsp.getOutputStream());
1181 return;
1182 }
1183 if (req.getMethod().equals("POST")) {
1184 // submission
1185 Node result = (Node)Jenkins.XSTREAM2.fromXML(req.getReader());
1186
1187 replaceBy(result);
1188 return;
1189 }
1190
1191 // huh?
1192 rsp.sendError(SC_BAD_REQUEST);
1193 }
1194
1195 /**
1196 * Replaces the current {@link Node} by another one.
1197 */
1198 private void replaceBy(Node newNode) throws ServletException, IOException {
1199 final Jenkins app = Jenkins.getInstance();
1200
1201 // replace the old Node object by the new one
1202 synchronized (app) {
1203 List<Node> nodes = new ArrayList<Node>(app.getNodes());
1204 int i = nodes.indexOf(getNode());
1205 if(i<0) {
1206 throw new IOException("This slave appears to be removed while you were editing the configuration");
1207 }
1208
1209 nodes.set(i, newNode);
1210 app.setNodes(nodes);
1211 }
1212 }
1213
1214 /**
1215 * Really deletes the slave.
1216 */
1217 @CLIMethod(name="delete-node")
1218 public HttpResponse doDoDelete() throws IOException {
1219 checkPermission(DELETE);
1220 Node node = getNode();
1221 if (node != null) {
1222 Jenkins.getInstance().removeNode(node);
1223 } else {
1224 AbstractCIBase app = Jenkins.getInstance();
1225 app.removeComputer(this);
1226 }
1227 return new HttpRedirect("..");
1228 }
1229
1230 /**
1231 * Blocks until the node becomes online/offline.
1232 */
1233 @CLIMethod(name="wait-node-online")
1234 public void waitUntilOnline() throws InterruptedException {
1235 synchronized (statusChangeLock) {
1236 while (!isOnline())
1237 statusChangeLock.wait(1000);
1238 }
1239 }
1240
1241 @CLIMethod(name="wait-node-offline")
1242 public void waitUntilOffline() throws InterruptedException {
1243 synchronized (statusChangeLock) {
1244 while (!isOffline())
1245 statusChangeLock.wait(1000);
1246 }
1247 }
1248
1249 /**
1250 * Handles incremental log.
1251 */
1252 public void doProgressiveLog( StaplerRequest req, StaplerResponse rsp) throws IOException {
1253 getLogText().doProgressText(req,rsp);
1254 }
1255
1256 /**
1257 * Gets the current {@link Computer} that the build is running.
1258 * This method only works when called during a build, such as by
1259 * {@link Publisher}, {@link BuildWrapper}, etc.
1260 */
1261 public static Computer currentComputer() {
1262 Executor e = Executor.currentExecutor();
1263 // If no executor then must be on master node
1264 return e != null ? e.getOwner() : Jenkins.getInstance().toComputer();
1265 }
1266
1267 /**
1268 * Returns {@code true} if the computer is accepting tasks. Needed to allow slaves programmatic suspension of task
1269 * scheduling that does not overlap with being offline.
1270 *
1271 * @return {@code true} if the computer is accepting tasks
1272 */
1273 public boolean isAcceptingTasks() {
1274 return true;
1275 }
1276
1277 /**
1278 * Used for CLI binding.
1279 */
1280 @CLIResolver
1281 public static Computer resolveForCLI(
1282 @Argument(required=true,metaVar="NAME",usage="Slave name, or empty string for master") String name) throws CmdLineException {
1283 Jenkins h = Jenkins.getInstance();
1284 Computer item = h.getComputer(name);
1285 if (item==null) {
1286 List<String> names = new ArrayList<String>();
1287 for (Computer c : h.getComputers())
1288 if (c.getName().length()>0)
1289 names.add(c.getName());
1290 throw new CmdLineException(null,Messages.Computer_NoSuchSlaveExists(name,EditDistance.findNearest(name,names)));
1291 }
1292 return item;
1293 }
1294
1295 /**
1296 * Relocate log files in the old location to the new location.
1297 *
1298 * Files were used to be $JENKINS_ROOT/slave-NAME.log (and .1, .2, ...)
1299 * but now they are at $JENKINS_ROOT/logs/slaves/NAME/slave.log (and .1, .2, ...)
1300 *
1301 * @see #getLogFile()
1302 */
1303 @Initializer
1304 public static void relocateOldLogs() {
1305 relocateOldLogs(Jenkins.getInstance().getRootDir());
1306 }
1307
1308 /*package*/ static void relocateOldLogs(File dir) {
1309 final Pattern logfile = Pattern.compile("slave-(.*)\\.log(\\.[0-9]+)?");
1310 File[] logfiles = dir.listFiles(new FilenameFilter() {
1311 public boolean accept(File dir, String name) {
1312 return logfile.matcher(name).matches();
1313 }
1314 });
1315 if (logfiles==null) return;
1316
1317 for (File f : logfiles) {
1318 Matcher m = logfile.matcher(f.getName());
1319 if (m.matches()) {
1320 File newLocation = new File(dir, "logs/slaves/" + m.group(1) + "/slave.log" + Util.fixNull(m.group(2)));
1321 newLocation.getParentFile().mkdirs();
1322 f.renameTo(newLocation);
1323 } else {
1324 assert false;
1325 }
1326 }
1327 }
1328
1329 public static final PermissionGroup PERMISSIONS = new PermissionGroup(Computer.class,Messages._Computer_Permissions_Title());
1330 /**
1331 * Permission to configure slaves.
1332 */
1333 public static final Permission CONFIGURE = new Permission(PERMISSIONS,"Configure", Messages._Computer_ConfigurePermission_Description(), Permission.CONFIGURE, PermissionScope.COMPUTER);
1334 public static final Permission DELETE = new Permission(PERMISSIONS,"Delete", Messages._Computer_DeletePermission_Description(), Permission.DELETE, PermissionScope.COMPUTER);
1335 public static final Permission CREATE = new Permission(PERMISSIONS,"Create", Messages._Computer_CreatePermission_Description(), Permission.CREATE, PermissionScope.COMPUTER);
1336 public static final Permission DISCONNECT = new Permission(PERMISSIONS,"Disconnect", Messages._Computer_DisconnectPermission_Description(), Jenkins.ADMINISTER, PermissionScope.COMPUTER);
1337 public static final Permission CONNECT = new Permission(PERMISSIONS,"Connect", Messages._Computer_ConnectPermission_Description(), DISCONNECT, PermissionScope.COMPUTER);
1338
1339 private static final Logger LOGGER = Logger.getLogger(Computer.class.getName());
1340 }
1341