org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.TestDockerContainerRuntime.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.TestDockerContainerRuntime.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.nodemanager.containermanager.linux.runtime;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.yarn.api.records.ContainerId;
import org.apache.hadoop.yarn.api.records.ContainerLaunchContext;
import org.apache.hadoop.yarn.conf.YarnConfiguration;
import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.Container;
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.server.nodemanager.containermanager.runtime.ContainerExecutionException;
import org.apache.hadoop.yarn.server.nodemanager.containermanager.runtime.ContainerRuntimeConstants;
import org.apache.hadoop.yarn.server.nodemanager.containermanager.runtime.ContainerRuntimeContext;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.LinuxContainerRuntimeConstants.*;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;

public class TestDockerContainerRuntime {
    private static final Log LOG = LogFactory.getLog(TestDockerContainerRuntime.class);
    private Configuration conf;
    PrivilegedOperationExecutor mockExecutor;
    String containerId;
    Container container;
    ContainerId cId;
    ContainerLaunchContext context;
    HashMap<String, String> env;
    String image;
    String runAsUser;
    String user;
    String appId;
    String containerIdStr = containerId;
    Path containerWorkDir;
    Path nmPrivateContainerScriptPath;
    Path nmPrivateTokensPath;
    Path pidFilePath;
    List<String> localDirs;
    List<String> logDirs;
    String resourcesOptions;
    ContainerRuntimeContext.Builder builder;
    String submittingUser = "anakin";
    String whitelistedUser = "yoda";

    @Before
    public void setup() {
        String tmpPath = new StringBuffer(System.getProperty("test.build.data")).append('/')
                .append("hadoop.tmp.dir").toString();

        conf = new Configuration();
        conf.set("hadoop.tmp.dir", tmpPath);

        mockExecutor = Mockito.mock(PrivilegedOperationExecutor.class);
        containerId = "container_id";
        container = mock(Container.class);
        cId = mock(ContainerId.class);
        context = mock(ContainerLaunchContext.class);
        env = new HashMap<String, String>();
        image = "busybox:latest";

        env.put(DockerLinuxContainerRuntime.ENV_DOCKER_CONTAINER_IMAGE, image);
        when(container.getContainerId()).thenReturn(cId);
        when(cId.toString()).thenReturn(containerId);
        when(container.getLaunchContext()).thenReturn(context);
        when(context.getEnvironment()).thenReturn(env);
        when(container.getUser()).thenReturn(submittingUser);

        runAsUser = "run_as_user";
        user = "user";
        appId = "app_id";
        containerIdStr = containerId;
        containerWorkDir = new Path("/test_container_work_dir");
        nmPrivateContainerScriptPath = new Path("/test_script_path");
        nmPrivateTokensPath = new Path("/test_private_tokens_path");
        pidFilePath = new Path("/test_pid_file_path");
        localDirs = new ArrayList<>();
        logDirs = new ArrayList<>();
        resourcesOptions = "cgroups:none";

        localDirs.add("/test_local_dir");
        logDirs.add("/test_log_dir");

        builder = new ContainerRuntimeContext.Builder(container);

        builder.setExecutionAttribute(RUN_AS_USER, runAsUser).setExecutionAttribute(USER, user)
                .setExecutionAttribute(APPID, appId).setExecutionAttribute(CONTAINER_ID_STR, containerIdStr)
                .setExecutionAttribute(CONTAINER_WORK_DIR, containerWorkDir)
                .setExecutionAttribute(NM_PRIVATE_CONTAINER_SCRIPT_PATH, nmPrivateContainerScriptPath)
                .setExecutionAttribute(NM_PRIVATE_TOKENS_PATH, nmPrivateTokensPath)
                .setExecutionAttribute(PID_FILE_PATH, pidFilePath).setExecutionAttribute(LOCAL_DIRS, localDirs)
                .setExecutionAttribute(LOG_DIRS, logDirs)
                .setExecutionAttribute(RESOURCES_OPTIONS, resourcesOptions);
    }

    @Test
    public void testSelectDockerContainerType() {
        Map<String, String> envDockerType = new HashMap<>();
        Map<String, String> envOtherType = new HashMap<>();

        envDockerType.put(ContainerRuntimeConstants.ENV_CONTAINER_TYPE, "docker");
        envOtherType.put(ContainerRuntimeConstants.ENV_CONTAINER_TYPE, "other");

        Assert.assertEquals(false, DockerLinuxContainerRuntime.isDockerContainerRequested(null));
        Assert.assertEquals(true, DockerLinuxContainerRuntime.isDockerContainerRequested(envDockerType));
        Assert.assertEquals(false, DockerLinuxContainerRuntime.isDockerContainerRequested(envOtherType));
    }

