|
@@ -0,0 +1,436 @@
|
|
|
+/*
|
|
|
+ * *
|
|
|
+ * 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.yarn.server.nodemanager.containermanager.linux.resources;
|
|
|
+
|
|
|
+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.io.IOUtils;
|
|
|
+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;
|
|
|
+import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.privileged.PrivilegedOperationExecutor;
|
|
|
+import org.apache.hadoop.yarn.util.Clock;
|
|
|
+import org.apache.hadoop.yarn.util.SystemClock;
|
|
|
+
|
|
|
+import java.io.*;
|
|
|
+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.Map;
|
|
|
+import java.util.concurrent.locks.ReadWriteLock;
|
|
|
+import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
|
+import java.util.regex.Matcher;
|
|
|
+import java.util.regex.Pattern;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Support for interacting with various CGroup subsystems. Thread-safe.
|
|
|
+ */
|
|
|
+
|
|
|
+@InterfaceAudience.Private
|
|
|
+@InterfaceStability.Unstable
|
|
|
+class CGroupsHandlerImpl implements CGroupsHandler {
|
|
|
+
|
|
|
+ private static final Log LOG = LogFactory.getLog(CGroupsHandlerImpl.class);
|
|
|
+ private static final String MTAB_FILE = "/proc/mounts";
|
|
|
+ private static final String CGROUPS_FSTYPE = "cgroup";
|
|
|
+
|
|
|
+ private final String cGroupPrefix;
|
|
|
+ private final boolean enableCGroupMount;
|
|
|
+ private final String cGroupMountPath;
|
|
|
+ private final long deleteCGroupTimeout;
|
|
|
+ private final long deleteCGroupDelay;
|
|
|
+ private final Map<CGroupController, String> controllerPaths;
|
|
|
+ private final ReadWriteLock rwLock;
|
|
|
+ private final PrivilegedOperationExecutor privilegedOperationExecutor;
|
|
|
+ private final Clock clock;
|
|
|
+
|
|
|
+ public CGroupsHandlerImpl(Configuration conf, PrivilegedOperationExecutor
|
|
|
+ privilegedOperationExecutor) throws ResourceHandlerException {
|
|
|
+ this.cGroupPrefix = conf.get(YarnConfiguration.
|
|
|
+ NM_LINUX_CONTAINER_CGROUPS_HIERARCHY, "/hadoop-yarn")
|
|
|
+ .replaceAll("^/", "").replaceAll("$/", "");
|
|
|
+ this.enableCGroupMount = conf.getBoolean(YarnConfiguration.
|
|
|
+ NM_LINUX_CONTAINER_CGROUPS_MOUNT, false);
|
|
|
+ this.cGroupMountPath = conf.get(YarnConfiguration.
|
|
|
+ NM_LINUX_CONTAINER_CGROUPS_MOUNT_PATH, null);
|
|
|
+ this.deleteCGroupTimeout = conf.getLong(
|
|
|
+ YarnConfiguration.NM_LINUX_CONTAINER_CGROUPS_DELETE_TIMEOUT,
|
|
|
+ YarnConfiguration.DEFAULT_NM_LINUX_CONTAINER_CGROUPS_DELETE_TIMEOUT);
|
|
|
+ this.deleteCGroupDelay =
|
|
|
+ conf.getLong(YarnConfiguration.NM_LINUX_CONTAINER_CGROUPS_DELETE_DELAY,
|
|
|
+ YarnConfiguration.DEFAULT_NM_LINUX_CONTAINER_CGROUPS_DELETE_DELAY);
|
|
|
+ this.controllerPaths = new HashMap<>();
|
|
|
+ this.rwLock = new ReentrantReadWriteLock();
|
|
|
+ this.privilegedOperationExecutor = privilegedOperationExecutor;
|
|
|
+ this.clock = new SystemClock();
|
|
|
+
|
|
|
+ init();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void init() throws ResourceHandlerException {
|
|
|
+ initializeControllerPaths();
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getControllerPath(CGroupController controller) {
|
|
|
+ try {
|
|
|
+ rwLock.readLock().lock();
|
|
|
+ return controllerPaths.get(controller);
|
|
|
+ } finally {
|
|
|
+ rwLock.readLock().unlock();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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
|
|
|
+ initializeControllerPathsFromMtab();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void initializeControllerPathsFromMtab()
|
|
|
+ throws ResourceHandlerException {
|
|
|
+ try {
|
|
|
+ Map<String, List<String>> parsedMtab = parseMtab();
|
|
|
+
|
|
|
+ //we want to do a bulk update without the paths changing concurrently
|
|
|
+ rwLock.writeLock().lock();
|
|
|
+
|
|
|
+ for (CGroupController controller : CGroupController.values()) {
|
|
|
+ String name = controller.getName();
|
|
|
+ String controllerPath = findControllerInMtab(name, parsedMtab);
|
|
|
+
|
|
|
+ if (controllerPath != null) {
|
|
|
+ File f = new File(controllerPath + "/" + this.cGroupPrefix);
|
|
|
+
|
|
|
+ if (FileUtil.canWrite(f)) {
|
|
|
+ controllerPaths.put(controller, controllerPath);
|
|
|
+ } else {
|
|
|
+ String error =
|
|
|
+ new StringBuffer("Mount point Based on mtab file: ")
|
|
|
+ .append(MTAB_FILE).append(
|
|
|
+ ". Controller mount point not writable for: ")
|
|
|
+ .append(name).toString();
|
|
|
+
|
|
|
+ LOG.error(error);
|
|
|
+ throw new ResourceHandlerException(error);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+
|
|
|
+ LOG.warn("Controller not mounted but automount disabled: " + name);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ LOG.warn("Failed to initialize controller paths! Exception: " + e);
|
|
|
+ throw new ResourceHandlerException(
|
|
|
+ "Failed to initialize controller paths!");
|
|
|
+ } finally {
|
|
|
+ rwLock.writeLock().unlock();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* We are looking for entries of the form:
|
|
|
+ * none /cgroup/path/mem cgroup rw,memory 0 0
|
|
|
+ *
|
|
|
+ * Use a simple pattern that splits on the five spaces, and
|
|
|
+ * grabs the 2, 3, and 4th fields.
|
|
|
+ */
|
|
|
+
|
|
|
+ private static final Pattern MTAB_FILE_FORMAT = Pattern.compile(
|
|
|
+ "^[^\\s]+\\s([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s[^\\s]+\\s[^\\s]+$");
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Returns a map: path -> mount options
|
|
|
+ * 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>>();
|
|
|
+ BufferedReader in = null;
|
|
|
+
|
|
|
+ try {
|
|
|
+ FileInputStream fis = new FileInputStream(new File(getMtabFileName()));
|
|
|
+ in = new BufferedReader(new InputStreamReader(fis, "UTF-8"));
|
|
|
+
|
|
|
+ for (String str = in.readLine(); str != null;
|
|
|
+ str = in.readLine()) {
|
|
|
+ Matcher m = MTAB_FILE_FORMAT.matcher(str);
|
|
|
+ boolean mat = m.find();
|
|
|
+ if (mat) {
|
|
|
+ String path = m.group(1);
|
|
|
+ String type = m.group(2);
|
|
|
+ String options = m.group(3);
|
|
|
+
|
|
|
+ if (type.equals(CGROUPS_FSTYPE)) {
|
|
|
+ List<String> value = Arrays.asList(options.split(","));
|
|
|
+ ret.put(path, value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ throw new IOException("Error while reading " + getMtabFileName(), e);
|
|
|
+ } finally {
|
|
|
+ IOUtils.cleanup(LOG, in);
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String findControllerInMtab(String controller,
|
|
|
+ Map<String, List<String>> entries) {
|
|
|
+ for (Map.Entry<String, List<String>> e : entries.entrySet()) {
|
|
|
+ if (e.getValue().contains(controller))
|
|
|
+ return e.getKey();
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ String getMtabFileName() {
|
|
|
+ return MTAB_FILE;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void mountCGroupController(CGroupController controller)
|
|
|
+ throws ResourceHandlerException {
|
|
|
+ if (!enableCGroupMount) {
|
|
|
+ LOG.warn("CGroup mounting is disabled - ignoring mount request for: " +
|
|
|
+ controller.getName());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ String path = getControllerPath(controller);
|
|
|
+
|
|
|
+ if (path == null) {
|
|
|
+ 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);
|
|
|
+ PrivilegedOperation.OperationType opType = PrivilegedOperation
|
|
|
+ .OperationType.MOUNT_CGROUPS;
|
|
|
+ PrivilegedOperation op = new PrivilegedOperation(opType, (String) null);
|
|
|
+
|
|
|
+ op.appendArgs(hierarchy, cGroupKV.toString());
|
|
|
+ LOG.info("Mounting controller " + controller.getName() + " at " +
|
|
|
+ controllerPath);
|
|
|
+ privilegedOperationExecutor.executePrivilegedOperation(op, false);
|
|
|
+
|
|
|
+ //if privileged operation succeeds, update controller paths
|
|
|
+ controllerPaths.put(controller, controllerPath.toString());
|
|
|
+
|
|
|
+ return;
|
|
|
+ } catch (PrivilegedOperationException e) {
|
|
|
+ LOG.error("Failed to mount controller: " + controller.getName());
|
|
|
+ throw new ResourceHandlerException("Failed to mount controller: "
|
|
|
+ + controller.getName());
|
|
|
+ } finally {
|
|
|
+ rwLock.writeLock().unlock();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ LOG.info("CGroup controller already mounted at: " + path);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String getPathForCGroup(CGroupController controller, String cGroupId) {
|
|
|
+ return new StringBuffer(getControllerPath(controller))
|
|
|
+ .append('/').append(cGroupPrefix).append("/")
|
|
|
+ .append(cGroupId).toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String getPathForCGroupTasks(CGroupController controller,
|
|
|
+ String cGroupId) {
|
|
|
+ return new StringBuffer(getPathForCGroup(controller, cGroupId))
|
|
|
+ .append('/').append(CGROUP_FILE_TASKS).toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String getPathForCGroupParam(CGroupController controller,
|
|
|
+ String cGroupId, String param) {
|
|
|
+ return new StringBuffer(getPathForCGroup(controller, cGroupId))
|
|
|
+ .append('/').append(controller.getName()).append('.')
|
|
|
+ .append(param).toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String createCGroup(CGroupController controller, String cGroupId)
|
|
|
+ throws ResourceHandlerException {
|
|
|
+ String path = getPathForCGroup(controller, cGroupId);
|
|
|
+
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
+ LOG.debug("createCgroup: " + path);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!new File(path).mkdir()) {
|
|
|
+ throw new ResourceHandlerException("Failed to create cgroup at " + path);
|
|
|
+ }
|
|
|
+
|
|
|
+ return path;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Utility routine to print first line from cgroup tasks file
|
|
|
+ */
|
|
|
+ private void logLineFromTasksFile(File cgf) {
|
|
|
+ String str;
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
+ try (BufferedReader inl =
|
|
|
+ new BufferedReader(new InputStreamReader(new FileInputStream(cgf
|
|
|
+ + "/tasks"), "UTF-8"))) {
|
|
|
+ if ((str = inl.readLine()) != null) {
|
|
|
+ LOG.debug("First line in cgroup tasks file: " + cgf + " " + str);
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ LOG.warn("Failed to read cgroup tasks file. ", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * If tasks file is empty, delete the cgroup.
|
|
|
+ *
|
|
|
+ * @param cgf object referring to the cgroup to be deleted
|
|
|
+ * @return Boolean indicating whether cgroup was deleted
|
|
|
+ */
|
|
|
+ boolean checkAndDeleteCgroup(File cgf) throws InterruptedException {
|
|
|
+ boolean deleted = false;
|
|
|
+ // FileInputStream in = null;
|
|
|
+ try (FileInputStream in = new FileInputStream(cgf + "/tasks")) {
|
|
|
+ if (in.read() == -1) {
|
|
|
+ /*
|
|
|
+ * "tasks" file is empty, sleep a bit more and then try to delete the
|
|
|
+ * cgroup. Some versions of linux will occasionally panic due to a race
|
|
|
+ * condition in this area, hence the paranoia.
|
|
|
+ */
|
|
|
+ Thread.sleep(deleteCGroupDelay);
|
|
|
+ deleted = cgf.delete();
|
|
|
+ if (!deleted) {
|
|
|
+ LOG.warn("Failed attempt to delete cgroup: " + cgf);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ logLineFromTasksFile(cgf);
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ LOG.warn("Failed to read cgroup tasks file. ", e);
|
|
|
+ }
|
|
|
+ return deleted;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void deleteCGroup(CGroupController controller, String cGroupId)
|
|
|
+ throws ResourceHandlerException {
|
|
|
+ boolean deleted = false;
|
|
|
+ String cGroupPath = getPathForCGroup(controller, cGroupId);
|
|
|
+
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
+ LOG.debug("deleteCGroup: " + cGroupPath);
|
|
|
+ }
|
|
|
+
|
|
|
+ long start = clock.getTime();
|
|
|
+
|
|
|
+ do {
|
|
|
+ try {
|
|
|
+ deleted = checkAndDeleteCgroup(new File(cGroupPath));
|
|
|
+ if (!deleted) {
|
|
|
+ Thread.sleep(deleteCGroupDelay);
|
|
|
+ }
|
|
|
+ } catch (InterruptedException ex) {
|
|
|
+ // NOP
|
|
|
+ }
|
|
|
+ } while (!deleted && (clock.getTime() - start) < deleteCGroupTimeout);
|
|
|
+
|
|
|
+ if (!deleted) {
|
|
|
+ LOG.warn("Unable to delete " + cGroupPath +
|
|
|
+ ", tried to delete for " + deleteCGroupTimeout + "ms");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void updateCGroupParam(CGroupController controller, String cGroupId,
|
|
|
+ String param, String value) throws ResourceHandlerException {
|
|
|
+ String cGroupParamPath = getPathForCGroupParam(controller, cGroupId, param);
|
|
|
+ PrintWriter pw = null;
|
|
|
+
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
+ LOG.debug(
|
|
|
+ "updateCGroupParam for path: " + cGroupParamPath + " with value " +
|
|
|
+ value);
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ File file = new File(cGroupParamPath);
|
|
|
+ Writer w = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
|
|
|
+ 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);
|
|
|
+ } 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());
|
|
|
+ }
|
|
|
+ if (pw.checkError()) {
|
|
|
+ throw new ResourceHandlerException("Error while closing cgroup file" +
|
|
|
+ " " + cGroupParamPath);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String getCGroupParam(CGroupController controller, String cGroupId,
|
|
|
+ String param) throws ResourceHandlerException {
|
|
|
+ String cGroupParamPath = getPathForCGroupParam(controller, cGroupId, param);
|
|
|
+
|
|
|
+ try {
|
|
|
+ byte[] contents = Files.readAllBytes(Paths.get(cGroupParamPath));
|
|
|
+ return new String(contents, "UTF-8").trim();
|
|
|
+ } catch (IOException e) {
|
|
|
+ throw new ResourceHandlerException(
|
|
|
+ "Unable to read from " + cGroupParamPath);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|