YARN-5301. NM mount cpu cgroups failed on some systems

(Contributed by Miklos Szegedi via Daniel Templeton)
This commit is contained in:
Daniel Templeton 2017-05-09 11:18:21 -07:00
parent 7dd258d8f4
commit a2f680493f
7 changed files with 454 additions and 279 deletions

View File

@ -39,7 +39,11 @@ public interface CGroupsHandler {
CPU("cpu"),
NET_CLS("net_cls"),
BLKIO("blkio"),
MEMORY("memory");
MEMORY("memory"),
CPUACCT("cpuacct"),
CPUSET("cpuset"),
FREEZER("freezer"),
DEVICES("devices");
private final String name;

View File

@ -21,13 +21,16 @@
package org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.resources;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileUtil;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.util.Shell;
import org.apache.hadoop.yarn.conf.YarnConfiguration;
import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.privileged.PrivilegedOperation;
import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.privileged.PrivilegedOperationException;
@ -40,8 +43,9 @@ import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Matcher;
@ -66,6 +70,7 @@ class CGroupsHandlerImpl implements CGroupsHandler {
private final long deleteCGroupTimeout;
private final long deleteCGroupDelay;
private Map<CGroupController, String> controllerPaths;
private Map<String, Set<String>> parsedMtab;
private final ReadWriteLock rwLock;
private final PrivilegedOperationExecutor privilegedOperationExecutor;
private final Clock clock;
@ -95,6 +100,7 @@ class CGroupsHandlerImpl implements CGroupsHandler {
conf.getLong(YarnConfiguration.NM_LINUX_CONTAINER_CGROUPS_DELETE_DELAY,
YarnConfiguration.DEFAULT_NM_LINUX_CONTAINER_CGROUPS_DELETE_DELAY);
this.controllerPaths = new HashMap<>();
this.parsedMtab = new HashMap<>();
this.rwLock = new ReentrantReadWriteLock();
this.privilegedOperationExecutor = privilegedOperationExecutor;
this.clock = SystemClock.getInstance();
@ -128,51 +134,53 @@ class CGroupsHandlerImpl implements CGroupsHandler {
}
private void initializeControllerPaths() throws ResourceHandlerException {
if (enableCGroupMount) {
// nothing to do here - we support 'deferred' mounting of specific
// controllers - we'll populate the path for a given controller when an
// explicit mountCGroupController request is issued.
LOG.info("CGroup controller mounting enabled.");
} else {
// cluster admins are expected to have mounted controllers in specific
// locations - we'll attempt to figure out mount points
// Cluster admins may have some subsystems mounted in specific locations
// We'll attempt to figure out mount points. We do this even if we plan
// to mount cgroups into our own tree to control the path permissions or
// to mount subsystems that are not mounted previously.
// The subsystems for new and existing mount points have to match, and
// the same hierarchy will be mounted at each mount point with the same
// subsystem set.
Map<CGroupController, String> cPaths =
initializeControllerPathsFromMtab(mtabFile, this.cGroupPrefix);
// we want to do a bulk update without the paths changing concurrently
try {
rwLock.writeLock().lock();
controllerPaths = cPaths;
} finally {
rwLock.writeLock().unlock();
}
Map<String, Set<String>> newMtab;
Map<CGroupController, String> cPaths;
try {
// parse mtab
newMtab = parseMtab(mtabFile);
// find cgroup controller paths
cPaths = initializeControllerPathsFromMtab(newMtab);
} catch (IOException e) {
LOG.warn("Failed to initialize controller paths! Exception: " + e);
throw new ResourceHandlerException(
"Failed to initialize controller paths!");
}
// we want to do a bulk update without the paths changing concurrently
try {
rwLock.writeLock().lock();
controllerPaths = cPaths;
parsedMtab = newMtab;
} finally {
rwLock.writeLock().unlock();
}
}
@VisibleForTesting
static Map<CGroupController, String> initializeControllerPathsFromMtab(
String mtab, String cGroupPrefix) throws ResourceHandlerException {
try {
Map<String, List<String>> parsedMtab = parseMtab(mtab);
Map<CGroupController, String> ret = new HashMap<>();
Map<String, Set<String>> parsedMtab)
throws ResourceHandlerException {
Map<CGroupController, String> ret = new HashMap<>();
for (CGroupController controller : CGroupController.values()) {
String subsystemName = controller.getName();
String controllerPath = findControllerInMtab(subsystemName, parsedMtab);
for (CGroupController controller : CGroupController.values()) {
String subsystemName = controller.getName();
String controllerPath = findControllerInMtab(subsystemName, parsedMtab);
if (controllerPath != null) {
ret.put(controller, controllerPath);
} else {
LOG.warn("Controller not mounted but automount disabled: " +
subsystemName);
}
if (controllerPath != null) {
ret.put(controller, controllerPath);
}
return ret;
} catch (IOException e) {
LOG.warn("Failed to initialize controller paths! Exception: " + e);
throw new ResourceHandlerException(
"Failed to initialize controller paths!");
}
return ret;
}
/* We are looking for entries of the form:
@ -190,10 +198,15 @@ class CGroupsHandlerImpl implements CGroupsHandler {
* for mounts with type "cgroup". Cgroup controllers will
* appear in the list of options for a path.
*/
private static Map<String, List<String>> parseMtab(String mtab)
@VisibleForTesting
static Map<String, Set<String>> parseMtab(String mtab)
throws IOException {
Map<String, List<String>> ret = new HashMap<String, List<String>>();
Map<String, Set<String>> ret = new HashMap<>();
BufferedReader in = null;
HashSet<String> validCgroups = new HashSet<>();
for (CGroupController controller : CGroupController.values()) {
validCgroups.add(controller.getName());
}
try {
FileInputStream fis = new FileInputStream(new File(mtab));
@ -209,13 +222,21 @@ class CGroupsHandlerImpl implements CGroupsHandler {
String options = m.group(3);
if (type.equals(CGROUPS_FSTYPE)) {
List<String> value = Arrays.asList(options.split(","));
ret.put(path, value);
Set<String> cgroupList =
new HashSet<>(Arrays.asList(options.split(",")));
// Collect the valid subsystem names
cgroupList.retainAll(validCgroups);
ret.put(path, cgroupList);
}
}
}
} catch (IOException e) {
throw new IOException("Error while reading " + mtab, e);
if (Shell.LINUX) {
throw new IOException("Error while reading " + mtab, e);
} else {
// Ignore the error, if we are running on an os other than Linux
LOG.warn("Error while reading " + mtab, e);
}
} finally {
IOUtils.cleanup(LOG, in);
}
@ -234,8 +255,8 @@ class CGroupsHandlerImpl implements CGroupsHandler {
*/
@VisibleForTesting
static String findControllerInMtab(String controller,
Map<String, List<String>> entries) {
for (Map.Entry<String, List<String>> e : entries.entrySet()) {
Map<String, Set<String>> entries) {
for (Map.Entry<String, Set<String>> e : entries.entrySet()) {
if (e.getValue().contains(controller)) {
if (new File(e.getKey()).canRead()) {
return e.getKey();
@ -251,31 +272,45 @@ class CGroupsHandlerImpl implements CGroupsHandler {
private void mountCGroupController(CGroupController controller)
throws ResourceHandlerException {
String path = getControllerPath(controller);
if (cGroupMountPath == null) {
throw new ResourceHandlerException(
String.format("Cgroups mount path not specified in %s.",
YarnConfiguration.NM_LINUX_CONTAINER_CGROUPS_MOUNT_PATH));
}
String existingMountPath = getControllerPath(controller);
String requestedMountPath =
new File(cGroupMountPath, controller.getName()).getAbsolutePath();
if (path == null) {
if (existingMountPath == null ||
!requestedMountPath.equals(existingMountPath)) {
try {
//lock out other readers/writers till we are done
rwLock.writeLock().lock();
String hierarchy = cGroupPrefix;
StringBuffer controllerPath = new StringBuffer()
.append(cGroupMountPath).append('/').append(controller.getName());
StringBuffer cGroupKV = new StringBuffer()
.append(controller.getName()).append('=').append(controllerPath);
// If the controller was already mounted we have to mount it
// with the same options to clone the mount point otherwise
// the operation will fail
String mountOptions;
if (existingMountPath != null) {
mountOptions = Joiner.on(',')
.join(parsedMtab.get(existingMountPath));
} else {
mountOptions = controller.getName();
}
String cGroupKV =
mountOptions + "=" + requestedMountPath;
PrivilegedOperation.OperationType opType = PrivilegedOperation
.OperationType.MOUNT_CGROUPS;
PrivilegedOperation op = new PrivilegedOperation(opType);
op.appendArgs(hierarchy, cGroupKV.toString());
op.appendArgs(cGroupPrefix, cGroupKV);
LOG.info("Mounting controller " + controller.getName() + " at " +
controllerPath);
requestedMountPath);
privilegedOperationExecutor.executePrivilegedOperation(op, false);
//if privileged operation succeeds, update controller paths
controllerPaths.put(controller, controllerPath.toString());
return;
controllerPaths.put(controller, requestedMountPath);
} catch (PrivilegedOperationException e) {
LOG.error("Failed to mount controller: " + controller.getName());
throw new ResourceHandlerException("Failed to mount controller: "
@ -284,37 +319,34 @@ class CGroupsHandlerImpl implements CGroupsHandler {
rwLock.writeLock().unlock();
}
} else {
LOG.info("CGroup controller already mounted at: " + path);
return;
LOG.info("CGroup controller already mounted at: " + existingMountPath);
}
}
@Override
public String getRelativePathForCGroup(String cGroupId) {
return new StringBuffer(cGroupPrefix).append("/")
.append(cGroupId).toString();
return cGroupPrefix + Path.SEPARATOR + cGroupId;
}
@Override
public String getPathForCGroup(CGroupController controller, String cGroupId) {
return new StringBuffer(getControllerPath(controller))
.append('/').append(cGroupPrefix).append("/")
.append(cGroupId).toString();
return getControllerPath(controller) + Path.SEPARATOR + cGroupPrefix
+ Path.SEPARATOR + cGroupId;
}
@Override
public String getPathForCGroupTasks(CGroupController controller,
String cGroupId) {
return new StringBuffer(getPathForCGroup(controller, cGroupId))
.append('/').append(CGROUP_FILE_TASKS).toString();
return getPathForCGroup(controller, cGroupId)
+ Path.SEPARATOR + CGROUP_FILE_TASKS;
}
@Override
public String getPathForCGroupParam(CGroupController controller,
String cGroupId, String param) {
return new StringBuffer(getPathForCGroup(controller, cGroupId))
.append('/').append(controller.getName()).append('.')
.append(param).toString();
return getPathForCGroup(controller, cGroupId)
+ Path.SEPARATOR + controller.getName()
+ "." + param;
}
/**
@ -330,10 +362,21 @@ class CGroupsHandlerImpl implements CGroupsHandler {
// We have a controller that needs to be mounted
mountCGroupController(controller);
} else {
// We are working with a pre-mounted contoller
// Make sure that Yarn cgroup hierarchy path exists
initializePreMountedCGroupController(controller);
String controllerPath = getControllerPath(controller);
if (controllerPath == null) {
throw new ResourceHandlerException(
String.format("Controller %s not mounted."
+ " You either need to mount it with %s"
+ " or mount cgroups before launching Yarn",
controller.getName(), YarnConfiguration.
NM_LINUX_CONTAINER_CGROUPS_MOUNT));
}
}
// We are working with a pre-mounted contoller
// Make sure that Yarn cgroup hierarchy path exists
initializePreMountedCGroupController(controller);
}
/**
@ -347,11 +390,22 @@ class CGroupsHandlerImpl implements CGroupsHandler {
* @throws ResourceHandlerException yarn hierarchy cannot be created or
* accessed for any reason
*/
public void initializePreMountedCGroupController(CGroupController controller)
private void initializePreMountedCGroupController(CGroupController controller)
throws ResourceHandlerException {
// Check permissions to cgroup hierarchy and
// create YARN cgroup if it does not exist, yet
File rootHierarchy = new File(getControllerPath(controller));
String controllerPath = getControllerPath(controller);
if (controllerPath == null) {
throw new ResourceHandlerException(
String.format("Controller %s not mounted."
+ " You either need to mount it with %s"
+ " or mount cgroups before launching Yarn",
controller.getName(), YarnConfiguration.
NM_LINUX_CONTAINER_CGROUPS_MOUNT));
}
File rootHierarchy = new File(controllerPath);
File yarnHierarchy = new File(rootHierarchy, cGroupPrefix);
String subsystemName = controller.getName();
@ -403,17 +457,9 @@ class CGroupsHandlerImpl implements CGroupsHandler {
String errorMessage,
String subsystemName,
String yarnCgroupPath) {
return new StringBuilder()
.append(errorMessage)
.append(" Subsystem:")
.append(subsystemName)
.append(" Mount points:")
.append(mtabFile)
.append(" User:")
.append(System.getProperty("user.name"))
.append(" Path: ")
.append(yarnCgroupPath)
.toString();
return String.format("%s Subsystem:%s Mount points:%s User:%s Path:%s ",
errorMessage, subsystemName, mtabFile, System.getProperty("user.name"),
yarnCgroupPath);
}
@Override
@ -456,7 +502,7 @@ class CGroupsHandlerImpl implements CGroupsHandler {
* @param cgf object referring to the cgroup to be deleted
* @return Boolean indicating whether cgroup was deleted
*/
boolean checkAndDeleteCgroup(File cgf) throws InterruptedException {
private boolean checkAndDeleteCgroup(File cgf) throws InterruptedException {
boolean deleted = false;
// FileInputStream in = null;
try (FileInputStream in = new FileInputStream(cgf + "/tasks")) {
@ -504,8 +550,8 @@ class CGroupsHandlerImpl implements CGroupsHandler {
} while (!deleted && (clock.getTime() - start) < deleteCGroupTimeout);
if (!deleted) {
LOG.warn("Unable to delete " + cGroupPath +
", tried to delete for " + deleteCGroupTimeout + "ms");
LOG.warn(String.format("Unable to delete %s, tried to delete for %d ms",
cGroupPath, deleteCGroupTimeout));
}
}
@ -517,8 +563,8 @@ class CGroupsHandlerImpl implements CGroupsHandler {
if (LOG.isDebugEnabled()) {
LOG.debug(
"updateCGroupParam for path: " + cGroupParamPath + " with value " +
value);
String.format("updateCGroupParam for path: %s with value %s",
cGroupParamPath, value));
}
try {
@ -527,22 +573,22 @@ class CGroupsHandlerImpl implements CGroupsHandler {
pw = new PrintWriter(w);
pw.write(value);
} catch (IOException e) {
throw new ResourceHandlerException(new StringBuffer("Unable to write to ")
.append(cGroupParamPath).append(" with value: ").append(value)
.toString(), e);
throw new ResourceHandlerException(
String.format("Unable to write to %s with value: %s",
cGroupParamPath, value), e);
} finally {
if (pw != null) {
boolean hasError = pw.checkError();
pw.close();
if (hasError) {
throw new ResourceHandlerException(
new StringBuffer("Unable to write to ")
.append(cGroupParamPath).append(" with value: ").append(value)
.toString());
String.format("PrintWriter unable to write to %s with value: %s",
cGroupParamPath, value));
}
if (pw.checkError()) {
throw new ResourceHandlerException("Error while closing cgroup file" +
" " + cGroupParamPath);
throw new ResourceHandlerException(
String.format("Error while closing cgroup file %s",
cGroupParamPath));
}
}
}

View File

@ -28,17 +28,18 @@ import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Sets;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
@ -262,7 +263,7 @@ public class CgroupsLCEResourcesHandler implements LCEResourcesHandler {
/**
* If tasks file is empty, delete the cgroup.
*
* @param file object referring to the cgroup to be deleted
* @param cgf object referring to the cgroup to be deleted
* @return Boolean indicating whether cgroup was deleted
*/
@VisibleForTesting
@ -396,8 +397,8 @@ public class CgroupsLCEResourcesHandler implements LCEResourcesHandler {
* for mounts with type "cgroup". Cgroup controllers will
* appear in the list of options for a path.
*/
private Map<String, List<String>> parseMtab() throws IOException {
Map<String, List<String>> ret = new HashMap<String, List<String>>();
private Map<String, Set<String>> parseMtab() throws IOException {
Map<String, Set<String>> ret = new HashMap<String, Set<String>>();
BufferedReader in = null;
try {
@ -414,7 +415,7 @@ public class CgroupsLCEResourcesHandler implements LCEResourcesHandler {
String options = m.group(3);
if (type.equals(CGROUPS_FSTYPE)) {
List<String> value = Arrays.asList(options.split(","));
HashSet<String> value = Sets.newHashSet(options.split(","));
ret.put(path, value);
}
}
@ -430,8 +431,8 @@ public class CgroupsLCEResourcesHandler implements LCEResourcesHandler {
@VisibleForTesting
String findControllerInMtab(String controller,
Map<String, List<String>> entries) {
for (Entry<String, List<String>> e : entries.entrySet()) {
Map<String, Set<String>> entries) {
for (Entry<String, Set<String>> e : entries.entrySet()) {
if (e.getValue().contains(controller)) {
if (new File(e.getKey()).canRead()) {
return e.getKey();
@ -447,7 +448,7 @@ public class CgroupsLCEResourcesHandler implements LCEResourcesHandler {
private void initializeControllerPaths() throws IOException {
String controllerPath;
Map<String, List<String>> parsedMtab = parseMtab();
Map<String, Set<String>> parsedMtab = parseMtab();
// CPU

View File

@ -2050,17 +2050,31 @@ int mount_cgroup(const char *pair, const char *hierarchy) {
fprintf(LOGFILE, "Failed to mount cgroup controller, not supported\n");
return -1;
#else
char *controller = malloc(strlen(pair));
char *mount_path = malloc(strlen(pair));
size_t len = strlen(pair);
char *controller = malloc(len);
char *mount_path = malloc(len);
char hier_path[EXECUTOR_PATH_MAX];
int result = 0;
struct stat sb;
if (get_kv_key(pair, controller, strlen(pair)) < 0 ||
get_kv_value(pair, mount_path, strlen(pair)) < 0) {
if (controller == NULL || mount_path == NULL) {
fprintf(LOGFILE, "Failed to mount cgroup controller; not enough memory\n");
result = OUT_OF_MEMORY;
}
if (get_kv_key(pair, controller, len) < 0 ||
get_kv_value(pair, mount_path, len) < 0) {
fprintf(LOGFILE, "Failed to mount cgroup controller; invalid option: %s\n",
pair);
result = -1;
} else {
if (stat(mount_path, &sb) != 0) {
// Create mount point, if it does not exist
const mode_t mount_perms = S_IRWXU | S_IRGRP | S_IXGRP;
if (mkdirs(mount_path, mount_perms) == 0) {
fprintf(LOGFILE, "Failed to create cgroup mount point %s at %s\n",
controller, mount_path);
}
}
if (mount("none", mount_path, "cgroup", 0, controller) == 0) {
char *buf = stpncpy(hier_path, mount_path, strlen(mount_path));
*buf++ = '/';

View File

@ -25,6 +25,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileUtil;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.yarn.conf.YarnConfiguration;
import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.privileged.PrivilegedOperation;
import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.privileged.PrivilegedOperationException;
@ -42,10 +43,11 @@ import java.nio.file.Files;
import java.security.Permission;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@ -60,7 +62,6 @@ public class TestCGroupsHandlerImpl {
LogFactory.getLog(TestCGroupsHandlerImpl.class);
private PrivilegedOperationExecutor privilegedOperationExecutorMock;
private Configuration conf;
private String tmpPath;
private String hierarchy;
private CGroupsHandler.CGroupController controller;
@ -69,36 +70,143 @@ public class TestCGroupsHandlerImpl {
@Before
public void setup() {
privilegedOperationExecutorMock = mock(PrivilegedOperationExecutor.class);
conf = new YarnConfiguration();
// Prepare test directory
tmpPath = System.getProperty("test.build.data") + "/cgroups";
File tmpDir = new File(tmpPath);
FileUtils.deleteQuietly(tmpDir);
assertTrue(tmpDir.mkdirs());
//no leading or trailing slashes here
hierarchy = "test-hadoop-yarn";
// Sample subsystem. Not used by all the tests
controller = CGroupsHandler.CGroupController.NET_CLS;
controllerPath =
new File(new File(tmpPath, controller.getName()), hierarchy)
.getAbsolutePath();
}
@After
public void teardown() {
FileUtil.fullyDelete(new File(tmpPath));
}
/**
* Security manager simulating access denied.
*/
private class MockSecurityManagerDenyWrite extends SecurityManager {
@Override
public void checkPermission(Permission perm) {
if(perm.getActions().equals("write")) {
throw new SecurityException("Mock not allowed");
}
}
}
/**
* Create configuration to mount cgroups that do not exist.
* @return configuration object
*/
private YarnConfiguration createMountConfiguration() {
YarnConfiguration conf = new YarnConfiguration();
conf.set(YarnConfiguration.NM_LINUX_CONTAINER_CGROUPS_HIERARCHY, hierarchy);
conf.setBoolean(YarnConfiguration.NM_LINUX_CONTAINER_CGROUPS_MOUNT, true);
conf.set(YarnConfiguration.NM_LINUX_CONTAINER_CGROUPS_MOUNT_PATH, tmpPath);
controller = CGroupsHandler.CGroupController.NET_CLS;
controllerPath = new StringBuffer(tmpPath).append('/')
.append(controller.getName()).append('/').append(hierarchy).toString();
return conf;
}
/**
* Create configuration where the cgroups are premounted.
* @param myHierarchy Yarn cgroup
* @return configuration object
*/
private Configuration createNoMountConfiguration(String myHierarchy) {
Configuration confNoMount = new Configuration();
confNoMount.set(YarnConfiguration.NM_LINUX_CONTAINER_CGROUPS_HIERARCHY,
myHierarchy);
confNoMount.setBoolean(YarnConfiguration.NM_LINUX_CONTAINER_CGROUPS_MOUNT,
false);
return confNoMount;
}
/**
* Create an empty mtab file. No cgroups are premounted
* @return mtab file
* @throws IOException could not create file
*/
private File createEmptyCgroups() throws IOException {
File emptyMtab = new File(tmpPath, "mtab");
assertTrue("New file should have been created", emptyMtab.createNewFile());
return emptyMtab;
}
/**
* Create simulated cgroups mount point.
* @param parentDir cgroups mount point
* @param cpuAcct simulate newer Linux behavior by mounting cpu with cpuacct
* @return simulated mtab file location
* @throws IOException mtab file was not created
*/
public static File createPremountedCgroups(File parentDir, boolean cpuAcct)
throws IOException {
// Mark an empty directory called 'cp' cgroup. It is processed before 'cpu'
String cpuMtabContentMissing =
"none " + parentDir.getAbsolutePath()
+ "/cp cgroup rw,relatime,cpu 0 0\n";
File cpuCgroup = new File(parentDir, "cpu");
String cpuMtabContent =
"none " + cpuCgroup.getAbsolutePath()
+ " cgroup rw,relatime,cpu"
+ (cpuAcct ? ",cpuacct" :"")
+ " 0 0\n";
assertTrue("Directory should be created", cpuCgroup.mkdirs());
File blkioCgroup = new File(parentDir, "blkio");
String blkioMtabContent =
"none " + blkioCgroup.getAbsolutePath()
+ " cgroup rw,relatime,blkio 0 0\n";
assertTrue("Directory should be created", blkioCgroup.mkdirs());
File mockMtab = new File(parentDir, UUID.randomUUID().toString());
if (!mockMtab.exists()) {
if (!mockMtab.createNewFile()) {
String message = "Could not create file " + mockMtab.getAbsolutePath();
throw new IOException(message);
}
}
FileWriter mtabWriter = new FileWriter(mockMtab.getAbsoluteFile());
mtabWriter.write(cpuMtabContentMissing);
mtabWriter.write(cpuMtabContent);
mtabWriter.write(blkioMtabContent);
mtabWriter.close();
mockMtab.deleteOnExit();
return mockMtab;
}
@Test
public void testMountController() {
CGroupsHandler cGroupsHandler = null;
public void testMountController() throws IOException {
File parentDir = new File(tmpPath);
File cgroup = new File(parentDir, controller.getName());
assertTrue("cgroup dir should be cerated", cgroup.mkdirs());
//Since we enabled (deferred) cgroup controller mounting, no interactions
//should have occurred, with this mock
verifyZeroInteractions(privilegedOperationExecutorMock);
File emptyMtab = createEmptyCgroups();
try {
cGroupsHandler = new CGroupsHandlerImpl(conf,
privilegedOperationExecutorMock);
CGroupsHandler cGroupsHandler = new CGroupsHandlerImpl(
createMountConfiguration(),
privilegedOperationExecutorMock,
emptyMtab.getAbsolutePath());
PrivilegedOperation expectedOp = new PrivilegedOperation(
PrivilegedOperation.OperationType.MOUNT_CGROUPS);
//This is expected to be of the form :
//net_cls=<mount_path>/net_cls
StringBuffer controllerKV = new StringBuffer(controller.getName())
.append('=').append(tmpPath).append('/').append(controller.getName());
expectedOp.appendArgs(hierarchy, controllerKV.toString());
String controllerKV = controller.getName() + "=" + tmpPath
+ Path.SEPARATOR + controller.getName();
expectedOp.appendArgs(hierarchy, controllerKV);
cGroupsHandler.initializeCGroupController(controller);
try {
@ -117,79 +225,90 @@ public class TestCGroupsHandlerImpl {
verifyNoMoreInteractions(privilegedOperationExecutorMock);
} catch (PrivilegedOperationException e) {
LOG.error("Caught exception: " + e);
Assert.assertTrue("Unexpected PrivilegedOperationException from mock!",
assertTrue("Unexpected PrivilegedOperationException from mock!",
false);
}
} catch (ResourceHandlerException e) {
LOG.error("Caught exception: " + e);
Assert.assertTrue("Unexpected ResourceHandler Exception!", false);
assertTrue("Unexpected ResourceHandler Exception!", false);
}
}
@Test
public void testCGroupPaths() {
public void testCGroupPaths() throws IOException {
//As per junit behavior, we expect a new mock object to be available
//in this test.
verifyZeroInteractions(privilegedOperationExecutorMock);
CGroupsHandler cGroupsHandler = null;
File mtab = createEmptyCgroups();
// Lets manually create a path to (partially) simulate a controller mounted
// later in the test. This is required because the handler uses a mocked
// privileged operation executor
assertTrue("Sample subsystem should be created",
new File(controllerPath).mkdirs());
try {
cGroupsHandler = new CGroupsHandlerImpl(conf,
privilegedOperationExecutorMock);
cGroupsHandler = new CGroupsHandlerImpl(createMountConfiguration(),
privilegedOperationExecutorMock, mtab.getAbsolutePath());
cGroupsHandler.initializeCGroupController(controller);
} catch (ResourceHandlerException e) {
LOG.error("Caught exception: " + e);
Assert.assertTrue(
assertTrue(
"Unexpected ResourceHandlerException when mounting controller!",
false);
}
String testCGroup = "container_01";
String expectedPath = new StringBuffer(controllerPath).append('/')
.append(testCGroup).toString();
String expectedPath =
controllerPath + Path.SEPARATOR + testCGroup;
String path = cGroupsHandler.getPathForCGroup(controller, testCGroup);
Assert.assertEquals(expectedPath, path);
String expectedPathTasks = new StringBuffer(expectedPath).append('/')
.append(CGroupsHandler.CGROUP_FILE_TASKS).toString();
String expectedPathTasks = expectedPath + Path.SEPARATOR
+ CGroupsHandler.CGROUP_FILE_TASKS;
path = cGroupsHandler.getPathForCGroupTasks(controller, testCGroup);
Assert.assertEquals(expectedPathTasks, path);
String param = CGroupsHandler.CGROUP_PARAM_CLASSID;
String expectedPathParam = new StringBuffer(expectedPath).append('/')
.append(controller.getName()).append('.').append(param).toString();
String expectedPathParam = expectedPath + Path.SEPARATOR
+ controller.getName() + "." + param;
path = cGroupsHandler.getPathForCGroupParam(controller, testCGroup, param);
Assert.assertEquals(expectedPathParam, path);
}
@Test
public void testCGroupOperations() {
public void testCGroupOperations() throws IOException {
//As per junit behavior, we expect a new mock object to be available
//in this test.
verifyZeroInteractions(privilegedOperationExecutorMock);
CGroupsHandler cGroupsHandler = null;
File mtab = createEmptyCgroups();
// Lets manually create a path to (partially) simulate a controller mounted
// later in the test. This is required because the handler uses a mocked
// privileged operation executor
assertTrue("Sample subsystem should be created",
new File(controllerPath).mkdirs());
try {
cGroupsHandler = new CGroupsHandlerImpl(conf,
privilegedOperationExecutorMock);
cGroupsHandler = new CGroupsHandlerImpl(createMountConfiguration(),
privilegedOperationExecutorMock, mtab.getAbsolutePath());
cGroupsHandler.initializeCGroupController(controller);
} catch (ResourceHandlerException e) {
LOG.error("Caught exception: " + e);
Assert.assertTrue(
assertTrue(
"Unexpected ResourceHandlerException when mounting controller!",
false);
}
//Lets manually create a path to (partially) simulate a mounted controller
//this is required because the handler uses a mocked privileged operation
//executor
new File(controllerPath).mkdirs();
String testCGroup = "container_01";
String expectedPath = new StringBuffer(controllerPath).append('/')
.append(testCGroup).toString();
String expectedPath = controllerPath
+ Path.SEPARATOR + testCGroup;
try {
String path = cGroupsHandler.createCGroup(controller, testCGroup);
Assert.assertTrue(new File(expectedPath).exists());
assertTrue(new File(expectedPath).exists());
Assert.assertEquals(expectedPath, path);
//update param and read param tests.
@ -202,11 +321,12 @@ public class TestCGroupsHandlerImpl {
cGroupsHandler
.updateCGroupParam(controller, testCGroup, param, paramValue);
String paramPath = new StringBuffer(expectedPath).append('/')
.append(controller.getName()).append('.').append(param).toString();
String paramPath = expectedPath
+ Path.SEPARATOR + controller.getName()
+ "." + param;
File paramFile = new File(paramPath);
Assert.assertTrue(paramFile.exists());
assertTrue(paramFile.exists());
try {
Assert.assertEquals(paramValue, new String(Files.readAllBytes(
paramFile.toPath())));
@ -233,52 +353,6 @@ public class TestCGroupsHandlerImpl {
}
}
public static File createMockCgroupMount(File parentDir, String type)
throws IOException {
return createMockCgroupMount(parentDir, type, "hadoop-yarn");
}
private static File createMockCgroupMount(File parentDir, String type,
String hierarchy) throws IOException {
File cgroupMountDir =
new File(parentDir.getAbsolutePath(), type + "/" + hierarchy);
FileUtils.deleteQuietly(cgroupMountDir);
if (!cgroupMountDir.mkdirs()) {
String message =
"Could not create dir " + cgroupMountDir.getAbsolutePath();
throw new IOException(message);
}
return cgroupMountDir;
}
public static File createMockMTab(File parentDir) throws IOException {
String cpuMtabContent =
"none " + parentDir.getAbsolutePath()
+ "/cpu cgroup rw,relatime,cpu 0 0\n";
// Mark an empty directory called 'cp' cgroup. It is processed before 'cpu'
String cpuMtabContentMissing =
"none " + parentDir.getAbsolutePath()
+ "/cp cgroup rw,relatime,cpu 0 0\n";
String blkioMtabContent =
"none " + parentDir.getAbsolutePath()
+ "/blkio cgroup rw,relatime,blkio 0 0\n";
File mockMtab = new File(parentDir, UUID.randomUUID().toString());
if (!mockMtab.exists()) {
if (!mockMtab.createNewFile()) {
String message = "Could not create file " + mockMtab.getAbsolutePath();
throw new IOException(message);
}
}
FileWriter mtabWriter = new FileWriter(mockMtab.getAbsoluteFile());
mtabWriter.write(cpuMtabContentMissing);
mtabWriter.write(cpuMtabContent);
mtabWriter.write(blkioMtabContent);
mtabWriter.close();
mockMtab.deleteOnExit();
return mockMtab;
}
/**
* Tests whether mtab parsing works as expected with a valid hierarchy set.
* @throws Exception the test will fail
@ -288,24 +362,20 @@ public class TestCGroupsHandlerImpl {
// Initialize mtab and cgroup dir
File parentDir = new File(tmpPath);
// create mock cgroup
File cpuCgroupMountDir = createMockCgroupMount(parentDir, "cpu",
hierarchy);
Assert.assertTrue(cpuCgroupMountDir.exists());
File blkioCgroupMountDir = createMockCgroupMount(parentDir,
"blkio", hierarchy);
Assert.assertTrue(blkioCgroupMountDir.exists());
File mockMtabFile = createMockMTab(parentDir);
File mockMtabFile = createPremountedCgroups(parentDir, false);
// Run mtabs parsing
Map<String, Set<String>> newMtab =
CGroupsHandlerImpl.parseMtab(mockMtabFile.getAbsolutePath());
Map<CGroupsHandler.CGroupController, String> controllerPaths =
CGroupsHandlerImpl.initializeControllerPathsFromMtab(
mockMtabFile.getAbsolutePath(), hierarchy);
newMtab);
// Verify
Assert.assertEquals(2, controllerPaths.size());
Assert.assertTrue(controllerPaths
assertTrue(controllerPaths
.containsKey(CGroupsHandler.CGroupController.CPU));
Assert.assertTrue(controllerPaths
assertTrue(controllerPaths
.containsKey(CGroupsHandler.CGroupController.BLKIO));
String cpuDir = controllerPaths.get(CGroupsHandler.CGroupController.CPU);
String blkioDir =
@ -314,17 +384,6 @@ public class TestCGroupsHandlerImpl {
Assert.assertEquals(parentDir.getAbsolutePath() + "/blkio", blkioDir);
}
/**
* Tests whether mtab parsing works as expected with an empty hierarchy set.
* @throws Exception the test will fail
*/
@Test
public void testPreMountedController() throws Exception {
testPreMountedControllerInitialization("hadoop-yarn");
testPreMountedControllerInitialization("");
testPreMountedControllerInitialization("/");
}
/**
* Tests whether mtab parsing works as expected with the specified hierarchy.
* @param myHierarchy path to local cgroup hierarchy
@ -334,64 +393,57 @@ public class TestCGroupsHandlerImpl {
throws Exception {
// Initialize mount point
File parentDir = new File(tmpPath);
FileUtils.deleteQuietly(parentDir);
Assert.assertTrue("Could not create dirs", parentDir.mkdirs());
File mtab = createMockMTab(parentDir);
File mtab = createPremountedCgroups(parentDir, false);
File mountPoint = new File(parentDir, "cpu");
File cpuCgroupMountDir = createMockCgroupMount(
parentDir, "cpu", myHierarchy);
// Initialize Yarn classes
Configuration confNoMount = new Configuration();
confNoMount.set(YarnConfiguration.NM_LINUX_CONTAINER_CGROUPS_HIERARCHY,
myHierarchy);
confNoMount.setBoolean(YarnConfiguration.NM_LINUX_CONTAINER_CGROUPS_MOUNT,
false);
Configuration confNoMount = createNoMountConfiguration(myHierarchy);
CGroupsHandlerImpl cGroupsHandler = new CGroupsHandlerImpl(confNoMount,
privilegedOperationExecutorMock, mtab.getAbsolutePath());
File cpuCgroupMountDir = new File(
cGroupsHandler.getPathForCGroup(CGroupsHandler.CGroupController.CPU,
""));
// Test that a missing yarn hierarchy will be created automatically
if (!cpuCgroupMountDir.equals(mountPoint)) {
Assert.assertTrue("Could not delete cgroups", cpuCgroupMountDir.delete());
Assert.assertTrue("Directory should be deleted",
assertTrue("Directory should be deleted",
!cpuCgroupMountDir.exists());
}
cGroupsHandler.initializeCGroupController(
CGroupsHandler.CGroupController.CPU);
Assert.assertTrue("Cgroups not writable", cpuCgroupMountDir.exists() &&
assertTrue("Cgroups not writable", cpuCgroupMountDir.exists() &&
cpuCgroupMountDir.canWrite());
// Test that an inaccessible yarn hierarchy results in an exception
Assert.assertTrue(cpuCgroupMountDir.setWritable(false));
assertTrue(cpuCgroupMountDir.setWritable(false));
try {
cGroupsHandler.initializeCGroupController(
CGroupsHandler.CGroupController.CPU);
Assert.fail("An inaccessible path should result in an exception");
} catch (Exception e) {
Assert.assertTrue("Unexpected exception " + e.getClass().toString(),
assertTrue("Unexpected exception " + e.getClass().toString(),
e instanceof ResourceHandlerException);
} finally {
Assert.assertTrue("Could not revert writable permission",
assertTrue("Could not revert writable permission",
cpuCgroupMountDir.setWritable(true));
}
// Test that a non-accessible mount directory results in an exception
if (!cpuCgroupMountDir.equals(mountPoint)) {
Assert.assertTrue("Could not delete cgroups", cpuCgroupMountDir.delete());
Assert.assertTrue("Directory should be deleted",
assertTrue("Could not delete cgroups", cpuCgroupMountDir.delete());
assertTrue("Directory should be deleted",
!cpuCgroupMountDir.exists());
}
Assert.assertTrue(mountPoint.setWritable(false));
assertTrue(mountPoint.setWritable(false));
try {
cGroupsHandler.initializeCGroupController(
CGroupsHandler.CGroupController.CPU);
Assert.fail("An inaccessible path should result in an exception");
} catch (Exception e) {
Assert.assertTrue("Unexpected exception " + e.getClass().toString(),
assertTrue("Unexpected exception " + e.getClass().toString(),
e instanceof ResourceHandlerException);
} finally {
Assert.assertTrue("Could not revert writable permission",
assertTrue("Could not revert writable permission",
mountPoint.setWritable(true));
}
@ -399,7 +451,7 @@ public class TestCGroupsHandlerImpl {
if (!cpuCgroupMountDir.equals(mountPoint)) {
Assert.assertFalse("Could not delete cgroups",
cpuCgroupMountDir.delete());
Assert.assertTrue("Directory should be deleted",
assertTrue("Directory should be deleted",
!cpuCgroupMountDir.exists());
SecurityManager manager = System.getSecurityManager();
System.setSecurityManager(new MockSecurityManagerDenyWrite());
@ -408,7 +460,7 @@ public class TestCGroupsHandlerImpl {
CGroupsHandler.CGroupController.CPU);
Assert.fail("An inaccessible path should result in an exception");
} catch (Exception e) {
Assert.assertTrue("Unexpected exception " + e.getClass().toString(),
assertTrue("Unexpected exception " + e.getClass().toString(),
e instanceof ResourceHandlerException);
} finally {
System.setSecurityManager(manager);
@ -419,18 +471,18 @@ public class TestCGroupsHandlerImpl {
if (!cpuCgroupMountDir.equals(mountPoint)) {
Assert.assertFalse("Could not delete cgroups",
cpuCgroupMountDir.delete());
Assert.assertTrue("Directory should be deleted",
assertTrue("Directory should be deleted",
!cpuCgroupMountDir.exists());
}
FileUtils.deleteQuietly(mountPoint);
Assert.assertTrue("cgroups mount point should be deleted",
assertTrue("cgroups mount point should be deleted",
!mountPoint.exists());
try {
cGroupsHandler.initializeCGroupController(
CGroupsHandler.CGroupController.CPU);
Assert.fail("An inaccessible path should result in an exception");
} catch (Exception e) {
Assert.assertTrue("Unexpected exception " + e.getClass().toString(),
assertTrue("Unexpected exception " + e.getClass().toString(),
e instanceof ResourceHandlerException);
}
}
@ -442,19 +494,19 @@ public class TestCGroupsHandlerImpl {
File memory = new File(tmpPath, "memory");
try {
CGroupsHandlerImpl handler = new CGroupsHandlerImpl(
conf,
createNoMountConfiguration(tmpPath),
privilegedOperationExecutorMock);
Map<String, List<String>> cgroups = new LinkedHashMap<>();
Map<String, Set<String>> cgroups = new LinkedHashMap<>();
Assert.assertTrue("temp dir should be created", cpu.mkdirs());
Assert.assertTrue("temp dir should be created", memory.mkdirs());
Assert.assertFalse("temp dir should not be created", cpuNoExist.exists());
cgroups.put(
memory.getAbsolutePath(), Collections.singletonList("memory"));
memory.getAbsolutePath(), Collections.singleton("memory"));
cgroups.put(
cpuNoExist.getAbsolutePath(), Collections.singletonList("cpu"));
cgroups.put(cpu.getAbsolutePath(), Collections.singletonList("cpu"));
cpuNoExist.getAbsolutePath(), Collections.singleton("cpu"));
cgroups.put(cpu.getAbsolutePath(), Collections.singleton("cpu"));
String selectedCPU = handler.findControllerInMtab("cpu", cgroups);
Assert.assertEquals("Wrong CPU mount point selected",
cpu.getAbsolutePath(), selectedCPU);
@ -464,17 +516,61 @@ public class TestCGroupsHandlerImpl {
}
}
@After
public void teardown() {
FileUtil.fullyDelete(new File(tmpPath));
/**
* Tests whether mtab parsing works as expected with an empty hierarchy set.
* @throws Exception the test will fail
*/
@Test
public void testPreMountedControllerEmpty() throws Exception {
testPreMountedControllerInitialization("");
}
private class MockSecurityManagerDenyWrite extends SecurityManager {
@Override
public void checkPermission(Permission perm) {
if(perm.getActions().equals("write")) {
throw new SecurityException("Mock not allowed");
}
}
/**
* Tests whether mtab parsing works as expected with a / hierarchy set.
* @throws Exception the test will fail
*/
@Test
public void testPreMountedControllerRoot() throws Exception {
testPreMountedControllerInitialization("/");
}
/**
* Tests whether mtab parsing works as expected with the specified hierarchy.
* @throws Exception the test will fail
*/
@Test
public void testRemount()
throws Exception {
// Initialize mount point
File parentDir = new File(tmpPath);
final String oldMountPointDir = "oldmount";
final String newMountPointDir = "newmount";
File oldMountPoint = new File(parentDir, oldMountPointDir);
File mtab = createPremountedCgroups(
oldMountPoint, true);
File newMountPoint = new File(parentDir, newMountPointDir);
assertTrue("Could not create dirs",
new File(newMountPoint, "cpu").mkdirs());
// Initialize Yarn classes
Configuration confMount = createMountConfiguration();
confMount.set(YarnConfiguration.NM_LINUX_CONTAINER_CGROUPS_MOUNT_PATH,
parentDir.getAbsolutePath() + Path.SEPARATOR + newMountPointDir);
CGroupsHandlerImpl cGroupsHandler = new CGroupsHandlerImpl(confMount,
privilegedOperationExecutorMock, mtab.getAbsolutePath());
cGroupsHandler.initializeCGroupController(
CGroupsHandler.CGroupController.CPU);
ArgumentCaptor<PrivilegedOperation> opCaptor = ArgumentCaptor.forClass(
PrivilegedOperation.class);
verify(privilegedOperationExecutorMock)
.executePrivilegedOperation(opCaptor.capture(), eq(false));
File hierarchyFile =
new File(new File(newMountPoint, "cpu"), this.hierarchy);
assertTrue("Yarn cgroup should exist", hierarchyFile.exists());
}
}

View File

@ -43,9 +43,6 @@ public class TestResourceHandlerModule {
networkEnabledConf.setBoolean(YarnConfiguration.NM_NETWORK_RESOURCE_ENABLED,
true);
//We need to bypass mtab parsing for figuring out cgroups mount locations
networkEnabledConf.setBoolean(YarnConfiguration
.NM_LINUX_CONTAINER_CGROUPS_MOUNT, true);
ResourceHandlerModule.nullifyResourceHandlerChain();
}

View File

@ -38,11 +38,12 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
@Deprecated
public class TestCgroupsLCEResourcesHandler {
static File cgroupDir = null;
private static File cgroupDir = null;
@Before
public void setUp() throws Exception {
@ -132,9 +133,9 @@ public class TestCgroupsLCEResourcesHandler {
static class CustomCgroupsLCEResourceHandler extends
CgroupsLCEResourcesHandler {
String mtabFile;
int[] limits = new int[2];
boolean generateLimitsMode = false;
private String mtabFile;
private int[] limits = new int[2];
private boolean generateLimitsMode = false;
@Override
int[] getOverallLimits(float x) {
@ -154,6 +155,20 @@ public class TestCgroupsLCEResourcesHandler {
}
}
private static File createMockCgroupMount(File parentDir,
String type)
throws IOException {
File cgroupMountDir =
new File(parentDir.getAbsolutePath(), type + "/hadoop-yarn");
FileUtils.deleteQuietly(cgroupMountDir);
if (!cgroupMountDir.mkdirs()) {
String message =
"Could not create dir " + cgroupMountDir.getAbsolutePath();
throw new IOException(message);
}
return cgroupMountDir;
}
@Test
public void testInit() throws IOException {
LinuxContainerExecutor mockLCE = new MockLinuxContainerExecutor();
@ -168,12 +183,13 @@ public class TestCgroupsLCEResourcesHandler {
handler.setConf(conf);
handler.initConfig();
// create mock cgroup
File cpuCgroupMountDir = TestCGroupsHandlerImpl.createMockCgroupMount(
cgroupDir, "cpu");
// create mock mtab
File mockMtab = TestCGroupsHandlerImpl.createMockMTab(cgroupDir);
File mockMtab =
TestCGroupsHandlerImpl.createPremountedCgroups(cgroupDir, false);
// create mock cgroup
File cpuCgroupMountDir = createMockCgroupMount(
cgroupDir, "cpu");
// setup our handler and call init()
handler.setMtabFile(mockMtab.getAbsolutePath());
@ -265,12 +281,13 @@ public class TestCgroupsLCEResourcesHandler {
handler.setConf(conf);
handler.initConfig();
// create mock cgroup
File cpuCgroupMountDir = TestCGroupsHandlerImpl.createMockCgroupMount(
cgroupDir, "cpu");
// create mock mtab
File mockMtab = TestCGroupsHandlerImpl.createMockMTab(cgroupDir);
File mockMtab =
TestCGroupsHandlerImpl.createPremountedCgroups(cgroupDir, false);
// create mock cgroup
File cpuCgroupMountDir = createMockCgroupMount(
cgroupDir, "cpu");
// setup our handler and call init()
handler.setMtabFile(mockMtab.getAbsolutePath());
@ -352,17 +369,17 @@ public class TestCgroupsLCEResourcesHandler {
File memory = new File(cgroupDir, "memory");
try {
CgroupsLCEResourcesHandler handler = new CgroupsLCEResourcesHandler();
Map<String, List<String>> cgroups = new LinkedHashMap<>();
Map<String, Set<String>> cgroups = new LinkedHashMap<>();
Assert.assertTrue("temp dir should be created", cpu.mkdirs());
Assert.assertTrue("temp dir should be created", memory.mkdirs());
Assert.assertFalse("temp dir should not be created", cpuNoExist.exists());
cgroups.put(
memory.getAbsolutePath(), Collections.singletonList("memory"));
memory.getAbsolutePath(), Collections.singleton("memory"));
cgroups.put(
cpuNoExist.getAbsolutePath(), Collections.singletonList("cpu"));
cgroups.put(cpu.getAbsolutePath(), Collections.singletonList("cpu"));
cpuNoExist.getAbsolutePath(), Collections.singleton("cpu"));
cgroups.put(cpu.getAbsolutePath(), Collections.singleton("cpu"));
String selectedCPU = handler.findControllerInMtab("cpu", cgroups);
Assert.assertEquals("Wrong CPU mount point selected",
cpu.getAbsolutePath(), selectedCPU);