    @SuppressWarnings("unchecked")
    private PrivilegedOperation capturePrivilegedOperationAndVerifyArgs() throws PrivilegedOperationException {
        ArgumentCaptor<PrivilegedOperation> opCaptor = ArgumentCaptor.forClass(PrivilegedOperation.class);

        //single invocation expected
        //due to type erasure + mocking, this verification requires a suppress
        // warning annotation on the entire method
        verify(mockExecutor, times(1)).executePrivilegedOperation(anyList(), opCaptor.capture(), any(File.class),
                any(Map.class), eq(false), eq(false));

        PrivilegedOperation op = opCaptor.getValue();

        Assert.assertEquals(PrivilegedOperation.OperationType.LAUNCH_DOCKER_CONTAINER, op.getOperationType());

        List<String> args = op.getArguments();

        //This invocation of container-executor should use 13 arguments in a
        // specific order (sigh.)
        Assert.assertEquals(13, args.size());

        //verify arguments
        Assert.assertEquals(runAsUser, args.get(0));
        Assert.assertEquals(user, args.get(1));
        Assert.assertEquals(
                Integer.toString(PrivilegedOperation.RunAsUserCommand.LAUNCH_DOCKER_CONTAINER.getValue()),
                args.get(2));
        Assert.assertEquals(appId, args.get(3));
        Assert.assertEquals(containerId, args.get(4));
        Assert.assertEquals(containerWorkDir.toString(), args.get(5));
        Assert.assertEquals(nmPrivateContainerScriptPath.toUri().toString(), args.get(6));
        Assert.assertEquals(nmPrivateTokensPath.toUri().getPath(), args.get(7));
        Assert.assertEquals(pidFilePath.toString(), args.get(8));
        Assert.assertEquals(localDirs.get(0), args.get(9));
        Assert.assertEquals(logDirs.get(0), args.get(10));
        Assert.assertEquals(resourcesOptions, args.get(12));

        return op;
    }

    @Test
    public void testDockerContainerLaunch()
            throws ContainerExecutionException, PrivilegedOperationException, IOException {
        DockerLinuxContainerRuntime runtime = new DockerLinuxContainerRuntime(mockExecutor);
        runtime.initialize(conf);

        String[] testCapabilities = { "NET_BIND_SERVICE", "SYS_CHROOT" };

        conf.setStrings(YarnConfiguration.NM_DOCKER_CONTAINER_CAPABILITIES, testCapabilities);
        runtime.launchContainer(builder.build());

        PrivilegedOperation op = capturePrivilegedOperationAndVerifyArgs();
        List<String> args = op.getArguments();
        String dockerCommandFile = args.get(11);

        /* Ordering of capabilities depends on HashSet ordering. */
        Set<String> capabilitySet = new HashSet<>(Arrays.asList(testCapabilities));
        StringBuilder expectedCapabilitiesString = new StringBuilder("--cap-drop=ALL ");

        for (String capability : capabilitySet) {
            expectedCapabilitiesString.append("--cap-add=").append(capability).append(" ");
        }

        //This is the expected docker invocation for this case
        StringBuffer expectedCommandTemplate = new StringBuffer("run --name=%1$s ").append("--user=%2$s -d ")
                .append("--workdir=%3$s ").append("--net=host ").append(expectedCapabilitiesString)
                .append("-v /etc/passwd:/etc/password:ro ").append("-v %4$s:%4$s ").append("-v %5$s:%5$s ")
                .append("-v %6$s:%6$s ").append("%7$s ").append("bash %8$s/launch_container.sh");

        String expectedCommand = String.format(expectedCommandTemplate.toString(), containerId, runAsUser,
                containerWorkDir, localDirs.get(0), containerWorkDir, logDirs.get(0), image, containerWorkDir);

        List<String> dockerCommands = Files.readAllLines(Paths.get(dockerCommandFile), Charset.forName("UTF-8"));

        Assert.assertEquals(1, dockerCommands.size());
        Assert.assertEquals(expectedCommand, dockerCommands.get(0));
    }

    @Test
    public void testLaunchPrivilegedContainersInvalidEnvVar()
            throws ContainerExecutionException, PrivilegedOperationException, IOException {
        DockerLinuxContainerRuntime runtime = new DockerLinuxContainerRuntime(mockExecutor);
        runtime.initialize(conf);

        env.put("YARN_CONTAINER_RUNTIME_DOCKER_RUN_PRIVILEGED_CONTAINER", "invalid-value");
        runtime.launchContainer(builder.build());

        PrivilegedOperation op = capturePrivilegedOperationAndVerifyArgs();
        List<String> args = op.getArguments();
        String dockerCommandFile = args.get(11);

        List<String> dockerCommands = Files.readAllLines(Paths.get(dockerCommandFile), Charset.forName("UTF-8"));

        Assert.assertEquals(1, dockerCommands.size());

        String command = dockerCommands.get(0);

        //ensure --privileged isn't in the invocation
        Assert.assertTrue("Unexpected --privileged in docker run args : " + command,
                !command.contains("--privileged"));
    }

