Skip to content

Commit c22c77a

Browse files
committed
[YARN-10607] User environment is unable to prepend PATH when mapreduce.admin.user.env also sets PATH. Contributed by Eric Badger.
1 parent 5aa9866 commit c22c77a

File tree

4 files changed

+104
-0
lines changed
  • hadoop-yarn-project/hadoop-yarn

4 files changed

+104
-0
lines changed

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1245,6 +1245,15 @@ public static boolean isAclEnabled(Configuration conf) {
12451245
public static final String NM_ADMIN_USER_ENV = NM_PREFIX + "admin-env";
12461246
public static final String DEFAULT_NM_ADMIN_USER_ENV = "MALLOC_ARENA_MAX=$MALLOC_ARENA_MAX";
12471247

1248+
/**
1249+
* PATH components that will be prepended to the user's path.
1250+
* If this is defined and the user does not define PATH, NM will also
1251+
* append ":$PATH" to prevent this from eclipsing the PATH defined in
1252+
* the container. This feature is only available for Linux.
1253+
* */
1254+
public static final String NM_ADMIN_FORCE_PATH = NM_PREFIX + "force.path";
1255+
public static final String DEFAULT_NM_ADMIN_FORCE_PATH = "";
1256+
12481257
/** Environment variables that containers may override rather than use NodeManager's default.*/
12491258
public static final String NM_ENV_WHITELIST = NM_PREFIX + "env-whitelist";
12501259
public static final String DEFAULT_NM_ENV_WHITELIST = StringUtils.join(",",

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,17 @@
11951195
<value>MALLOC_ARENA_MAX=$MALLOC_ARENA_MAX</value>
11961196
</property>
11971197

1198+
<property>
1199+
<description>
1200+
* PATH components that will be prepended to the user's path.
1201+
* If this is defined and the user does not define PATH, NM will also
1202+
* append ":$PATH" to prevent this from eclipsing the PATH defined in
1203+
* the container. This feature is only available for Linux.
1204+
</description>
1205+
<name>yarn.nodemanager.force.path</name>
1206+
<value></value>
1207+
</property>
1208+
11981209
<property>
11991210
<description>Environment variables that containers may override rather than use NodeManager's default.</description>
12001211
<name>yarn.nodemanager.env-whitelist</name>

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/launcher/ContainerLaunch.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1636,6 +1636,27 @@ public void sanitizeEnv(Map<String, String> environment, Path pwd,
16361636
nmVars.addAll(Apps.getEnvVarsFromInputProperty(
16371637
YarnConfiguration.NM_ADMIN_USER_ENV, defEnvStr, conf));
16381638

1639+
if (!Shell.WINDOWS) {
1640+
// maybe force path components
1641+
String forcePath = conf.get(YarnConfiguration.NM_ADMIN_FORCE_PATH,
1642+
YarnConfiguration.DEFAULT_NM_ADMIN_FORCE_PATH);
1643+
if (!forcePath.isEmpty()) {
1644+
String userPath = environment.get(Environment.PATH.name());
1645+
environment.remove(Environment.PATH.name());
1646+
if (userPath == null || userPath.isEmpty()) {
1647+
Apps.addToEnvironment(environment, Environment.PATH.name(),
1648+
forcePath, File.pathSeparator);
1649+
Apps.addToEnvironment(environment, Environment.PATH.name(),
1650+
"$PATH", File.pathSeparator);
1651+
} else {
1652+
Apps.addToEnvironment(environment, Environment.PATH.name(),
1653+
forcePath, File.pathSeparator);
1654+
Apps.addToEnvironment(environment, Environment.PATH.name(),
1655+
userPath, File.pathSeparator);
1656+
}
1657+
}
1658+
}
1659+
16391660
// TODO: Remove Windows check and use this approach on all platforms after
16401661
// additional testing. See YARN-358.
16411662
if (Shell.WINDOWS) {

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/launcher/TestContainerLaunch.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,69 @@ public void handle(Event event) {
823823
Assert.assertEquals(testVal3, userSetEnv.get(testKey3));
824824
}
825825

826+
@Test
827+
public void testNmForcePath() throws Exception {
828+
// Valid only for unix
829+
assumeNotWindows();
830+
ContainerLaunchContext containerLaunchContext =
831+
recordFactory.newRecordInstance(ContainerLaunchContext.class);
832+
ApplicationId appId = ApplicationId.newInstance(0, 0);
833+
ApplicationAttemptId appAttemptId =
834+
ApplicationAttemptId.newInstance(appId, 1);
835+
ContainerId cId = ContainerId.newContainerId(appAttemptId, 0);
836+
Map<String, String> userSetEnv = new HashMap<>();
837+
Set<String> nmEnvTrack = new LinkedHashSet<>();
838+
containerLaunchContext.setEnvironment(userSetEnv);
839+
Container container = mock(Container.class);
840+
when(container.getContainerId()).thenReturn(cId);
841+
when(container.getLaunchContext()).thenReturn(containerLaunchContext);
842+
when(container.getLocalizedResources()).thenReturn(null);
843+
Dispatcher dispatcher = mock(Dispatcher.class);
844+
EventHandler<Event> eventHandler = new EventHandler<Event>() {
845+
public void handle(Event event) {
846+
Assert.assertTrue(event instanceof ContainerExitEvent);
847+
ContainerExitEvent exitEvent = (ContainerExitEvent) event;
848+
Assert.assertEquals(ContainerEventType.CONTAINER_EXITED_WITH_FAILURE,
849+
exitEvent.getType());
850+
}
851+
};
852+
when(dispatcher.getEventHandler()).thenReturn(eventHandler);
853+
854+
String testDir = System.getProperty("test.build.data",
855+
"target/test-dir");
856+
Path pwd = new Path(testDir);
857+
List<Path> appDirs = new ArrayList<>();
858+
List<String> userLocalDirs = new ArrayList<>();
859+
List<String> containerLogs = new ArrayList<>();
860+
Map<Path, List<String>> resources = new HashMap<>();
861+
Path nmp = new Path(testDir);
862+
863+
YarnConfiguration conf = new YarnConfiguration();
864+
String forcePath = "./force-path";
865+
conf.set("yarn.nodemanager.force.path", forcePath);
866+
867+
ContainerLaunch launch = new ContainerLaunch(distContext, conf,
868+
dispatcher, exec, null, container, dirsHandler, containerManager);
869+
launch.sanitizeEnv(userSetEnv, pwd, appDirs, userLocalDirs, containerLogs,
870+
resources, nmp, nmEnvTrack);
871+
872+
Assert.assertTrue(userSetEnv.containsKey(Environment.PATH.name()));
873+
Assert.assertEquals(forcePath + ":$PATH",
874+
userSetEnv.get(Environment.PATH.name()));
875+
876+
String userPath = "/usr/bin:/usr/local/bin";
877+
userSetEnv.put(Environment.PATH.name(), userPath);
878+
containerLaunchContext.setEnvironment(userSetEnv);
879+
when(container.getLaunchContext()).thenReturn(containerLaunchContext);
880+
881+
launch.sanitizeEnv(userSetEnv, pwd, appDirs, userLocalDirs, containerLogs,
882+
resources, nmp, nmEnvTrack);
883+
884+
Assert.assertTrue(userSetEnv.containsKey(Environment.PATH.name()));
885+
Assert.assertEquals(forcePath + ":" + userPath,
886+
userSetEnv.get(Environment.PATH.name()));
887+
}
888+
826889
@Test
827890
public void testErrorLogOnContainerExit() throws Exception {
828891
verifyTailErrorLogOnContainerExit(new Configuration(), "/stderr", false);

0 commit comments

Comments
 (0)