1 /**
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 
19 package org.apache.hadoop.yarn.server.nodemanager.containermanager.launcher;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertThat;
23 import static org.junit.Assert.fail;
24 import static org.junit.matchers.JUnitMatchers.containsString;
25 import static org.mockito.Mockito.mock;
26 import static org.mockito.Mockito.when;
27 
28 import java.io.BufferedReader;
29 import java.io.File;
30 import java.io.FileOutputStream;
31 import java.io.FileReader;
32 import java.io.IOException;
33 import java.io.PrintStream;
34 import java.io.PrintWriter;
35 import java.nio.ByteBuffer;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.Collections;
39 import java.util.HashMap;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.StringTokenizer;
43 import java.util.jar.JarFile;
44 import java.util.jar.Manifest;
45 
46 import org.apache.commons.codec.binary.Base64;
47 import org.apache.hadoop.conf.Configuration;
48 import org.apache.hadoop.fs.FileUtil;
49 import org.apache.hadoop.fs.Path;
50 import org.apache.hadoop.fs.UnsupportedFileSystemException;
51 import org.apache.hadoop.security.token.SecretManager.InvalidToken;
52 import org.apache.hadoop.util.Shell;
53 import org.apache.hadoop.util.Shell.ExitCodeException;
54 import org.apache.hadoop.util.StringUtils;
55 import org.apache.hadoop.yarn.api.ApplicationConstants;
56 import org.apache.hadoop.yarn.api.ApplicationConstants.Environment;
57 import org.apache.hadoop.yarn.api.protocolrecords.GetContainerStatusesRequest;
58 import org.apache.hadoop.yarn.api.protocolrecords.StartContainerRequest;
59 import org.apache.hadoop.yarn.api.protocolrecords.StartContainersRequest;
60 import org.apache.hadoop.yarn.api.protocolrecords.StopContainersRequest;
61 import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
62 import org.apache.hadoop.yarn.api.records.ApplicationId;
63 import org.apache.hadoop.yarn.api.records.ContainerExitStatus;
64 import org.apache.hadoop.yarn.api.records.ContainerId;
65 import org.apache.hadoop.yarn.api.records.ContainerLaunchContext;
66 import org.apache.hadoop.yarn.api.records.ContainerState;
67 import org.apache.hadoop.yarn.api.records.ContainerStatus;
68 import org.apache.hadoop.yarn.api.records.LocalResource;
69 import org.apache.hadoop.yarn.api.records.LocalResourceType;
70 import org.apache.hadoop.yarn.api.records.LocalResourceVisibility;
71 import org.apache.hadoop.yarn.api.records.NodeId;
72 import org.apache.hadoop.yarn.api.records.Priority;
73 import org.apache.hadoop.yarn.api.records.Resource;
74 import org.apache.hadoop.yarn.api.records.Token;
75 import org.apache.hadoop.yarn.api.records.URL;
76 import org.apache.hadoop.yarn.conf.YarnConfiguration;
77 import org.apache.hadoop.yarn.event.Dispatcher;
78 import org.apache.hadoop.yarn.event.Event;
79 import org.apache.hadoop.yarn.event.EventHandler;
80 import org.apache.hadoop.yarn.security.ContainerTokenIdentifier;
81 import org.apache.hadoop.yarn.server.api.protocolrecords.NMContainerStatus;
82 import org.apache.hadoop.yarn.server.nodemanager.ContainerExecutor;
83 import org.apache.hadoop.yarn.server.nodemanager.ContainerExecutor.ExitCode;
84 import org.apache.hadoop.yarn.server.nodemanager.DefaultContainerExecutor;
85 import org.apache.hadoop.yarn.server.nodemanager.Context;
86 import org.apache.hadoop.yarn.server.nodemanager.NodeManager.NMContext;
87 import org.apache.hadoop.yarn.server.nodemanager.containermanager.BaseContainerManagerTest;
88 import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.Container;
89 import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.ContainerEventType;
90 import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.ContainerExitEvent;
91 import org.apache.hadoop.yarn.server.nodemanager.containermanager.launcher.ContainerLaunch.ShellScriptBuilder;
92 import org.apache.hadoop.yarn.server.nodemanager.containermanager.localizer.ContainerLocalizer;
93 import org.apache.hadoop.yarn.server.nodemanager.security.NMContainerTokenSecretManager;
94 import org.apache.hadoop.yarn.server.nodemanager.security.NMTokenSecretManagerInNM;
95 import org.apache.hadoop.yarn.server.security.ApplicationACLsManager;
96 import org.apache.hadoop.yarn.server.nodemanager.recovery.NMNullStateStoreService;
97 import org.apache.hadoop.yarn.server.utils.BuilderUtils;
98 import org.apache.hadoop.yarn.util.Apps;
99 import org.apache.hadoop.yarn.util.AuxiliaryServiceHelper;
100 import org.apache.hadoop.yarn.util.ConverterUtils;
101 import org.apache.hadoop.yarn.util.LinuxResourceCalculatorPlugin;
102 import org.apache.hadoop.yarn.util.ResourceCalculatorPlugin;
103 import org.junit.Assert;
104 import org.junit.Assume;
105 import org.junit.Before;
106 import org.junit.Test;
107 
108 public class TestContainerLaunch extends BaseContainerManagerTest {
109 
110   protected Context distContext = new NMContext(new NMContainerTokenSecretManager(
111     conf), new NMTokenSecretManagerInNM(), null,
112     new ApplicationACLsManager(conf), new NMNullStateStoreService()) {
113     public int getHttpPort() {
114       return HTTP_PORT;
115     };
116     public NodeId getNodeId() {
117       return NodeId.newInstance("ahost", 1234);
118     };
119   };
120 
TestContainerLaunch()121   public TestContainerLaunch() throws UnsupportedFileSystemException {
122     super();
123   }
124 
125   @Before
setup()126   public void setup() throws IOException {
127     conf.setClass(
128         YarnConfiguration.NM_CONTAINER_MON_RESOURCE_CALCULATOR,
129         LinuxResourceCalculatorPlugin.class, ResourceCalculatorPlugin.class);
130     super.setup();
131   }
132 
133   @Test
testSpecialCharSymlinks()134   public void testSpecialCharSymlinks() throws IOException  {
135 
136     File shellFile = null;
137     File tempFile = null;
138     String badSymlink = Shell.WINDOWS ? "foo@zz_#!-+bar.cmd" :
139       "foo@zz%_#*&!-+= bar()";
140     File symLinkFile = null;
141 
142     try {
143       shellFile = Shell.appendScriptExtension(tmpDir, "hello");
144       tempFile = Shell.appendScriptExtension(tmpDir, "temp");
145       String timeoutCommand = Shell.WINDOWS ? "@echo \"hello\"" :
146         "echo \"hello\"";
147       PrintWriter writer = new PrintWriter(new FileOutputStream(shellFile));
148       FileUtil.setExecutable(shellFile, true);
149       writer.println(timeoutCommand);
150       writer.close();
151 
152       Map<Path, List<String>> resources =
153           new HashMap<Path, List<String>>();
154       Path path = new Path(shellFile.getAbsolutePath());
155       resources.put(path, Arrays.asList(badSymlink));
156 
157       FileOutputStream fos = new FileOutputStream(tempFile);
158 
159       Map<String, String> env = new HashMap<String, String>();
160       List<String> commands = new ArrayList<String>();
161       if (Shell.WINDOWS) {
162         commands.add("cmd");
163         commands.add("/c");
164         commands.add("\"" + badSymlink + "\"");
165       } else {
166         commands.add("/bin/sh ./\\\"" + badSymlink + "\\\"");
167       }
168 
169       new DefaultContainerExecutor().writeLaunchEnv(fos, env, resources, commands);
170       fos.flush();
171       fos.close();
172       FileUtil.setExecutable(tempFile, true);
173 
174       Shell.ShellCommandExecutor shexc
175       = new Shell.ShellCommandExecutor(new String[]{tempFile.getAbsolutePath()}, tmpDir);
176 
177       shexc.execute();
178       assertEquals(shexc.getExitCode(), 0);
179       assert(shexc.getOutput().contains("hello"));
180 
181       symLinkFile = new File(tmpDir, badSymlink);
182     }
183     finally {
184       // cleanup
185       if (shellFile != null
186           && shellFile.exists()) {
187         shellFile.delete();
188       }
189       if (tempFile != null
190           && tempFile.exists()) {
191         tempFile.delete();
192       }
193       if (symLinkFile != null
194           && symLinkFile.exists()) {
195         symLinkFile.delete();
196       }
197     }
198   }
199 
200   // test the diagnostics are generated
201   @Test (timeout = 20000)
testInvalidSymlinkDiagnostics()202   public void testInvalidSymlinkDiagnostics() throws IOException  {
203 
204     File shellFile = null;
205     File tempFile = null;
206     String symLink = Shell.WINDOWS ? "test.cmd" :
207       "test";
208     File symLinkFile = null;
209 
210     try {
211       shellFile = Shell.appendScriptExtension(tmpDir, "hello");
212       tempFile = Shell.appendScriptExtension(tmpDir, "temp");
213       String timeoutCommand = Shell.WINDOWS ? "@echo \"hello\"" :
214         "echo \"hello\"";
215       PrintWriter writer = new PrintWriter(new FileOutputStream(shellFile));
216       FileUtil.setExecutable(shellFile, true);
217       writer.println(timeoutCommand);
218       writer.close();
219 
220       Map<Path, List<String>> resources =
221           new HashMap<Path, List<String>>();
222       //This is an invalid path and should throw exception because of No such file.
223       Path invalidPath = new Path(shellFile.getAbsolutePath()+"randomPath");
224       resources.put(invalidPath, Arrays.asList(symLink));
225       FileOutputStream fos = new FileOutputStream(tempFile);
226 
227       Map<String, String> env = new HashMap<String, String>();
228       List<String> commands = new ArrayList<String>();
229       if (Shell.WINDOWS) {
230         commands.add("cmd");
231         commands.add("/c");
232         commands.add("\"" + symLink + "\"");
233       } else {
234         commands.add("/bin/sh ./\\\"" + symLink + "\\\"");
235       }
236       new DefaultContainerExecutor().writeLaunchEnv(fos, env, resources, commands);
237       fos.flush();
238       fos.close();
239       FileUtil.setExecutable(tempFile, true);
240 
241       Shell.ShellCommandExecutor shexc
242       = new Shell.ShellCommandExecutor(new String[]{tempFile.getAbsolutePath()}, tmpDir);
243       String diagnostics = null;
244       try {
245         shexc.execute();
246         Assert.fail("Should catch exception");
247       } catch(ExitCodeException e){
248         diagnostics = e.getMessage();
249       }
250       Assert.assertNotNull(diagnostics);
251       Assert.assertTrue(shexc.getExitCode() != 0);
252       symLinkFile = new File(tmpDir, symLink);
253     }
254     finally {
255       // cleanup
256       if (shellFile != null
257           && shellFile.exists()) {
258         shellFile.delete();
259       }
260       if (tempFile != null
261           && tempFile.exists()) {
262         tempFile.delete();
263       }
264       if (symLinkFile != null
265           && symLinkFile.exists()) {
266         symLinkFile.delete();
267       }
268     }
269   }
270 
271   @Test (timeout = 20000)
testInvalidEnvSyntaxDiagnostics()272   public void testInvalidEnvSyntaxDiagnostics() throws IOException  {
273 
274     File shellFile = null;
275     try {
276       shellFile = Shell.appendScriptExtension(tmpDir, "hello");
277       Map<Path, List<String>> resources =
278           new HashMap<Path, List<String>>();
279       FileOutputStream fos = new FileOutputStream(shellFile);
280       FileUtil.setExecutable(shellFile, true);
281 
282       Map<String, String> env = new HashMap<String, String>();
283       // invalid env
284       env.put(
285           "APPLICATION_WORKFLOW_CONTEXT", "{\"workflowId\":\"609f91c5cd83\"," +
286           "\"workflowName\":\"\n\ninsert table " +
287           "\npartition (cd_education_status)\nselect cd_demo_sk, cd_gender, " );
288       List<String> commands = new ArrayList<String>();
289       new DefaultContainerExecutor().writeLaunchEnv(fos, env, resources, commands);
290       fos.flush();
291       fos.close();
292 
293       // It is supposed that LANG is set as C.
294       Map<String, String> cmdEnv = new HashMap<String, String>();
295       cmdEnv.put("LANG", "C");
296       Shell.ShellCommandExecutor shexc
297       = new Shell.ShellCommandExecutor(new String[]{shellFile.getAbsolutePath()},
298         tmpDir, cmdEnv);
299       String diagnostics = null;
300       try {
301         shexc.execute();
302         Assert.fail("Should catch exception");
303       } catch(ExitCodeException e){
304         diagnostics = e.getMessage();
305       }
306       Assert.assertTrue(diagnostics.contains(Shell.WINDOWS ?
307           "is not recognized as an internal or external command" :
308           "command not found"));
309       Assert.assertTrue(shexc.getExitCode() != 0);
310     }
311     finally {
312       // cleanup
313       if (shellFile != null
314           && shellFile.exists()) {
315         shellFile.delete();
316       }
317     }
318   }
319 
320   @Test(timeout = 10000)
testEnvExpansion()321   public void testEnvExpansion() throws IOException {
322     Path logPath = new Path("/nm/container/logs");
323     String input =
324         Apps.crossPlatformify("HADOOP_HOME") + "/share/hadoop/common/*"
325             + ApplicationConstants.CLASS_PATH_SEPARATOR
326             + Apps.crossPlatformify("HADOOP_HOME") + "/share/hadoop/common/lib/*"
327             + ApplicationConstants.CLASS_PATH_SEPARATOR
328             + Apps.crossPlatformify("HADOOP_LOG_HOME")
329             + ApplicationConstants.LOG_DIR_EXPANSION_VAR;
330 
331     String res = ContainerLaunch.expandEnvironment(input, logPath);
332 
333     if (Shell.WINDOWS) {
334       Assert.assertEquals("%HADOOP_HOME%/share/hadoop/common/*;"
335           + "%HADOOP_HOME%/share/hadoop/common/lib/*;"
336           + "%HADOOP_LOG_HOME%/nm/container/logs", res);
337     } else {
338       Assert.assertEquals("$HADOOP_HOME/share/hadoop/common/*:"
339           + "$HADOOP_HOME/share/hadoop/common/lib/*:"
340           + "$HADOOP_LOG_HOME/nm/container/logs", res);
341     }
342     System.out.println(res);
343   }
344 
345   @Test (timeout = 20000)
testContainerLaunchStdoutAndStderrDiagnostics()346   public void testContainerLaunchStdoutAndStderrDiagnostics() throws IOException {
347 
348     File shellFile = null;
349     try {
350       shellFile = Shell.appendScriptExtension(tmpDir, "hello");
351       // echo "hello" to stdout and "error" to stderr and exit code with 2;
352       String command = Shell.WINDOWS ?
353           "@echo \"hello\" & @echo \"error\" 1>&2 & exit /b 2" :
354           "echo \"hello\"; echo \"error\" 1>&2; exit 2;";
355       PrintWriter writer = new PrintWriter(new FileOutputStream(shellFile));
356       FileUtil.setExecutable(shellFile, true);
357       writer.println(command);
358       writer.close();
359       Map<Path, List<String>> resources =
360           new HashMap<Path, List<String>>();
361       FileOutputStream fos = new FileOutputStream(shellFile, true);
362 
363       Map<String, String> env = new HashMap<String, String>();
364       List<String> commands = new ArrayList<String>();
365       commands.add(command);
366       ContainerExecutor exec = new DefaultContainerExecutor();
367       exec.writeLaunchEnv(fos, env, resources, commands);
368       fos.flush();
369       fos.close();
370 
371       Shell.ShellCommandExecutor shexc
372       = new Shell.ShellCommandExecutor(new String[]{shellFile.getAbsolutePath()}, tmpDir);
373       String diagnostics = null;
374       try {
375         shexc.execute();
376         Assert.fail("Should catch exception");
377       } catch(ExitCodeException e){
378         diagnostics = e.getMessage();
379       }
380       // test stderr
381       Assert.assertTrue(diagnostics.contains("error"));
382       // test stdout
383       Assert.assertTrue(shexc.getOutput().contains("hello"));
384       Assert.assertTrue(shexc.getExitCode() == 2);
385     }
386     finally {
387       // cleanup
388       if (shellFile != null
389           && shellFile.exists()) {
390         shellFile.delete();
391       }
392     }
393   }
394 
395   @Test
testPrependDistcache()396   public void testPrependDistcache() throws Exception {
397 
398     // Test is only relevant on Windows
399     Assume.assumeTrue(Shell.WINDOWS);
400 
401     ContainerLaunchContext containerLaunchContext =
402         recordFactory.newRecordInstance(ContainerLaunchContext.class);
403 
404     ApplicationId appId = ApplicationId.newInstance(0, 0);
405     ApplicationAttemptId appAttemptId =
406         ApplicationAttemptId.newInstance(appId, 1);
407 
408     ContainerId cId = ContainerId.newContainerId(appAttemptId, 0);
409     Map<String, String> userSetEnv = new HashMap<String, String>();
410     userSetEnv.put(Environment.CONTAINER_ID.name(), "user_set_container_id");
411     userSetEnv.put(Environment.NM_HOST.name(), "user_set_NM_HOST");
412     userSetEnv.put(Environment.NM_PORT.name(), "user_set_NM_PORT");
413     userSetEnv.put(Environment.NM_HTTP_PORT.name(), "user_set_NM_HTTP_PORT");
414     userSetEnv.put(Environment.LOCAL_DIRS.name(), "user_set_LOCAL_DIR");
415     userSetEnv.put(Environment.USER.key(), "user_set_" +
416       Environment.USER.key());
417     userSetEnv.put(Environment.LOGNAME.name(), "user_set_LOGNAME");
418     userSetEnv.put(Environment.PWD.name(), "user_set_PWD");
419     userSetEnv.put(Environment.HOME.name(), "user_set_HOME");
420     userSetEnv.put(Environment.CLASSPATH.name(), "APATH");
421     containerLaunchContext.setEnvironment(userSetEnv);
422     Container container = mock(Container.class);
423     when(container.getContainerId()).thenReturn(cId);
424     when(container.getLaunchContext()).thenReturn(containerLaunchContext);
425     when(container.getLocalizedResources()).thenReturn(null);
426     Dispatcher dispatcher = mock(Dispatcher.class);
427     EventHandler eventHandler = new EventHandler() {
428       public void handle(Event event) {
429         Assert.assertTrue(event instanceof ContainerExitEvent);
430         ContainerExitEvent exitEvent = (ContainerExitEvent) event;
431         Assert.assertEquals(ContainerEventType.CONTAINER_EXITED_WITH_FAILURE,
432             exitEvent.getType());
433       }
434     };
435     when(dispatcher.getEventHandler()).thenReturn(eventHandler);
436 
437     Configuration conf = new Configuration();
438 
439     ContainerLaunch launch = new ContainerLaunch(distContext, conf,
440         dispatcher, exec, null, container, dirsHandler, containerManager);
441 
442     String testDir = System.getProperty("test.build.data",
443         "target/test-dir");
444     Path pwd = new Path(testDir);
445     List<Path> appDirs = new ArrayList<Path>();
446     List<String> containerLogs = new ArrayList<String>();
447 
448     Map<Path, List<String>> resources = new HashMap<Path, List<String>>();
449     Path userjar = new Path("user.jar");
450     List<String> lpaths = new ArrayList<String>();
451     lpaths.add("userjarlink.jar");
452     resources.put(userjar, lpaths);
453 
454     Path nmp = new Path(testDir);
455 
456     launch.sanitizeEnv(
457       userSetEnv, pwd, appDirs, containerLogs, resources, nmp);
458 
459     List<String> result =
460       getJarManifestClasspath(userSetEnv.get(Environment.CLASSPATH.name()));
461 
462     Assert.assertTrue(result.size() > 1);
463     Assert.assertTrue(
464       result.get(result.size() - 1).endsWith("userjarlink.jar"));
465 
466     //Then, with user classpath first
467     userSetEnv.put(Environment.CLASSPATH_PREPEND_DISTCACHE.name(), "true");
468 
469     cId = ContainerId.newContainerId(appAttemptId, 1);
470     when(container.getContainerId()).thenReturn(cId);
471 
472     launch = new ContainerLaunch(distContext, conf,
473         dispatcher, exec, null, container, dirsHandler, containerManager);
474 
475     launch.sanitizeEnv(
476       userSetEnv, pwd, appDirs, containerLogs, resources, nmp);
477 
478     result =
479       getJarManifestClasspath(userSetEnv.get(Environment.CLASSPATH.name()));
480 
481     Assert.assertTrue(result.size() > 1);
482     Assert.assertTrue(
483       result.get(0).endsWith("userjarlink.jar"));
484 
485   }
486 
getJarManifestClasspath(String path)487   private static List<String> getJarManifestClasspath(String path)
488       throws Exception {
489     List<String> classpath = new ArrayList<String>();
490     JarFile jarFile = new JarFile(path);
491     Manifest manifest = jarFile.getManifest();
492     String cps = manifest.getMainAttributes().getValue("Class-Path");
493     StringTokenizer cptok = new StringTokenizer(cps);
494     while (cptok.hasMoreTokens()) {
495       String cpentry = cptok.nextToken();
496       classpath.add(cpentry);
497     }
498     return classpath;
499   }
500 
501   /**
502    * See if environment variable is forwarded using sanitizeEnv.
503    * @throws Exception
504    */
505   @Test (timeout = 60000)
testContainerEnvVariables()506   public void testContainerEnvVariables() throws Exception {
507     containerManager.start();
508 
509     ContainerLaunchContext containerLaunchContext =
510         recordFactory.newRecordInstance(ContainerLaunchContext.class);
511 
512     // ////// Construct the Container-id
513     ApplicationId appId = ApplicationId.newInstance(0, 0);
514     ApplicationAttemptId appAttemptId =
515         ApplicationAttemptId.newInstance(appId, 1);
516 
517     ContainerId cId = ContainerId.newContainerId(appAttemptId, 0);
518     Map<String, String> userSetEnv = new HashMap<String, String>();
519     userSetEnv.put(Environment.CONTAINER_ID.name(), "user_set_container_id");
520     userSetEnv.put(Environment.NM_HOST.name(), "user_set_NM_HOST");
521     userSetEnv.put(Environment.NM_PORT.name(), "user_set_NM_PORT");
522     userSetEnv.put(Environment.NM_HTTP_PORT.name(), "user_set_NM_HTTP_PORT");
523     userSetEnv.put(Environment.LOCAL_DIRS.name(), "user_set_LOCAL_DIR");
524     userSetEnv.put(Environment.USER.key(), "user_set_" +
525     	Environment.USER.key());
526     userSetEnv.put(Environment.LOGNAME.name(), "user_set_LOGNAME");
527     userSetEnv.put(Environment.PWD.name(), "user_set_PWD");
528     userSetEnv.put(Environment.HOME.name(), "user_set_HOME");
529     containerLaunchContext.setEnvironment(userSetEnv);
530 
531     File scriptFile = Shell.appendScriptExtension(tmpDir, "scriptFile");
532     PrintWriter fileWriter = new PrintWriter(scriptFile);
533     File processStartFile =
534         new File(tmpDir, "env_vars.txt").getAbsoluteFile();
535     if (Shell.WINDOWS) {
536       fileWriter.println("@echo " + Environment.CONTAINER_ID.$() + "> "
537           + processStartFile);
538       fileWriter.println("@echo " + Environment.NM_HOST.$() + ">> "
539           + processStartFile);
540       fileWriter.println("@echo " + Environment.NM_PORT.$() + ">> "
541           + processStartFile);
542       fileWriter.println("@echo " + Environment.NM_HTTP_PORT.$() + ">> "
543           + processStartFile);
544       fileWriter.println("@echo " + Environment.LOCAL_DIRS.$() + ">> "
545           + processStartFile);
546       fileWriter.println("@echo " + Environment.USER.$() + ">> "
547     	  + processStartFile);
548       fileWriter.println("@echo " + Environment.LOGNAME.$() + ">> "
549           + processStartFile);
550       fileWriter.println("@echo " + Environment.PWD.$() + ">> "
551     	  + processStartFile);
552       fileWriter.println("@echo " + Environment.HOME.$() + ">> "
553           + processStartFile);
554       for (String serviceName : containerManager.getAuxServiceMetaData()
555           .keySet()) {
556         fileWriter.println("@echo %" + AuxiliaryServiceHelper.NM_AUX_SERVICE
557             + serviceName + "%>> "
558             + processStartFile);
559       }
560       fileWriter.println("@echo " + cId + ">> " + processStartFile);
561       fileWriter.println("@ping -n 100 127.0.0.1 >nul");
562     } else {
563       fileWriter.write("\numask 0"); // So that start file is readable by the test
564       fileWriter.write("\necho $" + Environment.CONTAINER_ID.name() + " > "
565           + processStartFile);
566       fileWriter.write("\necho $" + Environment.NM_HOST.name() + " >> "
567           + processStartFile);
568       fileWriter.write("\necho $" + Environment.NM_PORT.name() + " >> "
569           + processStartFile);
570       fileWriter.write("\necho $" + Environment.NM_HTTP_PORT.name() + " >> "
571           + processStartFile);
572       fileWriter.write("\necho $" + Environment.LOCAL_DIRS.name() + " >> "
573           + processStartFile);
574       fileWriter.write("\necho $" + Environment.USER.name() + " >> "
575           + processStartFile);
576       fileWriter.write("\necho $" + Environment.LOGNAME.name() + " >> "
577           + processStartFile);
578       fileWriter.write("\necho $" + Environment.PWD.name() + " >> "
579           + processStartFile);
580       fileWriter.write("\necho $" + Environment.HOME.name() + " >> "
581           + processStartFile);
582       for (String serviceName : containerManager.getAuxServiceMetaData()
583           .keySet()) {
584         fileWriter.write("\necho $" + AuxiliaryServiceHelper.NM_AUX_SERVICE
585             + serviceName + " >> "
586             + processStartFile);
587       }
588       fileWriter.write("\necho $$ >> " + processStartFile);
589       fileWriter.write("\nexec sleep 100");
590     }
591     fileWriter.close();
592 
593     // upload the script file so that the container can run it
594     URL resource_alpha =
595         ConverterUtils.getYarnUrlFromPath(localFS
596             .makeQualified(new Path(scriptFile.getAbsolutePath())));
597     LocalResource rsrc_alpha =
598         recordFactory.newRecordInstance(LocalResource.class);
599     rsrc_alpha.setResource(resource_alpha);
600     rsrc_alpha.setSize(-1);
601     rsrc_alpha.setVisibility(LocalResourceVisibility.APPLICATION);
602     rsrc_alpha.setType(LocalResourceType.FILE);
603     rsrc_alpha.setTimestamp(scriptFile.lastModified());
604     String destinationFile = "dest_file";
605     Map<String, LocalResource> localResources =
606         new HashMap<String, LocalResource>();
607     localResources.put(destinationFile, rsrc_alpha);
608     containerLaunchContext.setLocalResources(localResources);
609 
610     // set up the rest of the container
611     List<String> commands = Arrays.asList(Shell.getRunScriptCommand(scriptFile));
612     containerLaunchContext.setCommands(commands);
613     StartContainerRequest scRequest =
614         StartContainerRequest.newInstance(containerLaunchContext,
615           createContainerToken(cId, Priority.newInstance(0), 0));
616     List<StartContainerRequest> list = new ArrayList<StartContainerRequest>();
617     list.add(scRequest);
618     StartContainersRequest allRequests =
619         StartContainersRequest.newInstance(list);
620     containerManager.startContainers(allRequests);
621 
622     int timeoutSecs = 0;
623     while (!processStartFile.exists() && timeoutSecs++ < 20) {
624       Thread.sleep(1000);
625       LOG.info("Waiting for process start-file to be created");
626     }
627     Assert.assertTrue("ProcessStartFile doesn't exist!",
628         processStartFile.exists());
629 
630     // Now verify the contents of the file
631     List<String> localDirs = dirsHandler.getLocalDirs();
632     List<String> logDirs = dirsHandler.getLogDirs();
633 
634     List<Path> appDirs = new ArrayList<Path>(localDirs.size());
635     for (String localDir : localDirs) {
636       Path usersdir = new Path(localDir, ContainerLocalizer.USERCACHE);
637       Path userdir = new Path(usersdir, user);
638       Path appsdir = new Path(userdir, ContainerLocalizer.APPCACHE);
639       appDirs.add(new Path(appsdir, appId.toString()));
640     }
641     List<String> containerLogDirs = new ArrayList<String>();
642     String relativeContainerLogDir = ContainerLaunch
643         .getRelativeContainerLogDir(appId.toString(), cId.toString());
644     for(String logDir : logDirs){
645       containerLogDirs.add(logDir + Path.SEPARATOR + relativeContainerLogDir);
646     }
647     BufferedReader reader =
648         new BufferedReader(new FileReader(processStartFile));
649     Assert.assertEquals(cId.toString(), reader.readLine());
650     Assert.assertEquals(context.getNodeId().getHost(), reader.readLine());
651     Assert.assertEquals(String.valueOf(context.getNodeId().getPort()),
652       reader.readLine());
653     Assert.assertEquals(String.valueOf(HTTP_PORT), reader.readLine());
654     Assert.assertEquals(StringUtils.join(",", appDirs), reader.readLine());
655     Assert.assertEquals(user, reader.readLine());
656     Assert.assertEquals(user, reader.readLine());
657     String obtainedPWD = reader.readLine();
658     boolean found = false;
659     for (Path localDir : appDirs) {
660       if (new Path(localDir, cId.toString()).toString().equals(obtainedPWD)) {
661         found = true;
662         break;
663       }
664     }
665     Assert.assertTrue("Wrong local-dir found : " + obtainedPWD, found);
666     Assert.assertEquals(
667         conf.get(
668               YarnConfiguration.NM_USER_HOME_DIR,
669               YarnConfiguration.DEFAULT_NM_USER_HOME_DIR),
670         reader.readLine());
671 
672     for (String serviceName : containerManager.getAuxServiceMetaData().keySet()) {
673       Assert.assertEquals(
674           containerManager.getAuxServiceMetaData().get(serviceName),
675           ByteBuffer.wrap(Base64.decodeBase64(reader.readLine().getBytes())));
676     }
677 
678     Assert.assertEquals(cId.toString(), containerLaunchContext
679         .getEnvironment().get(Environment.CONTAINER_ID.name()));
680     Assert.assertEquals(context.getNodeId().getHost(), containerLaunchContext
681       .getEnvironment().get(Environment.NM_HOST.name()));
682     Assert.assertEquals(String.valueOf(context.getNodeId().getPort()),
683       containerLaunchContext.getEnvironment().get(Environment.NM_PORT.name()));
684     Assert.assertEquals(String.valueOf(HTTP_PORT), containerLaunchContext
685       .getEnvironment().get(Environment.NM_HTTP_PORT.name()));
686     Assert.assertEquals(StringUtils.join(",", appDirs), containerLaunchContext
687         .getEnvironment().get(Environment.LOCAL_DIRS.name()));
688     Assert.assertEquals(StringUtils.join(",", containerLogDirs),
689       containerLaunchContext.getEnvironment().get(Environment.LOG_DIRS.name()));
690     Assert.assertEquals(user, containerLaunchContext.getEnvironment()
691     	.get(Environment.USER.name()));
692     Assert.assertEquals(user, containerLaunchContext.getEnvironment()
693     	.get(Environment.LOGNAME.name()));
694     found = false;
695     obtainedPWD =
696         containerLaunchContext.getEnvironment().get(Environment.PWD.name());
697     for (Path localDir : appDirs) {
698       if (new Path(localDir, cId.toString()).toString().equals(obtainedPWD)) {
699         found = true;
700         break;
701       }
702     }
703     Assert.assertTrue("Wrong local-dir found : " + obtainedPWD, found);
704     Assert.assertEquals(
705         conf.get(
706     	        YarnConfiguration.NM_USER_HOME_DIR,
707     	        YarnConfiguration.DEFAULT_NM_USER_HOME_DIR),
708     	containerLaunchContext.getEnvironment()
709     		.get(Environment.HOME.name()));
710 
711     // Get the pid of the process
712     String pid = reader.readLine().trim();
713     // No more lines
714     Assert.assertEquals(null, reader.readLine());
715 
716     // Now test the stop functionality.
717 
718     // Assert that the process is alive
719     Assert.assertTrue("Process is not alive!",
720       DefaultContainerExecutor.containerIsAlive(pid));
721     // Once more
722     Assert.assertTrue("Process is not alive!",
723       DefaultContainerExecutor.containerIsAlive(pid));
724 
725     // Now test the stop functionality.
726     List<ContainerId> containerIds = new ArrayList<ContainerId>();
727     containerIds.add(cId);
728     StopContainersRequest stopRequest =
729         StopContainersRequest.newInstance(containerIds);
730     containerManager.stopContainers(stopRequest);
731 
732     BaseContainerManagerTest.waitForContainerState(containerManager, cId,
733         ContainerState.COMPLETE);
734 
735     GetContainerStatusesRequest gcsRequest =
736         GetContainerStatusesRequest.newInstance(containerIds);
737     ContainerStatus containerStatus =
738         containerManager.getContainerStatuses(gcsRequest).getContainerStatuses().get(0);
739     int expectedExitCode = ContainerExitStatus.KILLED_BY_APPMASTER;
740     Assert.assertEquals(expectedExitCode, containerStatus.getExitStatus());
741 
742     // Assert that the process is not alive anymore
743     Assert.assertFalse("Process is still alive!",
744       DefaultContainerExecutor.containerIsAlive(pid));
745   }
746 
747   @Test (timeout = 5000)
testAuxiliaryServiceHelper()748   public void testAuxiliaryServiceHelper() throws Exception {
749     Map<String, String> env = new HashMap<String, String>();
750     String serviceName = "testAuxiliaryService";
751     ByteBuffer bb = ByteBuffer.wrap("testAuxiliaryService".getBytes());
752     AuxiliaryServiceHelper.setServiceDataIntoEnv(serviceName, bb, env);
753     Assert.assertEquals(bb,
754         AuxiliaryServiceHelper.getServiceDataFromEnv(serviceName, env));
755   }
756 
internalKillTest(boolean delayed)757   private void internalKillTest(boolean delayed) throws Exception {
758     conf.setLong(YarnConfiguration.NM_SLEEP_DELAY_BEFORE_SIGKILL_MS,
759       delayed ? 1000 : 0);
760     containerManager.start();
761 
762     // ////// Construct the Container-id
763     ApplicationId appId = ApplicationId.newInstance(1, 1);
764     ApplicationAttemptId appAttemptId =
765         ApplicationAttemptId.newInstance(appId, 1);
766     ContainerId cId = ContainerId.newContainerId(appAttemptId, 0);
767     File processStartFile =
768         new File(tmpDir, "pid.txt").getAbsoluteFile();
769 
770     // setup a script that can handle sigterm gracefully
771     File scriptFile = Shell.appendScriptExtension(tmpDir, "testscript");
772     PrintWriter writer = new PrintWriter(new FileOutputStream(scriptFile));
773     if (Shell.WINDOWS) {
774       writer.println("@echo \"Running testscript for delayed kill\"");
775       writer.println("@echo \"Writing pid to start file\"");
776       writer.println("@echo " + cId + "> " + processStartFile);
777       writer.println("@ping -n 100 127.0.0.1 >nul");
778     } else {
779       writer.println("#!/bin/bash\n\n");
780       writer.println("echo \"Running testscript for delayed kill\"");
781       writer.println("hello=\"Got SIGTERM\"");
782       writer.println("umask 0");
783       writer.println("trap \"echo $hello >> " + processStartFile + "\" SIGTERM");
784       writer.println("echo \"Writing pid to start file\"");
785       writer.println("echo $$ >> " + processStartFile);
786       writer.println("while true; do\nsleep 1s;\ndone");
787     }
788     writer.close();
789     FileUtil.setExecutable(scriptFile, true);
790 
791     ContainerLaunchContext containerLaunchContext =
792         recordFactory.newRecordInstance(ContainerLaunchContext.class);
793 
794     // upload the script file so that the container can run it
795     URL resource_alpha =
796         ConverterUtils.getYarnUrlFromPath(localFS
797             .makeQualified(new Path(scriptFile.getAbsolutePath())));
798     LocalResource rsrc_alpha =
799         recordFactory.newRecordInstance(LocalResource.class);
800     rsrc_alpha.setResource(resource_alpha);
801     rsrc_alpha.setSize(-1);
802     rsrc_alpha.setVisibility(LocalResourceVisibility.APPLICATION);
803     rsrc_alpha.setType(LocalResourceType.FILE);
804     rsrc_alpha.setTimestamp(scriptFile.lastModified());
805     String destinationFile = "dest_file.sh";
806     Map<String, LocalResource> localResources =
807         new HashMap<String, LocalResource>();
808     localResources.put(destinationFile, rsrc_alpha);
809     containerLaunchContext.setLocalResources(localResources);
810 
811     // set up the rest of the container
812     List<String> commands = Arrays.asList(Shell.getRunScriptCommand(scriptFile));
813     containerLaunchContext.setCommands(commands);
814     Priority priority = Priority.newInstance(10);
815     long createTime = 1234;
816     Token containerToken = createContainerToken(cId, priority, createTime);
817 
818     StartContainerRequest scRequest =
819         StartContainerRequest.newInstance(containerLaunchContext,
820           containerToken);
821     List<StartContainerRequest> list = new ArrayList<StartContainerRequest>();
822     list.add(scRequest);
823     StartContainersRequest allRequests =
824         StartContainersRequest.newInstance(list);
825     containerManager.startContainers(allRequests);
826 
827     int timeoutSecs = 0;
828     while (!processStartFile.exists() && timeoutSecs++ < 20) {
829       Thread.sleep(1000);
830       LOG.info("Waiting for process start-file to be created");
831     }
832     Assert.assertTrue("ProcessStartFile doesn't exist!",
833         processStartFile.exists());
834 
835     NMContainerStatus nmContainerStatus =
836         containerManager.getContext().getContainers().get(cId)
837           .getNMContainerStatus();
838     Assert.assertEquals(priority, nmContainerStatus.getPriority());
839 
840     // Now test the stop functionality.
841     List<ContainerId> containerIds = new ArrayList<ContainerId>();
842     containerIds.add(cId);
843     StopContainersRequest stopRequest =
844         StopContainersRequest.newInstance(containerIds);
845     containerManager.stopContainers(stopRequest);
846 
847     BaseContainerManagerTest.waitForContainerState(containerManager, cId,
848         ContainerState.COMPLETE);
849 
850     // if delayed container stop sends a sigterm followed by a sigkill
851     // otherwise sigkill is sent immediately
852     GetContainerStatusesRequest gcsRequest =
853         GetContainerStatusesRequest.newInstance(containerIds);
854 
855     ContainerStatus containerStatus =
856         containerManager.getContainerStatuses(gcsRequest)
857           .getContainerStatuses().get(0);
858     Assert.assertEquals(ContainerExitStatus.KILLED_BY_APPMASTER,
859         containerStatus.getExitStatus());
860 
861     // Now verify the contents of the file.  Script generates a message when it
862     // receives a sigterm so we look for that.  We cannot perform this check on
863     // Windows, because the process is not notified when killed by winutils.
864     // There is no way for the process to trap and respond.  Instead, we can
865     // verify that the job object with ID matching container ID no longer exists.
866     if (Shell.WINDOWS || !delayed) {
867       Assert.assertFalse("Process is still alive!",
868         DefaultContainerExecutor.containerIsAlive(cId.toString()));
869     } else {
870       BufferedReader reader =
871           new BufferedReader(new FileReader(processStartFile));
872 
873       boolean foundSigTermMessage = false;
874       while (true) {
875         String line = reader.readLine();
876         if (line == null) {
877           break;
878         }
879         if (line.contains("SIGTERM")) {
880           foundSigTermMessage = true;
881           break;
882         }
883       }
884       Assert.assertTrue("Did not find sigterm message", foundSigTermMessage);
885       reader.close();
886     }
887   }
888 
889   @Test (timeout = 30000)
testDelayedKill()890   public void testDelayedKill() throws Exception {
891     internalKillTest(true);
892   }
893 
894   @Test (timeout = 30000)
testImmediateKill()895   public void testImmediateKill() throws Exception {
896     internalKillTest(false);
897   }
898 
899   @SuppressWarnings("rawtypes")
900   @Test (timeout = 10000)
testCallFailureWithNullLocalizedResources()901   public void testCallFailureWithNullLocalizedResources() {
902     Container container = mock(Container.class);
903     when(container.getContainerId()).thenReturn(ContainerId.newContainerId(
904         ApplicationAttemptId.newInstance(ApplicationId.newInstance(
905             System.currentTimeMillis(), 1), 1), 1));
906     ContainerLaunchContext clc = mock(ContainerLaunchContext.class);
907     when(clc.getCommands()).thenReturn(Collections.<String>emptyList());
908     when(container.getLaunchContext()).thenReturn(clc);
909     when(container.getLocalizedResources()).thenReturn(null);
910     Dispatcher dispatcher = mock(Dispatcher.class);
911     EventHandler eventHandler = new EventHandler() {
912       public void handle(Event event) {
913         Assert.assertTrue(event instanceof ContainerExitEvent);
914         ContainerExitEvent exitEvent = (ContainerExitEvent) event;
915         Assert.assertEquals(ContainerEventType.CONTAINER_EXITED_WITH_FAILURE,
916             exitEvent.getType());
917       }
918     };
919     when(dispatcher.getEventHandler()).thenReturn(eventHandler);
920     ContainerLaunch launch = new ContainerLaunch(context, new Configuration(),
921         dispatcher, exec, null, container, dirsHandler, containerManager);
922     launch.call();
923   }
924 
createContainerToken(ContainerId cId, Priority priority, long createTime)925   protected Token createContainerToken(ContainerId cId, Priority priority,
926       long createTime) throws InvalidToken {
927     Resource r = BuilderUtils.newResource(1024, 1);
928     ContainerTokenIdentifier containerTokenIdentifier =
929         new ContainerTokenIdentifier(cId, context.getNodeId().toString(), user,
930           r, System.currentTimeMillis() + 10000L, 123, DUMMY_RM_IDENTIFIER,
931           priority, createTime);
932     Token containerToken =
933         BuilderUtils.newContainerToken(
934           context.getNodeId(),
935           context.getContainerTokenSecretManager().retrievePassword(
936             containerTokenIdentifier), containerTokenIdentifier);
937     return containerToken;
938   }
939 
940   /**
941    * Test that script exists with non-zero exit code when command fails.
942    * @throws IOException
943    */
944   @Test (timeout = 10000)
testShellScriptBuilderNonZeroExitCode()945   public void testShellScriptBuilderNonZeroExitCode() throws IOException {
946     ShellScriptBuilder builder = ShellScriptBuilder.create();
947     builder.command(Arrays.asList(new String[] {"unknownCommand"}));
948     File shellFile = Shell.appendScriptExtension(tmpDir, "testShellScriptBuilderError");
949     PrintStream writer = new PrintStream(new FileOutputStream(shellFile));
950     builder.write(writer);
951     writer.close();
952     try {
953       FileUtil.setExecutable(shellFile, true);
954 
955       Shell.ShellCommandExecutor shexc = new Shell.ShellCommandExecutor(
956           new String[]{shellFile.getAbsolutePath()}, tmpDir);
957       try {
958         shexc.execute();
959         fail("builder shell command was expected to throw");
960       }
961       catch(IOException e) {
962         // expected
963         System.out.println("Received an expected exception: " + e.getMessage());
964       }
965     }
966     finally {
967       FileUtil.fullyDelete(shellFile);
968     }
969   }
970 
971   private static final String expectedMessage = "The command line has a length of";
972 
973   @Test (timeout = 10000)
testWindowsShellScriptBuilderCommand()974   public void testWindowsShellScriptBuilderCommand() throws IOException {
975     String callCmd = "@call ";
976 
977     // Test is only relevant on Windows
978     Assume.assumeTrue(Shell.WINDOWS);
979 
980     // The tests are built on assuming 8191 max command line length
981     assertEquals(8191, Shell.WINDOWS_MAX_SHELL_LENGHT);
982 
983     ShellScriptBuilder builder = ShellScriptBuilder.create();
984 
985     // Basic tests: less length, exact length, max+1 length
986     builder.command(Arrays.asList(
987         org.apache.commons.lang.StringUtils.repeat("A", 1024)));
988     builder.command(Arrays.asList(
989         org.apache.commons.lang.StringUtils.repeat(
990             "E", Shell.WINDOWS_MAX_SHELL_LENGHT - callCmd.length())));
991     try {
992       builder.command(Arrays.asList(
993           org.apache.commons.lang.StringUtils.repeat(
994               "X", Shell.WINDOWS_MAX_SHELL_LENGHT -callCmd.length() + 1)));
995       fail("longCommand was expected to throw");
996     } catch(IOException e) {
997       assertThat(e.getMessage(), containsString(expectedMessage));
998     }
999 
1000     // Composite tests, from parts: less, exact and +
1001     builder.command(Arrays.asList(
1002         org.apache.commons.lang.StringUtils.repeat("A", 1024),
1003         org.apache.commons.lang.StringUtils.repeat("A", 1024),
1004         org.apache.commons.lang.StringUtils.repeat("A", 1024)));
1005 
1006     // buildr.command joins the command parts with an extra space
1007     builder.command(Arrays.asList(
1008         org.apache.commons.lang.StringUtils.repeat("E", 4095),
1009         org.apache.commons.lang.StringUtils.repeat("E", 2047),
1010         org.apache.commons.lang.StringUtils.repeat("E", 2047 - callCmd.length())));
1011 
1012     try {
1013       builder.command(Arrays.asList(
1014           org.apache.commons.lang.StringUtils.repeat("X", 4095),
1015           org.apache.commons.lang.StringUtils.repeat("X", 2047),
1016           org.apache.commons.lang.StringUtils.repeat("X", 2048 - callCmd.length())));
1017       fail("long commands was expected to throw");
1018     } catch(IOException e) {
1019       assertThat(e.getMessage(), containsString(expectedMessage));
1020     }
1021   }
1022 
1023   @Test (timeout = 10000)
testWindowsShellScriptBuilderEnv()1024   public void testWindowsShellScriptBuilderEnv() throws IOException {
1025     // Test is only relevant on Windows
1026     Assume.assumeTrue(Shell.WINDOWS);
1027 
1028     // The tests are built on assuming 8191 max command line length
1029     assertEquals(8191, Shell.WINDOWS_MAX_SHELL_LENGHT);
1030 
1031     ShellScriptBuilder builder = ShellScriptBuilder.create();
1032 
1033     // test env
1034     builder.env("somekey", org.apache.commons.lang.StringUtils.repeat("A", 1024));
1035     builder.env("somekey", org.apache.commons.lang.StringUtils.repeat(
1036         "A", Shell.WINDOWS_MAX_SHELL_LENGHT - ("@set somekey=").length()));
1037     try {
1038       builder.env("somekey", org.apache.commons.lang.StringUtils.repeat(
1039           "A", Shell.WINDOWS_MAX_SHELL_LENGHT - ("@set somekey=").length()) + 1);
1040       fail("long env was expected to throw");
1041     } catch(IOException e) {
1042       assertThat(e.getMessage(), containsString(expectedMessage));
1043     }
1044   }
1045 
1046   @Test (timeout = 10000)
testWindowsShellScriptBuilderMkdir()1047   public void testWindowsShellScriptBuilderMkdir() throws IOException {
1048     String mkDirCmd = "@if not exist \"\" mkdir \"\"";
1049 
1050     // Test is only relevant on Windows
1051     Assume.assumeTrue(Shell.WINDOWS);
1052 
1053     // The tests are built on assuming 8191 max command line length
1054     assertEquals(8191, Shell.WINDOWS_MAX_SHELL_LENGHT);
1055 
1056     ShellScriptBuilder builder = ShellScriptBuilder.create();
1057 
1058     // test mkdir
1059     builder.mkdir(new Path(org.apache.commons.lang.StringUtils.repeat("A", 1024)));
1060     builder.mkdir(new Path(org.apache.commons.lang.StringUtils.repeat(
1061         "E", (Shell.WINDOWS_MAX_SHELL_LENGHT - mkDirCmd.length())/2)));
1062     try {
1063       builder.mkdir(new Path(org.apache.commons.lang.StringUtils.repeat(
1064           "X", (Shell.WINDOWS_MAX_SHELL_LENGHT - mkDirCmd.length())/2 +1)));
1065       fail("long mkdir was expected to throw");
1066     } catch(IOException e) {
1067       assertThat(e.getMessage(), containsString(expectedMessage));
1068     }
1069   }
1070 
1071   @Test (timeout = 10000)
testWindowsShellScriptBuilderLink()1072   public void testWindowsShellScriptBuilderLink() throws IOException {
1073     // Test is only relevant on Windows
1074     Assume.assumeTrue(Shell.WINDOWS);
1075 
1076     String linkCmd = "@" +Shell.WINUTILS + " symlink \"\" \"\"";
1077 
1078     // The tests are built on assuming 8191 max command line length
1079     assertEquals(8191, Shell.WINDOWS_MAX_SHELL_LENGHT);
1080 
1081     ShellScriptBuilder builder = ShellScriptBuilder.create();
1082 
1083     // test link
1084     builder.link(new Path(org.apache.commons.lang.StringUtils.repeat("A", 1024)),
1085         new Path(org.apache.commons.lang.StringUtils.repeat("B", 1024)));
1086     builder.link(
1087         new Path(org.apache.commons.lang.StringUtils.repeat(
1088             "E", (Shell.WINDOWS_MAX_SHELL_LENGHT - linkCmd.length())/2)),
1089         new Path(org.apache.commons.lang.StringUtils.repeat(
1090             "F", (Shell.WINDOWS_MAX_SHELL_LENGHT - linkCmd.length())/2)));
1091     try {
1092       builder.link(
1093           new Path(org.apache.commons.lang.StringUtils.repeat(
1094               "X", (Shell.WINDOWS_MAX_SHELL_LENGHT - linkCmd.length())/2 + 1)),
1095           new Path(org.apache.commons.lang.StringUtils.repeat(
1096               "Y", (Shell.WINDOWS_MAX_SHELL_LENGHT - linkCmd.length())/2) + 1));
1097       fail("long link was expected to throw");
1098     } catch(IOException e) {
1099       assertThat(e.getMessage(), containsString(expectedMessage));
1100     }
1101   }
1102 
1103   @Test
testKillProcessGroup()1104   public void testKillProcessGroup() throws Exception {
1105     Assume.assumeTrue(Shell.isSetsidAvailable);
1106     containerManager.start();
1107 
1108     // Construct the Container-id
1109     ApplicationId appId = ApplicationId.newInstance(2, 2);
1110     ApplicationAttemptId appAttemptId =
1111         ApplicationAttemptId.newInstance(appId, 1);
1112     ContainerId cId = ContainerId.newContainerId(appAttemptId, 0);
1113     File processStartFile =
1114         new File(tmpDir, "pid.txt").getAbsoluteFile();
1115     File childProcessStartFile =
1116         new File(tmpDir, "child_pid.txt").getAbsoluteFile();
1117 
1118     // setup a script that can handle sigterm gracefully
1119     File scriptFile = Shell.appendScriptExtension(tmpDir, "testscript");
1120     PrintWriter writer = new PrintWriter(new FileOutputStream(scriptFile));
1121     writer.println("#!/bin/bash\n\n");
1122     writer.println("echo \"Running testscript for forked process\"");
1123     writer.println("umask 0");
1124     writer.println("echo $$ >> " + processStartFile);
1125     writer.println("while true;\ndo sleep 1s;\ndone > /dev/null 2>&1 &");
1126     writer.println("echo $! >> " + childProcessStartFile);
1127     writer.println("while true;\ndo sleep 1s;\ndone");
1128     writer.close();
1129     FileUtil.setExecutable(scriptFile, true);
1130 
1131     ContainerLaunchContext containerLaunchContext =
1132         recordFactory.newRecordInstance(ContainerLaunchContext.class);
1133 
1134     // upload the script file so that the container can run it
1135     URL resource_alpha =
1136         ConverterUtils.getYarnUrlFromPath(localFS
1137             .makeQualified(new Path(scriptFile.getAbsolutePath())));
1138     LocalResource rsrc_alpha =
1139         recordFactory.newRecordInstance(LocalResource.class);
1140     rsrc_alpha.setResource(resource_alpha);
1141     rsrc_alpha.setSize(-1);
1142     rsrc_alpha.setVisibility(LocalResourceVisibility.APPLICATION);
1143     rsrc_alpha.setType(LocalResourceType.FILE);
1144     rsrc_alpha.setTimestamp(scriptFile.lastModified());
1145     String destinationFile = "dest_file.sh";
1146     Map<String, LocalResource> localResources =
1147         new HashMap<String, LocalResource>();
1148     localResources.put(destinationFile, rsrc_alpha);
1149     containerLaunchContext.setLocalResources(localResources);
1150 
1151     // set up the rest of the container
1152     List<String> commands = Arrays.asList(Shell.getRunScriptCommand(scriptFile));
1153     containerLaunchContext.setCommands(commands);
1154     Priority priority = Priority.newInstance(10);
1155     long createTime = 1234;
1156     Token containerToken = createContainerToken(cId, priority, createTime);
1157 
1158     StartContainerRequest scRequest =
1159         StartContainerRequest.newInstance(containerLaunchContext,
1160             containerToken);
1161     List<StartContainerRequest> list = new ArrayList<StartContainerRequest>();
1162     list.add(scRequest);
1163     StartContainersRequest allRequests =
1164         StartContainersRequest.newInstance(list);
1165     containerManager.startContainers(allRequests);
1166 
1167     int timeoutSecs = 0;
1168     while (!processStartFile.exists() && timeoutSecs++ < 20) {
1169       Thread.sleep(1000);
1170       LOG.info("Waiting for process start-file to be created");
1171     }
1172     Assert.assertTrue("ProcessStartFile doesn't exist!",
1173         processStartFile.exists());
1174 
1175     BufferedReader reader =
1176           new BufferedReader(new FileReader(processStartFile));
1177     // Get the pid of the process
1178     String pid = reader.readLine().trim();
1179     // No more lines
1180     Assert.assertEquals(null, reader.readLine());
1181     reader.close();
1182 
1183     reader =
1184           new BufferedReader(new FileReader(childProcessStartFile));
1185     // Get the pid of the child process
1186     String child = reader.readLine().trim();
1187     // No more lines
1188     Assert.assertEquals(null, reader.readLine());
1189     reader.close();
1190 
1191     LOG.info("Manually killing pid " + pid + ", but not child pid " + child);
1192     Shell.execCommand(new String[]{"kill", "-9", pid});
1193 
1194     BaseContainerManagerTest.waitForContainerState(containerManager, cId,
1195         ContainerState.COMPLETE);
1196 
1197     Assert.assertFalse("Process is still alive!",
1198         DefaultContainerExecutor.containerIsAlive(pid));
1199 
1200     List<ContainerId> containerIds = new ArrayList<ContainerId>();
1201     containerIds.add(cId);
1202 
1203     GetContainerStatusesRequest gcsRequest =
1204         GetContainerStatusesRequest.newInstance(containerIds);
1205 
1206     ContainerStatus containerStatus =
1207         containerManager.getContainerStatuses(gcsRequest)
1208             .getContainerStatuses().get(0);
1209     Assert.assertEquals(ExitCode.FORCE_KILLED.getExitCode(),
1210         containerStatus.getExitStatus());
1211   }
1212 }
1213