    @Test
    public void testLaunchPrivilegedContainersWithDisabledSetting()
            throws ContainerExecutionException, PrivilegedOperationException, IOException {
        DockerLinuxContainerRuntime runtime = new DockerLinuxContainerRuntime(mockExecutor);
        runtime.initialize(conf);

        env.put("YARN_CONTAINER_RUNTIME_DOCKER_RUN_PRIVILEGED_CONTAINER", "true");

        try {
            runtime.launchContainer(builder.build());
            Assert.fail("Expected a privileged launch container failure.");
        } catch (ContainerExecutionException e) {
            LOG.info("Caught expected exception : " + e);
        }
    }

    @Test
    public void testLaunchPrivilegedContainersWithEnabledSettingAndDefaultACL()
            throws ContainerExecutionException, PrivilegedOperationException, IOException {
        //Enable privileged containers.
        conf.setBoolean(YarnConfiguration.NM_DOCKER_ALLOW_PRIVILEGED_CONTAINERS, true);

        DockerLinuxContainerRuntime runtime = new DockerLinuxContainerRuntime(mockExecutor);
        runtime.initialize(conf);

        env.put("YARN_CONTAINER_RUNTIME_DOCKER_RUN_PRIVILEGED_CONTAINER", "true");
        //By default
        // yarn.nodemanager.runtime.linux.docker.privileged-containers.acl
        // is empty. So we expect this launch to fail.

        try {
            runtime.launchContainer(builder.build());
            Assert.fail("Expected a privileged launch container failure.");
        } catch (ContainerExecutionException e) {
            LOG.info("Caught expected exception : " + e);
        }
    }

    @Test
    public void testLaunchPrivilegedContainersEnabledAndUserNotInWhitelist()
            throws ContainerExecutionException, PrivilegedOperationException, IOException {
        //Enable privileged containers.
        conf.setBoolean(YarnConfiguration.NM_DOCKER_ALLOW_PRIVILEGED_CONTAINERS, true);
        //set whitelist of users.
        conf.set(YarnConfiguration.NM_DOCKER_PRIVILEGED_CONTAINERS_ACL, whitelistedUser);

        DockerLinuxContainerRuntime runtime = new DockerLinuxContainerRuntime(mockExecutor);
        runtime.initialize(conf);

        env.put("YARN_CONTAINER_RUNTIME_DOCKER_RUN_PRIVILEGED_CONTAINER", "true");

        try {
            runtime.launchContainer(builder.build());
            Assert.fail("Expected a privileged launch container failure.");
        } catch (ContainerExecutionException e) {
            LOG.info("Caught expected exception : " + e);
        }
    }

    @Test
    public void testLaunchPrivilegedContainersEnabledAndUserInWhitelist()
            throws ContainerExecutionException, PrivilegedOperationException, IOException {
        //Enable privileged containers.
        conf.setBoolean(YarnConfiguration.NM_DOCKER_ALLOW_PRIVILEGED_CONTAINERS, true);
        //Add submittingUser to whitelist.
        conf.set(YarnConfiguration.NM_DOCKER_PRIVILEGED_CONTAINERS_ACL, submittingUser);

        DockerLinuxContainerRuntime runtime = new DockerLinuxContainerRuntime(mockExecutor);
        runtime.initialize(conf);

        env.put("YARN_CONTAINER_RUNTIME_DOCKER_RUN_PRIVILEGED_CONTAINER", "true");

        runtime.launchContainer(builder.build());
        PrivilegedOperation op = capturePrivilegedOperationAndVerifyArgs();
        List<String> args = op.getArguments();
        String dockerCommandFile = args.get(11);

        List<String> dockerCommands = Files.readAllLines(Paths.get(dockerCommandFile), Charset.forName("UTF-8"));

        Assert.assertEquals(1, dockerCommands.size());

        String command = dockerCommands.get(0);

        //submitting user is whitelisted. ensure --privileged is in the invocation
        Assert.assertTrue("Did not find expected '--privileged' in docker run args " + ": " + command,
                command.contains("--privileged"));
    }

    @Test
    public void testDockerImageNamePattern() throws Exception {
        String[] validNames = { "ubuntu", "fedora/httpd:version1.0", "fedora/httpd:version1.0.test",
                "fedora/httpd:version1.0.TEST", "myregistryhost:5000/ubuntu",
                "myregistryhost:5000/fedora/httpd:version1.0", "myregistryhost:5000/fedora/httpd:version1.0.test",
                "myregistryhost:5000/fedora/httpd:version1.0.TEST" };

        String[] invalidNames = { "Ubuntu", "ubuntu || fedora", "ubuntu#", "myregistryhost:50AB0/ubuntu",
                "myregistry#host:50AB0/ubuntu", ":8080/ubuntu" };

        for (String name : validNames) {
            DockerLinuxContainerRuntime.validateImageName(name);
        }

        for (String name : invalidNames) {
            try {
                DockerLinuxContainerRuntime.validateImageName(name);
                Assert.fail(name + " is an invalid name and should fail the regex");
            } catch (ContainerExecutionException ce) {
                continue;
            }
        }
    }
}