diff --git a/hadoop-yarn-project/CHANGES.txt b/hadoop-yarn-project/CHANGES.txt
index 98e42c12e0b..8f4907f9902 100644
--- a/hadoop-yarn-project/CHANGES.txt
+++ b/hadoop-yarn-project/CHANGES.txt
@@ -220,6 +220,9 @@ Release 2.8.0 - UNRELEASED
YARN-2740. Fix NodeLabelsManager to properly handle node label modifications
when distributed node label configuration enabled. (Naganarasimha G R via wangda)
+ YARN-3517. RM web ui for dumping scheduler logs should be for admins only
+ (Varun Vasudev via tgraves)
+
Release 2.7.1 - UNRELEASED
INCOMPATIBLE CHANGES
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/server/security/ApplicationACLsManager.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/server/security/ApplicationACLsManager.java
index 4daaa683122..97b41633872 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/server/security/ApplicationACLsManager.java
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/server/security/ApplicationACLsManager.java
@@ -138,4 +138,15 @@ public class ApplicationACLsManager {
}
return false;
}
+
+ /**
+ * Check if the given user in an admin.
+ *
+ * @param calledUGI
+ * UserGroupInformation for the user
+ * @return true if the user is an admin, false otherwise
+ */
+ public final boolean isAdmin(final UserGroupInformation calledUGI) {
+ return this.adminAclsManager.isAdmin(calledUGI);
+ }
}
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/CapacitySchedulerPage.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/CapacitySchedulerPage.java
index 2eeda6652bc..fa22a0d8309 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/CapacitySchedulerPage.java
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/CapacitySchedulerPage.java
@@ -24,6 +24,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
+import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.util.StringUtils;
import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager;
import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerHealth;
@@ -33,6 +34,7 @@ import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.UserInfo
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.CapacitySchedulerInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.CapacitySchedulerLeafQueueInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.CapacitySchedulerQueueInfo;
+import org.apache.hadoop.yarn.server.security.ApplicationACLsManager;
import org.apache.hadoop.yarn.util.Times;
import org.apache.hadoop.yarn.webapp.ResponseInfo;
import org.apache.hadoop.yarn.webapp.SubView;
@@ -190,28 +192,46 @@ class CapacitySchedulerPage extends RmView {
static class QueuesBlock extends HtmlBlock {
final CapacityScheduler cs;
final CSQInfo csqinfo;
+ private final ResourceManager rm;
@Inject QueuesBlock(ResourceManager rm, CSQInfo info) {
cs = (CapacityScheduler) rm.getResourceScheduler();
csqinfo = info;
+ this.rm = rm;
}
@Override
public void render(Block html) {
html._(MetricsOverviewTable.class);
- // Dump CapacityScheduler debug logs
- html.div()
+
+ UserGroupInformation callerUGI = this.getCallerUGI();
+ boolean isAdmin = false;
+ ApplicationACLsManager aclsManager = rm.getApplicationACLsManager();
+ if (aclsManager.areACLsEnabled()) {
+ if (callerUGI != null && aclsManager.isAdmin(callerUGI)) {
+ isAdmin = true;
+ }
+ } else {
+ isAdmin = true;
+ }
+
+ // only show button to dump CapacityScheduler debug logs to admins
+ if (isAdmin) {
+ html.div()
.button()
- .$onclick("confirmAction()").b("Dump scheduler logs")._()
- .select().$id("time")
- .option().$value("60")._("1 min")._()
- .option().$value("300")._("5 min")._()
- .option().$value("600")._("10 min")._()
+ .$style(
+ "border-style: solid; border-color: #000000; border-width: 1px;"
+ + " cursor: hand; cursor: pointer; border-radius: 4px")
+ .$onclick("confirmAction()").b("Dump scheduler logs")._().select()
+ .$id("time").option().$value("60")._("1 min")._().option()
+ .$value("300")._("5 min")._().option().$value("600")._("10 min")._()
._()._();
- StringBuilder script = new StringBuilder();
- script.append("function confirmAction() {")
- .append(" b = confirm(\"Are you sure you wish to generate scheduler logs?\");")
+ StringBuilder script = new StringBuilder();
+ script
+ .append("function confirmAction() {")
+ .append(" b = confirm(\"Are you sure you wish to generate"
+ + " scheduler logs?\");")
.append(" if (b == true) {")
.append(" var timePeriod = $(\"#time\").val();")
.append(" $.ajax({")
@@ -225,13 +245,14 @@ class CapacitySchedulerPage extends RmView {
.append(" alert(\"Scheduler log is being generated.\");")
.append(" }, 1000);")
.append(" }).fail(function(data){")
- .append(" alert(\"Scheduler log generation failed. Please check the ResourceManager log for more informtion.\");")
- .append(" console.log(data);")
- .append(" });")
- .append(" }")
+ .append(
+ " alert(\"Scheduler log generation failed. Please check the"
+ + " ResourceManager log for more informtion.\");")
+ .append(" console.log(data);").append(" });").append(" }")
.append("}");
- html.script().$type("text/javascript")._(script.toString())._();
+ html.script().$type("text/javascript")._(script.toString())._();
+ }
UL
>> ul = html.
div("#cs-wrapper.ui-widget").
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebServices.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebServices.java
index 9aea62d1c84..4ce2b5478ee 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebServices.java
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebServices.java
@@ -142,10 +142,12 @@ import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.SchedulerInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.SchedulerTypeInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.StatisticsItemInfo;
+import org.apache.hadoop.yarn.server.security.ApplicationACLsManager;
import org.apache.hadoop.yarn.server.utils.BuilderUtils;
import org.apache.hadoop.yarn.util.AdHocLogDumper;
import org.apache.hadoop.yarn.util.ConverterUtils;
import org.apache.hadoop.yarn.webapp.BadRequestException;
+import org.apache.hadoop.yarn.webapp.ForbiddenException;
import org.apache.hadoop.yarn.webapp.NotFoundException;
import org.apache.hadoop.yarn.webapp.util.WebAppUtils;
@@ -263,8 +265,17 @@ public class RMWebServices {
@POST
@Path("/scheduler/logs")
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
- public String dumpSchedulerLogs(@FormParam("time") String time) throws IOException {
+ public String dumpSchedulerLogs(@FormParam("time") String time,
+ @Context HttpServletRequest hsr) throws IOException {
init();
+ UserGroupInformation callerUGI = getCallerUserGroupInformation(hsr, true);
+ ApplicationACLsManager aclsManager = rm.getApplicationACLsManager();
+ if (aclsManager.areACLsEnabled()) {
+ if (callerUGI == null || !aclsManager.isAdmin(callerUGI)) {
+ String msg = "Only admins can carry out this operation.";
+ throw new ForbiddenException(msg);
+ }
+ }
ResourceScheduler rs = rm.getResourceScheduler();
int period = Integer.parseInt(time);
if (period <= 0) {
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServices.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServices.java
index e4614f8c9ec..cd1d7716bec 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServices.java
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServices.java
@@ -26,7 +26,9 @@ import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import java.io.File;
import java.io.StringReader;
+import java.security.Principal;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
@@ -37,6 +39,7 @@ import javax.ws.rs.core.MediaType;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
+import org.apache.commons.io.FileUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.service.Service.STATE;
import org.apache.hadoop.util.VersionInfo;
@@ -54,9 +57,13 @@ import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager;
import org.apache.hadoop.yarn.server.resourcemanager.nodelabels.RMNodeLabelsManager;
import org.apache.hadoop.yarn.server.resourcemanager.scheduler.QueueMetrics;
import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ResourceScheduler;
+import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler;
+import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler;
import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fifo.FifoScheduler;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.AppsInfo;
+import org.apache.hadoop.yarn.server.security.ApplicationACLsManager;
import org.apache.hadoop.yarn.util.YarnVersionInfo;
+import org.apache.hadoop.yarn.webapp.ForbiddenException;
import org.apache.hadoop.yarn.webapp.GenericExceptionHandler;
import org.apache.hadoop.yarn.webapp.JerseyTestBase;
import org.apache.hadoop.yarn.webapp.WebServicesTestUtils;
@@ -643,4 +650,74 @@ public class TestRMWebServices extends JerseyTestBase {
null, null, null, null, null, null, null, emptySet, emptySet);
assertTrue(appsInfo.getApps().isEmpty());
}
+
+ @Test
+ public void testDumpingSchedulerLogs() throws Exception {
+
+ ResourceManager mockRM = mock(ResourceManager.class);
+ Configuration conf = new YarnConfiguration();
+ HttpServletRequest mockHsr = mock(HttpServletRequest.class);
+ ApplicationACLsManager aclsManager = new ApplicationACLsManager(conf);
+ when(mockRM.getApplicationACLsManager()).thenReturn(aclsManager);
+ RMWebServices webSvc =
+ new RMWebServices(mockRM, conf, mock(HttpServletResponse.class));
+
+ // nothing should happen
+ webSvc.dumpSchedulerLogs("1", mockHsr);
+ Thread.sleep(1000);
+ checkSchedulerLogFileAndCleanup();
+
+ conf.setBoolean(YarnConfiguration.YARN_ACL_ENABLE, true);
+ conf.setStrings(YarnConfiguration.YARN_ADMIN_ACL, "admin");
+ aclsManager = new ApplicationACLsManager(conf);
+ when(mockRM.getApplicationACLsManager()).thenReturn(aclsManager);
+ webSvc = new RMWebServices(mockRM, conf, mock(HttpServletResponse.class));
+ boolean exceptionThrown = false;
+ try {
+ webSvc.dumpSchedulerLogs("1", mockHsr);
+ fail("Dumping logs should fail");
+ } catch (ForbiddenException ae) {
+ exceptionThrown = true;
+ }
+ assertTrue("ForbiddenException expected", exceptionThrown);
+ exceptionThrown = false;
+ when(mockHsr.getUserPrincipal()).thenReturn(new Principal() {
+ @Override
+ public String getName() {
+ return "testuser";
+ }
+ });
+ try {
+ webSvc.dumpSchedulerLogs("1", mockHsr);
+ fail("Dumping logs should fail");
+ } catch (ForbiddenException ae) {
+ exceptionThrown = true;
+ }
+ assertTrue("ForbiddenException expected", exceptionThrown);
+
+ when(mockHsr.getUserPrincipal()).thenReturn(new Principal() {
+ @Override
+ public String getName() {
+ return "admin";
+ }
+ });
+ webSvc.dumpSchedulerLogs("1", mockHsr);
+ Thread.sleep(1000);
+ checkSchedulerLogFileAndCleanup();
+ }
+
+ private void checkSchedulerLogFileAndCleanup() {
+ String targetFile;
+ ResourceScheduler scheduler = rm.getResourceScheduler();
+ if (scheduler instanceof FairScheduler) {
+ targetFile = "yarn-fair-scheduler-debug.log";
+ } else if (scheduler instanceof CapacityScheduler) {
+ targetFile = "yarn-capacity-scheduler-debug.log";
+ } else {
+ targetFile = "yarn-scheduler-debug.log";
+ }
+ File logFile = new File(System.getProperty("yarn.log.dir"), targetFile);
+ assertTrue("scheduler log file doesn't exist", logFile.exists());
+ FileUtils.deleteQuietly(logFile);
+ }
}