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

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2009, Sun Microsystems, Inc.
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.os.windows;
25
26 import static hudson.Util.copyStreamAndClose;
27 import static org.jvnet.hudson.wmi.Win32Service.Win32OwnProcess;
28
29 import hudson.EnvVars;
30 import hudson.Extension;
31 import hudson.Util;
32 import hudson.model.*;
33 import hudson.os.windows.ManagedWindowsServiceAccount.AnotherUser;
34 import hudson.os.windows.ManagedWindowsServiceAccount.LocalSystem;
35 import hudson.remoting.Channel;
36 import hudson.remoting.Channel.Listener;
37 import hudson.remoting.SocketInputStream;
38 import hudson.remoting.SocketOutputStream;
39 import hudson.slaves.*;
40 import hudson.tools.JDKInstaller;
41 import hudson.tools.JDKInstaller.CPU;
42 import hudson.tools.JDKInstaller.Platform;
43 import hudson.util.DescribableList;
44 import hudson.util.IOUtils;
45 import hudson.util.Secret;
46 import hudson.util.jna.DotNet;
47
48 import java.io.*;
49 import java.net.InetAddress;
50 import java.net.InetSocketAddress;
51 import java.net.Socket;
52 import java.net.URL;
53 import java.net.UnknownHostException;
54 import java.util.logging.Level;
55 import java.util.logging.Logger;
56
57 import jcifs.smb.NtlmPasswordAuthentication;
58 import jcifs.smb.SmbException;
59 import jcifs.smb.SmbFile;
60 import jenkins.model.Jenkins;
61
62 import org.apache.commons.lang.StringUtils;
63 import org.dom4j.Document;
64 import org.dom4j.DocumentException;
65 import org.dom4j.io.SAXReader;
66 import org.jinterop.dcom.common.JIDefaultAuthInfoImpl;
67 import org.jinterop.dcom.common.JIException;
68 import org.jinterop.dcom.core.JISession;
69 import org.jvnet.hudson.remcom.WindowsRemoteProcessLauncher;
70 import org.jvnet.hudson.wmi.SWbemServices;
71 import org.jvnet.hudson.wmi.WMI;
72 import org.jvnet.hudson.wmi.Win32Service;
73 import org.kohsuke.stapler.DataBoundConstructor;
74
75 /**
76 * Windows slave installed/managed as a service entirely remotely
77 *
78 * @author Kohsuke Kawaguchi
79 */
80 public class ManagedWindowsServiceLauncher extends ComputerLauncher {
81
82 /**
83 * "[DOMAIN\\]USERNAME" to follow the Windows convention.
84 */
85 public final String userName;
86
87 public final Secret password;
88
89 public final String vmargs;
90
91 public final String javaPath;
92
93 /**
94 * @deprecated Use {@link #account}
95 */
96 public transient final AccountInfo logOn;
97
98 /**
99 * Specifies the account used to run the service.
100 */
101 private ManagedWindowsServiceAccount account;
102
103 public static class LogOnOption {
104 public final String value;
105
106 public final AccountInfo logOn;
107
108 @DataBoundConstructor
109 public LogOnOption(String value, AccountInfo logOn) {
110 this.value = value;
111 this.logOn = logOn;
112 }
113 }
114
115 public static class AccountInfo extends AbstractDescribableImpl<AccountInfo> {
116 public final String userName;
117
118 public final Secret password;
119
120 @DataBoundConstructor
121 public AccountInfo(String userName, String password) {
122 this.userName = userName;
123 this.password = Secret.fromString(password);
124 }
125
126 @Extension
127 public static class DescriptorImpl extends Descriptor<AccountInfo> {
128 @Override
129 public String getDisplayName() {
130 return ""; // unused
131 }
132 }
133 }
134
135 /**
136 * Host name to connect to. For compatibility reasons, null if the same with the slave name.
137 * @since 1.419
138 */
139 public final String host;
140
141 public ManagedWindowsServiceLauncher(String userName, String password) {
142 this (userName, password, null);
143 }
144
145 public ManagedWindowsServiceLauncher(String userName, String password, String host) {
146 this(userName, password, host, null, null);
147 }
148
149 public ManagedWindowsServiceLauncher(String userName, String password, String host, AccountInfo account) {
150 this(userName,password,host,account==null ? new LocalSystem() : new AnotherUser(account.userName,account.password), null);
151 }
152
153 public ManagedWindowsServiceLauncher(String userName, String password, String host, ManagedWindowsServiceAccount account, String vmargs) {
154 this(userName, password, host, account, vmargs, "");
155 }
156 @DataBoundConstructor
157 public ManagedWindowsServiceLauncher(String userName, String password, String host, ManagedWindowsServiceAccount account, String vmargs, String javaPath) {
158 this.userName = userName;
159 this.password = Secret.fromString(password);
160 this.vmargs = Util.fixEmptyAndTrim(vmargs);
161 this.javaPath = Util.fixEmptyAndTrim(javaPath);
162 this.host = Util.fixEmptyAndTrim(host);
163 this.account = account==null ? new LocalSystem() : account;
164 this.logOn = null;
165 }
166
167 public Object readResolve() {
168 if (logOn!=null)
169 account = new AnotherUser(logOn.userName,logOn.password);
170 return this;
171 }
172
173 private JIDefaultAuthInfoImpl createAuth() {
174 String[] tokens = userName.split("\\\\");
175 if(tokens.length==2)
176 return new JIDefaultAuthInfoImpl(tokens[0], tokens[1], Secret.toString(password));
177 return new JIDefaultAuthInfoImpl("", userName, Secret.toString(password));
178 }
179
180 private NtlmPasswordAuthentication createSmbAuth() {
181 JIDefaultAuthInfoImpl auth = createAuth();
182 return new NtlmPasswordAuthentication(auth.getDomain(), auth.getUserName(), auth.getPassword());
183 }
184
185 public ManagedWindowsServiceAccount getAccount() {
186 return account;
187 }
188
189 private AccountInfo getLogOn() {
190 if (account==null) return null;
191 return account.getAccount(this);
192 }
193
194 @Override
195 public void launch(final SlaveComputer computer, final TaskListener listener) throws IOException, InterruptedException {
196 try {
197 final PrintStream logger = listener.getLogger();
198 final String name = determineHost(computer);
199
200 logger.println(Messages.ManagedWindowsServiceLauncher_ConnectingTo(name));
201
202 InetAddress host = InetAddress.getByName(name);
203
204 /*
205 Somehow this didn't work for me, so I'm disabling it.
206 */
207 // ping check
208 // if (!host.isReachable(3000)) {
209 // logger.println("Failed to ping "+name+". Is this a valid reachable host name?");
210 // // continue anyway, just in case it's just ICMP that's getting filtered
211 // }
212
213 checkPort135Access(logger, name, host);
214
215 JIDefaultAuthInfoImpl auth = createAuth();
216 JISession session = JISession.createSession(auth);
217 session.setGlobalSocketTimeout(60000);
218 SWbemServices services = WMI.connect(session, name);
219
220
221 String path = computer.getNode().getRemoteFS();
222 if (path.indexOf(':')==-1) throw new IOException("Remote file system root path of the slave needs to be absolute: "+path);
223 SmbFile remoteRoot = new SmbFile("smb://" + name + "/" + path.replace('\\', '/').replace(':', '$')+"/",createSmbAuth());
224
225 if(!remoteRoot.exists())
226 remoteRoot.mkdirs();
227
228 String java = resolveJava(computer);
229
230 try {// does Java exist?
231 logger.println("Checking if Java exists");
232 WindowsRemoteProcessLauncher wrpl = new WindowsRemoteProcessLauncher(name,auth);
233 Process proc = wrpl.launch("\"" +java + "\" -version","c:\\");
234 proc.getOutputStream().close();
235 StringWriter console = new StringWriter();
236 IOUtils.copy(proc.getInputStream(), console);
237 proc.getInputStream().close();
238 int exitCode = proc.waitFor();
239 if (exitCode==1) {// we'll get this error code if Java is not found
240 logger.println("No Java found. Downloading JDK");
241 JDKInstaller jdki = new JDKInstaller("jdk-6u16-oth-JPR@CDS-CDS_Developer",true);
242 URL jdk = jdki.locate(listener, Platform.WINDOWS, CPU.i386);
243
244 listener.getLogger().println("Installing JDK");
245 copyStreamAndClose(jdk.openStream(), new SmbFile(remoteRoot, "jdk.exe").getOutputStream());
246
247 String javaDir = path + "\\jdk"; // this is where we install Java to
248
249 WindowsRemoteFileSystem fs = new WindowsRemoteFileSystem(name, createSmbAuth());
250 fs.mkdirs(javaDir);
251
252 jdki.install(new WindowsRemoteLauncher(listener,wrpl), Platform.WINDOWS,
253 fs, listener, javaDir ,path+"\\jdk.exe");
254 } else {
255 checkJavaVersion(logger, java, new BufferedReader(new StringReader(console.toString())));
256 }
257 } catch (Exception e) {
258 e.printStackTrace(listener.error("Failed to prepare Java"));
259 return;
260 }
261
262 // this just doesn't work --- trying to obtain the type or check the existence of smb://server/C$/ results in "access denied"
263 // {// check if the administrative share exists
264 // String fullpath = remoteRoot.getPath();
265 // int idx = fullpath.indexOf("$/");
266 // if (idx>=0) {// this must be true but be defensive since all we are trying to do here is a friendlier error check
267 // boolean exists;
268 // try {
269 // // SmbFile.exists() doesn't work on a share
270 // new SmbFile(fullpath.substring(0, idx + 2)).getType();
271 // exists = true;
272 // } catch (SmbException e) {
273 // // on Windows XP that I was using for the test, if the share doesn't exist I get this error
274 // // a thread in jcifs library ML confirms this, too:
275 // // http://old.nabble.com/"The-network-name-cannot-be-found"-after-30-seconds-td18859163.html
276 // if (e.getNtStatus()== NtStatus.NT_STATUS_BAD_NETWORK_NAME)
277 // exists = false;
278 // else
279 // throw e;
280 // }
281 // if (!exists) {
282 // logger.println(name +" appears to be missing the administrative share "+fullpath.substring(idx-1,idx+1)/*C$*/);
283 // return;
284 // }
285 // }
286 // }
287
288 String id = generateServiceId(path);
289 Win32Service slaveService = services.getService(id);
290 if(slaveService==null) {
291 logger.println(Messages.ManagedWindowsServiceLauncher_InstallingSlaveService());
292 if(!DotNet.isInstalled(2,0, name, auth)) {
293 // abort the launch
294 logger.println(Messages.ManagedWindowsServiceLauncher_DotNetRequired());
295 return;
296 }
297
298 // copy exe
299 logger.println(Messages.ManagedWindowsServiceLauncher_CopyingSlaveExe());
300 copyStreamAndClose(getClass().getResource("/windows-service/jenkins.exe").openStream(), new SmbFile(remoteRoot,"jenkins-slave.exe").getOutputStream());
301
302 copySlaveJar(logger, remoteRoot);
303
304 // copy jenkins-slave.xml
305 String xml = createAndCopyJenkinsSlaveXml(java, id, logger, remoteRoot);
306
307 // install it as a service
308 logger.println(Messages.ManagedWindowsServiceLauncher_RegisteringService());
309 Document dom = new SAXReader().read(new StringReader(xml));
310 Win32Service svc = services.Get("Win32_Service").cast(Win32Service.class);
311 int r;
312 AccountInfo logOn = getLogOn();
313 if (logOn == null) {
314 r = svc.Create(
315 id,
316 dom.selectSingleNode("/service/name").getText()+" at "+path,
317 path+"\\jenkins-slave.exe",
318 Win32OwnProcess, 0, "Manual", true);
319 } else {
320 r = svc.Create(
321 id,
322 dom.selectSingleNode("/service/name").getText()+" at "+path,
323 path+"\\jenkins-slave.exe",
324 Win32OwnProcess,
325 0,
326 "Manual",
327 false, // When using a different user, it isn't possible to interact
328 logOn.userName,
329 Secret.toString(logOn.password),
330 null, null, null);
331
332 }
333 if(r!=0) {
334 listener.error("Failed to create a service: "+svc.getErrorMessage(r));
335 return;
336 }
337 slaveService = services.getService(id);
338 } else {
339 createAndCopyJenkinsSlaveXml(java, id, logger, remoteRoot);
340 copySlaveJar(logger, remoteRoot);
341 }
342
343 logger.println(Messages.ManagedWindowsServiceLauncher_StartingService());
344 slaveService.start();
345
346 // wait until we see the port.txt, but don't do so forever
347 logger.println(Messages.ManagedWindowsServiceLauncher_WaitingForService());
348 SmbFile portFile = new SmbFile(remoteRoot, "port.txt");
349 for( int i=0; !portFile.exists(); i++ ) {
350 if(i>=30) {
351 listener.error(Messages.ManagedWindowsServiceLauncher_ServiceDidntRespond());
352 return;
353 }
354 Thread.sleep(1000);
355 }
356 int p = readSmbFile(portFile);
357
358 // connect
359 logger.println(Messages.ManagedWindowsServiceLauncher_ConnectingToPort(p));
360 final Socket s = new Socket(name,p);
361
362 // ready
363 computer.setChannel(new BufferedInputStream(new SocketInputStream(s)),
364 new BufferedOutputStream(new SocketOutputStream(s)),
365 listener.getLogger(),new Listener() {
366 @Override
367 public void onClosed(Channel channel, IOException cause) {
368 afterDisconnect(computer,listener);
369 }
370 });
371 //destroy session to free the socket
372 JISession.destroySession(session);
373 } catch (SmbException e) {
374 e.printStackTrace(listener.error(e.getMessage()));
375 } catch (JIException e) {
376 if(e.getErrorCode()==5)
377 // access denied error
378 e.printStackTrace(listener.error(Messages.ManagedWindowsServiceLauncher_AccessDenied()));
379 else
380 e.printStackTrace(listener.error(e.getMessage()));
381 } catch (DocumentException e) {
382 e.printStackTrace(listener.error(e.getMessage()));
383 }
384 }
385
386 private String resolveJava(SlaveComputer computer) {
387 if (StringUtils.isNotBlank(javaPath)) {
388 return getEnvVars(computer).expand(javaPath);
389 }
390 return "java";
391 }
392
393 // -- duplicates code from ssh-slaves-plugin
394 private EnvVars getEnvVars(SlaveComputer computer) {
395 final EnvVars global = getEnvVars(Jenkins.getInstance());
396
397 final EnvVars local = getEnvVars(computer.getNode());
398
399 if (global != null) {
400 if (local != null) {
401 final EnvVars merged = new EnvVars(global);
402 merged.overrideAll(local);
403
404 return merged;
405 } else {
406 return global;
407 }
408 } else if (local != null) {
409 return local;
410 } else {
411 return new EnvVars();
412 }
413 }
414
415 private EnvVars getEnvVars(Hudson h) {
416 return getEnvVars(h.getGlobalNodeProperties());
417 }
418
419 private EnvVars getEnvVars(Node n) {
420 return getEnvVars(n.getNodeProperties());
421 }
422
423 private EnvVars getEnvVars(DescribableList<NodeProperty<?>, NodePropertyDescriptor> dl) {
424 final EnvironmentVariablesNodeProperty evnp = dl.get(EnvironmentVariablesNodeProperty.class);
425 if (evnp == null) {
426 return null;
427 }
428 return evnp.getEnvVars();
429 }
430
431
432 private void checkPort135Access(PrintStream logger, String name, InetAddress host) throws IOException {
433 Socket s = new Socket();
434 try {
435 s.connect(new InetSocketAddress(host,135),5000);
436 } catch (IOException e) {
437 logger.println("Failed to connect to port 135 of "+name+". Is Windows firewall blocking this port? Or did you disable DCOM service?");
438 // again, let it continue.
439 } finally {
440 s.close();
441 }
442 }
443
444 /**
445 * Determines the host name (or the IP address) to connect to.
446 */
447 protected String determineHost(Computer c) {
448 // If host not provided, default to the slave name
449 if (StringUtils.isBlank(host)) {
450 return c.getName();
451 } else {
452 return host;
453 }
454 }
455
456 private String createAndCopyJenkinsSlaveXml(String java, String serviceId, PrintStream logger, SmbFile remoteRoot) throws IOException {
457 logger.println(Messages.ManagedWindowsServiceLauncher_CopyingSlaveXml());
458 String xml = generateSlaveXml(serviceId,
459 java + "w.exe", vmargs, "-tcp %BASE%\\port.txt");
460 copyStreamAndClose(new ByteArrayInputStream(xml.getBytes("UTF-8")), new SmbFile(remoteRoot,"jenkins-slave.xml").getOutputStream());
461 return xml;
462 }
463
464 private void copySlaveJar(PrintStream logger, SmbFile remoteRoot) throws IOException {
465 // copy slave.jar
466 logger.println(Messages.ManagedWindowsServiceLauncher_CopyingSlaveJar());
467 copyStreamAndClose(Jenkins.getInstance().getJnlpJars("slave.jar").getURL().openStream(), new SmbFile(remoteRoot,"slave.jar").getOutputStream());
468 }
469
470 private int readSmbFile(SmbFile f) throws IOException {
471 InputStream in=null;
472 try {
473 in = f.getInputStream();
474 return Integer.parseInt(IOUtils.toString(in));
475 } finally {
476 IOUtils.closeQuietly(in);
477 }
478 }
479
480 @Override
481 public void afterDisconnect(SlaveComputer computer, TaskListener listener) {
482 try {
483 JIDefaultAuthInfoImpl auth = createAuth();
484 JISession session = JISession.createSession(auth);
485 session.setGlobalSocketTimeout(60000);
486 SWbemServices services = WMI.connect(session, determineHost(computer));
487 String id = generateServiceId(computer.getNode().getRemoteFS());
488 Win32Service slaveService = services.getService(id);
489 if(slaveService!=null) {
490 listener.getLogger().println(Messages.ManagedWindowsServiceLauncher_StoppingService());
491 slaveService.StopService();
492 listener.getLogger().println(Messages.ManagedWindowsServiceLauncher_UnregisteringService());
493 slaveService.Delete();
494 }
495 //destroy session to free the socket
496 JISession.destroySession(session);
497 } catch (UnknownHostException e) {
498 e.printStackTrace(listener.error(e.getMessage()));
499 } catch (JIException e) {
500 e.printStackTrace(listener.error(e.getMessage()));
501 } catch (IOException e) {
502 e.printStackTrace(listener.error(e.getMessage()));
503 }
504 }
505
506 String generateServiceId(String slaveRoot) throws IOException {
507 return "jenkinsslave-"+slaveRoot.replace(':','_').replace('\\','_').replace('/','_');
508 }
509
510 String generateSlaveXml(String id, String java, String vmargs, String args) throws IOException {
511 String xml = org.apache.commons.io.IOUtils.toString(getClass().getResourceAsStream("/windows-service/jenkins-slave.xml"), "UTF-8");
512 xml = xml.replace("@ID@", id);
513 xml = xml.replace("@JAVA@", java);
514 xml = xml.replace("@VMARGS@", StringUtils.defaultString(vmargs));
515 xml = xml.replace("@ARGS@", args);
516 return xml;
517 }
518
519 @Extension
520 public static class DescriptorImpl extends Descriptor<ComputerLauncher> {
521 public String getDisplayName() {
522 return Messages.ManagedWindowsServiceLauncher_DisplayName();
523 }
524 }
525
526 private static final Logger JINTEROP_LOGGER = Logger.getLogger("org.jinterop");
527
528 static {
529 JINTEROP_LOGGER.setLevel(Level.WARNING);
530 }
531 }
532