org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.AllocationFileLoaderService.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.AllocationFileLoaderService.java

Source

/**
* 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.resourcemanager.scheduler.fair;

import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.classification.InterfaceAudience.Public;
import org.apache.hadoop.classification.InterfaceStability.Unstable;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.UnsupportedFileSystemException;
import org.apache.hadoop.security.authorize.AccessControlList;
import org.apache.hadoop.service.AbstractService;
import org.apache.hadoop.yarn.api.records.QueueACL;
import org.apache.hadoop.yarn.security.AccessType;
import org.apache.hadoop.yarn.security.Permission;
import org.apache.hadoop.yarn.security.PrivilegedEntity;
import org.apache.hadoop.yarn.security.PrivilegedEntity.EntityType;
import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerUtils;
import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.allocation.AllocationFileParser;
import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.allocation.AllocationFileQueueParser;
import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.allocation.QueueProperties;
import org.apache.hadoop.yarn.util.Clock;
import org.apache.hadoop.yarn.util.SystemClock;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.allocation.AllocationFileQueueParser.EVERYBODY_ACL;
import static org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.allocation.AllocationFileQueueParser.ROOT;

@Public
@Unstable
public class AllocationFileLoaderService extends AbstractService {

    public static final Log LOG = LogFactory.getLog(AllocationFileLoaderService.class.getName());

    /** Time to wait between checks of the allocation file */
    public static final long ALLOC_RELOAD_INTERVAL_MS = 10 * 1000;

    /**
     * Time to wait after the allocation has been modified before reloading it
     * (this is done to prevent loading a file that hasn't been fully written).
     */
    public static final long ALLOC_RELOAD_WAIT_MS = 5 * 1000;

    public static final long THREAD_JOIN_TIMEOUT_MS = 1000;

    //Permitted allocation file filesystems (case insensitive)
    private static final String SUPPORTED_FS_REGEX = "(?i)(hdfs)|(file)|(s3a)|(viewfs)";

    private final Clock clock;

    // Last time we successfully reloaded queues
    private volatile long lastSuccessfulReload;
    private volatile boolean lastReloadAttemptFailed = false;

    // Path to XML file containing allocations.
    private Path allocFile;
    private FileSystem fs;

    private Listener reloadListener;

    @VisibleForTesting
    long reloadIntervalMs = ALLOC_RELOAD_INTERVAL_MS;

    private Thread reloadThread;
    private volatile boolean running = true;

    public AllocationFileLoaderService() {
        this(SystemClock.getInstance());
    }

    private List<Permission> defaultPermissions;

    public AllocationFileLoaderService(Clock clock) {
        super(AllocationFileLoaderService.class.getName());
        this.clock = clock;
    }

    @Override
    public void serviceInit(Configuration conf) throws Exception {
        this.allocFile = getAllocationFile(conf);
        if (this.allocFile != null) {
            this.fs = allocFile.getFileSystem(conf);
            reloadThread = new Thread(() -> {
                while (running) {
                    try {
                        synchronized (this) {
                            reloadListener.onCheck();
                        }
                        long time = clock.getTime();
                        long lastModified = fs.getFileStatus(allocFile).getModificationTime();
                        if (lastModified > lastSuccessfulReload && time > lastModified + ALLOC_RELOAD_WAIT_MS) {
                            try {
                                reloadAllocations();
                            } catch (Exception ex) {
                                if (!lastReloadAttemptFailed) {
                                    LOG.error("Failed to reload fair scheduler config file - "
                                            + "will use existing allocations.", ex);
                                }
                                lastReloadAttemptFailed = true;
                            }
                        } else if (lastModified == 0l) {
                            if (!lastReloadAttemptFailed) {
                                LOG.warn("Failed to reload fair scheduler config file because"
                                        + " last modified returned 0. File exists: " + fs.exists(allocFile));
                            }
                            lastReloadAttemptFailed = true;
                        }
                    } catch (IOException e) {
                        LOG.error("Exception while loading allocation file: " + e);
                    }
                    try {
                        Thread.sleep(reloadIntervalMs);
                    } catch (InterruptedException ex) {
                        LOG.info("Interrupted while waiting to reload alloc configuration");
                    }
                }
            });
            reloadThread.setName("AllocationFileReloader");
            reloadThread.setDaemon(true);
        }
        super.serviceInit(conf);
    }

    @Override
    public void serviceStart() throws Exception {
        if (reloadThread != null) {
            reloadThread.start();
        }
        super.serviceStart();
    }

    @Override
    public void serviceStop() throws Exception {
        running = false;
        if (reloadThread != null) {
            reloadThread.interrupt();
            try {
                reloadThread.join(THREAD_JOIN_TIMEOUT_MS);
            } catch (InterruptedException e) {
                LOG.warn("reloadThread fails to join.");
            }
        }
        super.serviceStop();
    }

    /**
     * Path to XML file containing allocations. If the
     * path is relative, it is searched for in the
     * classpath, but loaded like a regular File.
     */
    @VisibleForTesting
    Path getAllocationFile(Configuration conf) throws UnsupportedFileSystemException {
        String allocFilePath = conf.get(FairSchedulerConfiguration.ALLOCATION_FILE,
                FairSchedulerConfiguration.DEFAULT_ALLOCATION_FILE);
        Path allocPath = new Path(allocFilePath);
        String allocPathScheme = allocPath.toUri().getScheme();
        if (allocPathScheme != null && !allocPathScheme.matches(SUPPORTED_FS_REGEX)) {
            throw new UnsupportedFileSystemException(
                    "Allocation file " + allocFilePath + " uses an unsupported filesystem");
        } else if (!allocPath.isAbsolute()) {
            URL url = Thread.currentThread().getContextClassLoader().getResource(allocFilePath);
            if (url == null) {
                LOG.warn(allocFilePath + " not found on the classpath.");
                allocPath = null;
            } else if (!url.getProtocol().equalsIgnoreCase("file")) {
                throw new RuntimeException(
                        "Allocation file " + url + " found on the classpath is not on the local filesystem.");
            } else {
                allocPath = new Path(url.getProtocol(), null, url.getPath());
            }
        } else if (allocPath.isAbsoluteAndSchemeAuthorityNull()) {
            allocPath = new Path("file", null, allocFilePath);
        }
        return allocPath;
    }

    public synchronized void setReloadListener(Listener reloadListener) {
        this.reloadListener = reloadListener;
    }

    /**
     * Updates the allocation list from the allocation config file. This file is
     * expected to be in the XML format specified in the design doc.
     *
     * @throws IOException if the config file cannot be read.
     * @throws AllocationConfigurationException if allocations are invalid.
     * @throws ParserConfigurationException if XML parser is misconfigured.
     * @throws SAXException if config file is malformed.
     */
    public synchronized void reloadAllocations()
            throws IOException, ParserConfigurationException, SAXException, AllocationConfigurationException {
        if (allocFile == null) {
            reloadListener.onReload(null);
            return;
        }
        LOG.info("Loading allocation file " + allocFile);

        // Read and parse the allocations file.
        DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
        docBuilderFactory.setIgnoringComments(true);
        DocumentBuilder builder = docBuilderFactory.newDocumentBuilder();
        Document doc = builder.parse(fs.open(allocFile));
        Element root = doc.getDocumentElement();
        if (!"allocations".equals(root.getTagName())) {
            throw new AllocationConfigurationException(
                    "Bad fair scheduler config " + "file: top-level element not <allocations>");
        }
        NodeList elements = root.getChildNodes();

        AllocationFileParser allocationFileParser = new AllocationFileParser(elements);
        allocationFileParser.parse();

        AllocationFileQueueParser queueParser = new AllocationFileQueueParser(
                allocationFileParser.getQueueElements());
        QueueProperties queueProperties = queueParser.parse();

        // Load placement policy and pass it configured queues
        Configuration conf = getConfig();
        QueuePlacementPolicy newPlacementPolicy = getQueuePlacementPolicy(allocationFileParser, queueProperties,
                conf);
        setupRootQueueProperties(allocationFileParser, queueProperties);

        ReservationQueueConfiguration globalReservationQueueConfig = createReservationQueueConfig(
                allocationFileParser);

        AllocationConfiguration info = new AllocationConfiguration(queueProperties, allocationFileParser,
                newPlacementPolicy, globalReservationQueueConfig);

        lastSuccessfulReload = clock.getTime();
        lastReloadAttemptFailed = false;

        reloadListener.onReload(info);
    }

    private QueuePlacementPolicy getQueuePlacementPolicy(AllocationFileParser allocationFileParser,
            QueueProperties queueProperties, Configuration conf) throws AllocationConfigurationException {
        if (allocationFileParser.getQueuePlacementPolicy().isPresent()) {
            return QueuePlacementPolicy.fromXml(allocationFileParser.getQueuePlacementPolicy().get(),
                    queueProperties.getConfiguredQueues(), conf);
        } else {
            return QueuePlacementPolicy.fromConfiguration(conf, queueProperties.getConfiguredQueues());
        }
    }

    private void setupRootQueueProperties(AllocationFileParser allocationFileParser,
            QueueProperties queueProperties) {
        // Set the min/fair share preemption timeout for the root queue
        if (!queueProperties.getMinSharePreemptionTimeouts().containsKey(QueueManager.ROOT_QUEUE)) {
            queueProperties.getMinSharePreemptionTimeouts().put(QueueManager.ROOT_QUEUE,
                    allocationFileParser.getDefaultMinSharePreemptionTimeout());
        }
        if (!queueProperties.getFairSharePreemptionTimeouts().containsKey(QueueManager.ROOT_QUEUE)) {
            queueProperties.getFairSharePreemptionTimeouts().put(QueueManager.ROOT_QUEUE,
                    allocationFileParser.getDefaultFairSharePreemptionTimeout());
        }

        // Set the fair share preemption threshold for the root queue
        if (!queueProperties.getFairSharePreemptionThresholds().containsKey(QueueManager.ROOT_QUEUE)) {
            queueProperties.getFairSharePreemptionThresholds().put(QueueManager.ROOT_QUEUE,
                    allocationFileParser.getDefaultFairSharePreemptionThreshold());
        }
    }

    private ReservationQueueConfiguration createReservationQueueConfig(AllocationFileParser allocationFileParser) {
        ReservationQueueConfiguration globalReservationQueueConfig = new ReservationQueueConfiguration();
        if (allocationFileParser.getReservationPlanner().isPresent()) {
            globalReservationQueueConfig.setPlanner(allocationFileParser.getReservationPlanner().get());
        }
        if (allocationFileParser.getReservationAdmissionPolicy().isPresent()) {
            globalReservationQueueConfig
                    .setReservationAdmissionPolicy(allocationFileParser.getReservationAdmissionPolicy().get());
        }
        if (allocationFileParser.getReservationAgent().isPresent()) {
            globalReservationQueueConfig.setReservationAgent(allocationFileParser.getReservationAgent().get());
        }
        return globalReservationQueueConfig;
    }

    /**
     * Returns the list of default permissions.
     * The default permission for the root queue is everybody ("*")
     * and the default permission for all other queues is nobody ("").
     * The default permission list would be loaded before the permissions
     * from allocation file.
     * @return default permission list
     */
    protected List<Permission> getDefaultPermissions() {
        if (defaultPermissions == null) {
            defaultPermissions = new ArrayList<>();
            Map<AccessType, AccessControlList> acls = new HashMap<>();
            for (QueueACL acl : QueueACL.values()) {
                acls.put(SchedulerUtils.toAccessType(acl), EVERYBODY_ACL);
            }
            defaultPermissions.add(new Permission(new PrivilegedEntity(EntityType.QUEUE, ROOT), acls));
        }
        return defaultPermissions;
    }

    public interface Listener {
        void onReload(AllocationConfiguration info) throws IOException;

        default void onCheck() {
        }
    }
}