/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.mapred;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import javax.security.auth.login.LoginException;
import junit.framework.TestCase;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.examples.SleepJob;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hdfs.MiniDFSCluster;
import org.apache.hadoop.security.UnixUserGroupInformation;
import org.apache.hadoop.security.UserGroupInformation;
public class TestQueueManager extends TestCase {
private static final Log LOG = LogFactory.getLog(TestQueueManager.class);
private MiniDFSCluster miniDFSCluster;
private MiniMRCluster miniMRCluster;
public void testDefaultQueueConfiguration() {
JobConf conf = new JobConf();
QueueManager qMgr = new QueueManager(conf);
Set<String> expQueues = new TreeSet<String>();
expQueues.add("default");
verifyQueues(expQueues, qMgr.getQueues());
// pass true so it will fail if the key is not found.
assertFalse(conf.getBoolean("mapred.acls.enabled", true));
}
public void testMultipleQueues() {
JobConf conf = new JobConf();
conf.set("mapred.queue.names", "q1,q2,Q3");
QueueManager qMgr = new QueueManager(conf);
Set<String> expQueues = new TreeSet<String>();
expQueues.add("q1");
expQueues.add("q2");
expQueues.add("Q3");
verifyQueues(expQueues, qMgr.getQueues());
}
public void testSchedulerInfo() {
JobConf conf = new JobConf();
conf.set("mapred.queue.names", "qq1,qq2");
QueueManager qMgr = new QueueManager(conf);
qMgr.setSchedulerInfo("qq1", "queueInfoForqq1");
qMgr.setSchedulerInfo("qq2", "queueInfoForqq2");
assertEquals(qMgr.getSchedulerInfo("qq2"), "queueInfoForqq2");
assertEquals(qMgr.getSchedulerInfo("qq1"), "queueInfoForqq1");
}
public void testAllEnabledACLForJobSubmission() throws IOException {
JobConf conf = setupConf("mapred.queue.default.acl-submit-job", "*");
verifyJobSubmission(conf, true);
}
public void testAllDisabledACLForJobSubmission() throws IOException {
JobConf conf = setupConf("mapred.queue.default.acl-submit-job", "");
verifyJobSubmission(conf, false);
}
public void testUserDisabledACLForJobSubmission() throws IOException {
JobConf conf = setupConf("mapred.queue.default.acl-submit-job",
"3698-non-existent-user");
verifyJobSubmission(conf, false);
}
public void testDisabledACLForNonDefaultQueue() throws IOException {
// allow everyone in default queue
JobConf conf = setupConf("mapred.queue.default.acl-submit-job", "*");
// setup a different queue
conf.set("mapred.queue.names", "default,q1");
// setup a different acl for this queue.
conf.set("mapred.queue.q1.acl-submit-job", "dummy-user");
// verify job submission to other queue fails.
verifyJobSubmission(conf, false, "q1");
}
public void testSubmissionToInvalidQueue() throws IOException{
JobConf conf = new JobConf();
conf.set("mapred.queue.names","default");
setUpCluster(conf);
String queueName = "q1";
try {
RunningJob rjob = submitSleepJob(1, 1, 100, 100, true, null, queueName);
} catch (IOException ioe) {
assertTrue(ioe.getMessage().contains("Queue \"" + queueName + "\" does not exist"));
return;
} finally {
tearDownCluster();
}
fail("Job submission to invalid queue job shouldnot complete , it should fail with proper exception ");
}
public void testEnabledACLForNonDefaultQueue() throws IOException,
LoginException {
// login as self...
UserGroupInformation ugi = UnixUserGroupInformation.login();
String userName = ugi.getUserName();
// allow everyone in default queue
JobConf conf = setupConf("mapred.queue.default.acl-submit-job", "*");
// setup a different queue
conf.set("mapred.queue.names", "default,q2");
// setup a different acl for this queue.
conf.set("mapred.queue.q2.acl-submit-job", userName);
// verify job submission to other queue fails.
verifyJobSubmission(conf, true, "q2");
}
public void testUserEnabledACLForJobSubmission()
throws IOException, LoginException {
// login as self...
UserGroupInformation ugi = UnixUserGroupInformation.login();
String userName = ugi.getUserName();
JobConf conf = setupConf("mapred.queue.default.acl-submit-job",
"3698-junk-user," + userName
+ " 3698-junk-group1,3698-junk-group2");
verifyJobSubmission(conf, true);
}
public void testGroupsEnabledACLForJobSubmission()
throws IOException, LoginException {
// login as self, get one group, and add in allowed list.
UserGroupInformation ugi = UnixUserGroupInformation.login();
String[] groups = ugi.getGroupNames();
assertTrue(groups.length > 0);
JobConf conf = setupConf("mapred.queue.default.acl-submit-job",
"3698-junk-user1,3698-junk-user2 "
+ groups[groups.length-1]
+ ",3698-junk-group");
verifyJobSubmission(conf, true);
}
public void testAllEnabledACLForJobKill() throws IOException {
JobConf conf = setupConf("mapred.queue.default.acl-administer-jobs", "*");
verifyJobKill(conf, true);
}
public void testAllDisabledACLForJobKill() throws IOException {
JobConf conf = setupConf("mapred.queue.default.acl-administer-jobs", "");
verifyJobKillAsOtherUser(conf, false, "dummy-user,dummy-user-group");
}
public void testOwnerAllowedForJobKill() throws IOException {
JobConf conf = setupConf("mapred.queue.default.acl-administer-jobs",
"junk-user");
verifyJobKill(conf, true);
}
public void testUserDisabledACLForJobKill() throws IOException {
//setup a cluster allowing a user to submit
JobConf conf = setupConf("mapred.queue.default.acl-administer-jobs",
"dummy-user");
verifyJobKillAsOtherUser(conf, false, "dummy-user,dummy-user-group");
}
public void testUserEnabledACLForJobKill() throws IOException,
LoginException {
// login as self...
UserGroupInformation ugi = UnixUserGroupInformation.login();
String userName = ugi.getUserName();
JobConf conf = setupConf("mapred.queue.default.acl-administer-jobs",
"dummy-user,"+userName);
verifyJobKillAsOtherUser(conf, true, "dummy-user,dummy-user-group");
}
public void testUserDisabledForJobPriorityChange() throws IOException {
JobConf conf = setupConf("mapred.queue.default.acl-administer-jobs",
"junk-user");
verifyJobPriorityChangeAsOtherUser(conf, false,
"junk-user,junk-user-group");
}
/**
* Test to verify refreshing of queue properties by using MRAdmin tool.
*
* @throws Exception
*/
public void testACLRefresh() throws Exception {
String queueConfigPath =
System.getProperty("test.build.extraconf", "build/test/extraconf");
File queueConfigFile =
new File(queueConfigPath, QueueManager.QUEUE_ACLS_FILE_NAME);
File hadoopConfigFile = new File(queueConfigPath, "mapred-site.xml");
try {
//Setting up default mapred-site.xml
Properties hadoopConfProps = new Properties();
//these properties should be retained.
hadoopConfProps.put("mapred.queue.names", "default,q1,q2");
hadoopConfProps.put("mapred.acls.enabled", "true");
//These property should always be overridden
hadoopConfProps.put("mapred.queue.default.acl-submit-job", "u1");
hadoopConfProps.put("mapred.queue.q1.acl-submit-job", "u2");
hadoopConfProps.put("mapred.queue.q2.acl-submit-job", "u1");
UtilsForTests.setUpConfigFile(hadoopConfProps, hadoopConfigFile);
//Actual property which would be used.
Properties queueConfProps = new Properties();
queueConfProps.put("mapred.queue.default.acl-submit-job", " ");
//Writing out the queue configuration file.
UtilsForTests.setUpConfigFile(queueConfProps, queueConfigFile);
//Create a new configuration to be used with QueueManager
JobConf conf = new JobConf();
QueueManager queueManager = new QueueManager(conf);
UserGroupInformation ugi = UnixUserGroupInformation.getCurrentUGI();
//Job Submission should fail because ugi to be used is set to blank.
assertFalse("User Job Submission Succeeded before refresh.",
queueManager.hasAccess("default", QueueManager.QueueOperation.
SUBMIT_JOB, ugi));
assertFalse("User Job Submission Succeeded before refresh.",
queueManager.hasAccess("q1", QueueManager.QueueOperation.
SUBMIT_JOB, ugi));
assertFalse("User Job Submission Succeeded before refresh.",
queueManager.hasAccess("q2", QueueManager.QueueOperation.
SUBMIT_JOB, ugi));
//Test job submission as alternate user.
Configuration alternateUserConfig = new Configuration();
alternateUserConfig.set("hadoop.job.ugi","u1,users");
UserGroupInformation alternateUgi =
UserGroupInformation.readFrom(alternateUserConfig);
assertTrue("Alternate User Job Submission failed before refresh.",
queueManager.hasAccess("q2", QueueManager.QueueOperation.
SUBMIT_JOB, alternateUgi));
//Set acl for the current user.
queueConfProps.put("mapred.queue.default.acl-submit-job", ugi.getUserName());
queueConfProps.put("mapred.queue.q1.acl-submit-job", ugi.getUserName());
queueConfProps.put("mapred.queue.q2.acl-submit-job", ugi.getUserName());
//write out queue-acls.xml.
UtilsForTests.setUpConfigFile(queueConfProps, queueConfigFile);
//refresh configuration
queueManager.refreshAcls(conf);
//Submission should succeed
assertTrue("User Job Submission failed after refresh.",
queueManager.hasAccess("default", QueueManager.QueueOperation.
SUBMIT_JOB, ugi));
assertTrue("User Job Submission failed after refresh.",
queueManager.hasAccess("q1", QueueManager.QueueOperation.
SUBMIT_JOB, ugi));
assertTrue("User Job Submission failed after refresh.",
queueManager.hasAccess("q2", QueueManager.QueueOperation.
SUBMIT_JOB, ugi));
assertFalse("Alternate User Job Submission succeeded after refresh.",
queueManager.hasAccess("q2", QueueManager.QueueOperation.
SUBMIT_JOB, alternateUgi));
//delete the ACL file.
queueConfigFile.delete();
//rewrite the mapred-site.xml
hadoopConfProps.put("mapred.acls.enabled", "true");
hadoopConfProps.put("mapred.queue.default.acl-submit-job", ugi.getUserName());
UtilsForTests.setUpConfigFile(hadoopConfProps, hadoopConfigFile);
queueManager.refreshAcls(conf);
assertTrue("User Job Submission failed after refresh and no queue acls file.",
queueManager.hasAccess("default", QueueManager.QueueOperation.
SUBMIT_JOB, ugi));
} finally{
if(queueConfigFile.exists()) {
queueConfigFile.delete();
}
if(hadoopConfigFile.exists()) {
hadoopConfigFile.delete();
}
}
}
public void testQueueAclRefreshWithInvalidConfFile() throws IOException {
String queueConfigPath =
System.getProperty("test.build.extraconf", "build/test/extraconf");
File queueConfigFile =
new File(queueConfigPath, QueueManager.QUEUE_ACLS_FILE_NAME);
File hadoopConfigFile = new File(queueConfigPath, "hadoop-site.xml");
try {
// queue properties with which the cluster is started.
Properties hadoopConfProps = new Properties();
hadoopConfProps.put("mapred.queue.names", "default,q1,q2");
hadoopConfProps.put("mapred.acls.enabled", "true");
UtilsForTests.setUpConfigFile(hadoopConfProps, hadoopConfigFile);
//properties for mapred-queue-acls.xml
Properties queueConfProps = new Properties();
UserGroupInformation ugi = UnixUserGroupInformation.getCurrentUGI();
queueConfProps.put("mapred.queue.default.acl-submit-job", ugi.getUserName());
queueConfProps.put("mapred.queue.q1.acl-submit-job", ugi.getUserName());
queueConfProps.put("mapred.queue.q2.acl-submit-job", ugi.getUserName());
UtilsForTests.setUpConfigFile(queueConfProps, queueConfigFile);
Configuration conf = new JobConf();
QueueManager queueManager = new QueueManager(conf);
//Testing access to queue.
assertTrue("User Job Submission failed.",
queueManager.hasAccess("default", QueueManager.QueueOperation.
SUBMIT_JOB, ugi));
assertTrue("User Job Submission failed.",
queueManager.hasAccess("q1", QueueManager.QueueOperation.
SUBMIT_JOB, ugi));
assertTrue("User Job Submission failed.",
queueManager.hasAccess("q2", QueueManager.QueueOperation.
SUBMIT_JOB, ugi));
//Write out a new incomplete invalid configuration file.
PrintWriter writer = new PrintWriter(new FileOutputStream(queueConfigFile));
writer.println("<configuration>");
writer.println("<property>");
writer.flush();
writer.close();
try {
//Exception to be thrown by queue manager because configuration passed
//is invalid.
queueManager.refreshAcls(conf);
fail("Refresh of ACLs should have failed with invalid conf file.");
} catch (Exception e) {
}
assertTrue("User Job Submission failed after invalid conf file refresh.",
queueManager.hasAccess("default", QueueManager.QueueOperation.
SUBMIT_JOB, ugi));
assertTrue("User Job Submission failed after invalid conf file refresh.",
queueManager.hasAccess("q1", QueueManager.QueueOperation.
SUBMIT_JOB, ugi));
assertTrue("User Job Submission failed after invalid conf file refresh.",
queueManager.hasAccess("q2", QueueManager.QueueOperation.
SUBMIT_JOB, ugi));
} finally {
//Cleanup the configuration files in all cases
if(hadoopConfigFile.exists()) {
hadoopConfigFile.delete();
}
if(queueConfigFile.exists()) {
queueConfigFile.delete();
}
}
}
private JobConf setupConf(String aclName, String aclValue) {
JobConf conf = new JobConf();
conf.setBoolean("mapred.acls.enabled", true);
conf.set(aclName, aclValue);
return conf;
}
private void verifyQueues(Set<String> expectedQueues,
Set<String> actualQueues) {
assertEquals(expectedQueues.size(), actualQueues.size());
for (String queue : expectedQueues) {
assertTrue(actualQueues.contains(queue));
}
}
private void verifyJobSubmission(JobConf conf, boolean shouldSucceed)
throws IOException {
verifyJobSubmission(conf, shouldSucceed, "default");
}
private void verifyJobSubmission(JobConf conf, boolean shouldSucceed,
String queue) throws IOException {
setUpCluster(conf);
try {
runAndVerifySubmission(conf, shouldSucceed, queue, null);
} finally {
tearDownCluster();
}
}
private void runAndVerifySubmission(JobConf conf, boolean shouldSucceed,
String queue, String userInfo)
throws IOException {
try {
RunningJob rjob = submitSleepJob(1, 1, 100, 100, true, userInfo, queue);
if (shouldSucceed) {
assertTrue(rjob.isSuccessful());
} else {
fail("Job submission should have failed.");
}
} catch (IOException ioe) {
if (shouldSucceed) {
throw ioe;
} else {
LOG.info("exception while submitting job: " + ioe.getMessage());
assertTrue(ioe.getMessage().
contains("cannot perform operation " +
"SUBMIT_JOB on queue " + queue));
// check if the system directory gets cleaned up or not
JobTracker jobtracker = miniMRCluster.getJobTrackerRunner().getJobTracker();
Path sysDir = new Path(jobtracker.getSystemDir());
FileSystem fs = sysDir.getFileSystem(conf);
int size = fs.listStatus(sysDir).length;
while (size > 1) { // ignore the jobtracker.info file
System.out.println("Waiting for the job files in sys directory to be cleaned up");
UtilsForTests.waitFor(100);
size = fs.listStatus(sysDir).length;
}
}
} finally {
tearDownCluster();
}
}
private void verifyJobKill(JobConf conf, boolean shouldSucceed)
throws IOException {
setUpCluster(conf);
try {
RunningJob rjob = submitSleepJob(1, 1, 1000, 1000, false);
assertFalse(rjob.isComplete());
while(rjob.mapProgress() == 0.0f) {
try {
Thread.sleep(10);
} catch (InterruptedException ie) {
break;
}
}
rjob.killJob();
while(rjob.cleanupProgress() == 0.0f) {
try {
Thread.sleep(10);
} catch (InterruptedException ie) {
break;
}
}
if (shouldSucceed) {
assertTrue(rjob.isComplete());
} else {
fail("Job kill should have failed.");
}
} catch (IOException ioe) {
if (shouldSucceed) {
throw ioe;
} else {
LOG.info("exception while submitting job: " + ioe.getMessage());
assertTrue(ioe.getMessage().
contains("cannot perform operation " +
"ADMINISTER_JOBS on queue default"));
}
} finally {
tearDownCluster();
}
}
private void verifyJobKillAsOtherUser(JobConf conf, boolean shouldSucceed,
String otherUserInfo)
throws IOException {
setUpCluster(conf);
try {
// submit a job as another user.
String userInfo = otherUserInfo;
RunningJob rjob = submitSleepJob(1, 1, 1000, 1000, false, userInfo);
assertFalse(rjob.isComplete());
//try to kill as self
try {
rjob.killJob();
if (!shouldSucceed) {
fail("should fail kill operation");
}
} catch (IOException ioe) {
if (shouldSucceed) {
throw ioe;
}
//verify it fails
LOG.info("exception while submitting job: " + ioe.getMessage());
assertTrue(ioe.getMessage().
contains("cannot perform operation " +
"ADMINISTER_JOBS on queue default"));
}
//wait for job to complete on its own
while (!rjob.isComplete()) {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
break;
}
}
} finally {
tearDownCluster();
}
}
private void verifyJobPriorityChangeAsOtherUser(JobConf conf,
boolean shouldSucceed, String otherUserInfo)
throws IOException {
setUpCluster(conf);
try {
// submit job as another user.
String userInfo = otherUserInfo;
RunningJob rjob = submitSleepJob(1, 1, 1000, 1000, false, userInfo);
assertFalse(rjob.isComplete());
// try to change priority as self
try {
rjob.setJobPriority("VERY_LOW");
if (!shouldSucceed) {
fail("changing priority should fail.");
}
} catch (IOException ioe) {
//verify it fails
LOG.info("exception while submitting job: " + ioe.getMessage());
assertTrue(ioe.getMessage().
contains("cannot perform operation " +
"ADMINISTER_JOBS on queue default"));
}
//wait for job to complete on its own
while (!rjob.isComplete()) {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
break;
}
}
} finally {
tearDownCluster();
}
}
private void setUpCluster(JobConf conf) throws IOException {
miniDFSCluster = new MiniDFSCluster(conf, 1, true, null);
FileSystem fileSys = miniDFSCluster.getFileSystem();
String namenode = fileSys.getUri().toString();
miniMRCluster = new MiniMRCluster(1, namenode, 3,
null, null, conf);
}
private void tearDownCluster() throws IOException {
if (miniMRCluster != null) { miniMRCluster.shutdown(); }
if (miniDFSCluster != null) { miniDFSCluster.shutdown(); }
}
private RunningJob submitSleepJob(int numMappers, int numReducers,
long mapSleepTime, long reduceSleepTime,
boolean shouldComplete)
throws IOException {
return submitSleepJob(numMappers, numReducers, mapSleepTime,
reduceSleepTime, shouldComplete, null);
}
private RunningJob submitSleepJob(int numMappers, int numReducers,
long mapSleepTime, long reduceSleepTime,
boolean shouldComplete, String userInfo)
throws IOException {
return submitSleepJob(numMappers, numReducers, mapSleepTime,
reduceSleepTime, shouldComplete, userInfo, null);
}
private RunningJob submitSleepJob(int numMappers, int numReducers,
long mapSleepTime, long reduceSleepTime,
boolean shouldComplete, String userInfo,
String queueName)
throws IOException {
JobConf clientConf = new JobConf();
clientConf.set("mapred.job.tracker", "localhost:"
+ miniMRCluster.getJobTrackerPort());
SleepJob job = new SleepJob();
job.setConf(clientConf);
clientConf = job.setupJobConf(numMappers, numReducers,
mapSleepTime, (int)mapSleepTime/100,
reduceSleepTime, (int)reduceSleepTime/100);
if (queueName != null) {
clientConf.setQueueName(queueName);
}
JobConf jc = new JobConf(clientConf);
if (userInfo != null) {
jc.set(UnixUserGroupInformation.UGI_PROPERTY_NAME, userInfo);
}
RunningJob rJob = null;
if (shouldComplete) {
rJob = JobClient.runJob(jc);
} else {
rJob = new JobClient(clientConf).submitJob(jc);
}
return rJob;
}
}