org.apache.flink.runtime.webmonitor.WebRuntimeMonitorITCase.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.flink.runtime.webmonitor.WebRuntimeMonitorITCase.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.flink.runtime.webmonitor;

import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.apache.curator.test.TestingServer;
import org.apache.flink.configuration.ConfigConstants;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.runtime.akka.AkkaUtils;
import org.apache.flink.runtime.jobmanager.JobManager;
import org.apache.flink.runtime.jobmanager.MemoryArchivist;
import org.apache.flink.runtime.leaderelection.TestingListener;
import org.apache.flink.runtime.leaderretrieval.LeaderRetrievalService;
import org.apache.flink.runtime.testingUtils.TestingCluster;
import org.apache.flink.runtime.testutils.ZooKeeperTestUtils;
import org.apache.flink.runtime.util.ZooKeeperUtils;
import org.apache.flink.runtime.webmonitor.files.MimeTypes;
import org.apache.flink.runtime.webmonitor.testutils.HttpTestClient;
import org.apache.flink.util.TestLogger;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.powermock.reflect.Whitebox;
import scala.Some;
import scala.Tuple2;
import scala.concurrent.duration.Deadline;
import scala.concurrent.duration.FiniteDuration;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;

public class WebRuntimeMonitorITCase extends TestLogger {

    @Rule
    public TemporaryFolder temporaryFolder = new TemporaryFolder();

    private final static FiniteDuration TestTimeout = new FiniteDuration(2, TimeUnit.MINUTES);

    private final String MAIN_RESOURCES_PATH = getClass().getResource("/web").getPath();

    /**
     * Tests operation of the monitor in standalone operation.
     */
    @Test
    public void testStandaloneWebRuntimeMonitor() throws Exception {
        final Deadline deadline = TestTimeout.fromNow();

        TestingCluster flink = null;
        WebRuntimeMonitor webMonitor = null;

        try {
            // Flink w/o a web monitor
            flink = new TestingCluster(new Configuration());
            flink.start(true);

            ActorSystem jmActorSystem = flink.jobManagerActorSystems().get().head();
            ActorRef jmActor = flink.jobManagerActors().get().head();

            File logDir = temporaryFolder.newFolder("log");
            Path logFile = Files.createFile(new File(logDir, "jobmanager.log").toPath());
            Files.createFile(new File(logDir, "jobmanager.out").toPath());

            Configuration monitorConfig = new Configuration();
            monitorConfig.setInteger(ConfigConstants.JOB_MANAGER_WEB_PORT_KEY, 0);
            monitorConfig.setString(ConfigConstants.JOB_MANAGER_WEB_LOG_PATH_KEY, logFile.toString());

            // Needs to match the leader address from the leader retrieval service
            String jobManagerAddress = AkkaUtils.getAkkaURL(jmActorSystem, jmActor);

            webMonitor = new WebRuntimeMonitor(monitorConfig, flink.createLeaderRetrievalService(), jmActorSystem);

            webMonitor.start(jobManagerAddress);

            try (HttpTestClient client = new HttpTestClient("localhost", webMonitor.getServerPort())) {
                String expected = new Scanner(new File(MAIN_RESOURCES_PATH + "/index.html")).useDelimiter("\\A")
                        .next();

                // Request the file from the web server
                client.sendGetRequest("index.html", deadline.timeLeft());

                HttpTestClient.SimpleHttpResponse response = client.getNextResponse(deadline.timeLeft());

                assertEquals(HttpResponseStatus.OK, response.getStatus());
                assertEquals(response.getType(), MimeTypes.getMimeTypeForExtension("html"));
                assertEquals(expected, response.getContent());

                // Simple overview request
                client.sendGetRequest("/overview", deadline.timeLeft());

                response = client.getNextResponse(deadline.timeLeft());
                assertEquals(HttpResponseStatus.OK, response.getStatus());
                assertEquals(response.getType(), MimeTypes.getMimeTypeForExtension("json"));
                assertTrue(response.getContent().contains("\"taskmanagers\":1"));
            }
        } finally {
            if (flink != null) {
                flink.shutdown();
            }

            if (webMonitor != null) {
                webMonitor.stop();
            }
        }
    }

    /**
     * Tests that the monitor associated with the following job manager redirects to the leader.
     */
    @Test
    public void testRedirectToLeader() throws Exception {
        final Deadline deadline = TestTimeout.fromNow();

        ActorSystem[] jobManagerSystem = new ActorSystem[2];
        WebRuntimeMonitor[] webMonitor = new WebRuntimeMonitor[2];
        List<LeaderRetrievalService> leaderRetrievalServices = new ArrayList<>();

        try (TestingServer zooKeeper = new TestingServer()) {
            final Configuration config = ZooKeeperTestUtils.createZooKeeperRecoveryModeConfig(
                    zooKeeper.getConnectString(), temporaryFolder.getRoot().getPath());

            File logDir = temporaryFolder.newFolder();
            Path logFile = Files.createFile(new File(logDir, "jobmanager.log").toPath());
            Files.createFile(new File(logDir, "jobmanager.out").toPath());

            config.setInteger(ConfigConstants.JOB_MANAGER_WEB_PORT_KEY, 0);
            config.setString(ConfigConstants.JOB_MANAGER_WEB_LOG_PATH_KEY, logFile.toString());

            for (int i = 0; i < jobManagerSystem.length; i++) {
                jobManagerSystem[i] = AkkaUtils.createActorSystem(new Configuration(),
                        new Some<>(new Tuple2<String, Object>("localhost", 0)));
            }

            for (int i = 0; i < webMonitor.length; i++) {
                LeaderRetrievalService lrs = ZooKeeperUtils.createLeaderRetrievalService(config);
                leaderRetrievalServices.add(lrs);
                webMonitor[i] = new WebRuntimeMonitor(config, lrs, jobManagerSystem[i]);
            }

            ActorRef[] jobManager = new ActorRef[2];
            String[] jobManagerAddress = new String[2];
            for (int i = 0; i < jobManager.length; i++) {
                Configuration jmConfig = config.clone();
                jmConfig.setInteger(ConfigConstants.JOB_MANAGER_WEB_PORT_KEY, webMonitor[i].getServerPort());

                jobManager[i] = JobManager.startJobManagerActors(jmConfig, jobManagerSystem[i], JobManager.class,
                        MemoryArchivist.class)._1();

                jobManagerAddress[i] = AkkaUtils.getAkkaURL(jobManagerSystem[i], jobManager[i]);
                webMonitor[i].start(jobManagerAddress[i]);
            }

            LeaderRetrievalService lrs = ZooKeeperUtils.createLeaderRetrievalService(config);
            leaderRetrievalServices.add(lrs);
            TestingListener leaderListener = new TestingListener();
            lrs.start(leaderListener);

            leaderListener.waitForNewLeader(deadline.timeLeft().toMillis());

            String leaderAddress = leaderListener.getAddress();

            int leaderIndex = leaderAddress.equals(jobManagerAddress[0]) ? 0 : 1;
            int followerIndex = (leaderIndex + 1) % 2;

            ActorSystem leadingSystem = jobManagerSystem[leaderIndex];
            ActorSystem followerSystem = jobManagerSystem[followerIndex];

            WebMonitor leadingWebMonitor = webMonitor[leaderIndex];
            WebMonitor followerWebMonitor = webMonitor[followerIndex];

            // For test stability reason we have to wait until we are sure that both leader
            // listeners have been notified.
            JobManagerRetriever leadingRetriever = Whitebox.getInternalState(leadingWebMonitor, "retriever");

            JobManagerRetriever followerRetriever = Whitebox.getInternalState(followerWebMonitor, "retriever");

            // Wait for the initial notifications
            waitForLeaderNotification(leadingSystem, jobManager[leaderIndex], leadingRetriever, deadline);
            waitForLeaderNotification(leadingSystem, jobManager[leaderIndex], followerRetriever, deadline);

            try (HttpTestClient leaderClient = new HttpTestClient("localhost", leadingWebMonitor.getServerPort());

                    HttpTestClient followingClient = new HttpTestClient("localhost",
                            followerWebMonitor.getServerPort())) {

                String expected = new Scanner(new File(MAIN_RESOURCES_PATH + "/index.html")).useDelimiter("\\A")
                        .next();

                // Request the file from the leaading web server
                leaderClient.sendGetRequest("index.html", deadline.timeLeft());

                HttpTestClient.SimpleHttpResponse response = leaderClient.getNextResponse(deadline.timeLeft());
                assertEquals(HttpResponseStatus.OK, response.getStatus());
                assertEquals(response.getType(), MimeTypes.getMimeTypeForExtension("html"));
                assertEquals(expected, response.getContent());

                // Request the file from the following web server
                followingClient.sendGetRequest("index.html", deadline.timeLeft());
                response = followingClient.getNextResponse(deadline.timeLeft());
                assertEquals(HttpResponseStatus.TEMPORARY_REDIRECT, response.getStatus());
                assertTrue(response.getLocation().contains("" + leadingWebMonitor.getServerPort()));

                // Kill the leader
                leadingSystem.shutdown();

                // Wait for the notification of the follower
                waitForLeaderNotification(followerSystem, jobManager[followerIndex], followerRetriever, deadline);

                // Same request to the new leader
                followingClient.sendGetRequest("index.html", deadline.timeLeft());

                response = followingClient.getNextResponse(deadline.timeLeft());
                assertEquals(HttpResponseStatus.OK, response.getStatus());
                assertEquals(response.getType(), MimeTypes.getMimeTypeForExtension("html"));
                assertEquals(expected, response.getContent());

                // Simple overview request
                followingClient.sendGetRequest("/overview", deadline.timeLeft());

                response = followingClient.getNextResponse(deadline.timeLeft());
                assertEquals(HttpResponseStatus.OK, response.getStatus());
                assertEquals(response.getType(), MimeTypes.getMimeTypeForExtension("json"));
                assertTrue(response.getContent().contains("\"taskmanagers\":1")
                        || response.getContent().contains("\"taskmanagers\":0"));
            }
        } finally {
            for (ActorSystem system : jobManagerSystem) {
                if (system != null) {
                    system.shutdown();
                }
            }

            for (WebMonitor monitor : webMonitor) {
                monitor.stop();
            }

            for (LeaderRetrievalService lrs : leaderRetrievalServices) {
                lrs.stop();
            }
        }
    }

    @Test
    public void testLeaderNotAvailable() throws Exception {
        final Deadline deadline = TestTimeout.fromNow();

        ActorSystem actorSystem = null;
        WebRuntimeMonitor webRuntimeMonitor = null;

        try (TestingServer zooKeeper = new TestingServer()) {

            File logDir = temporaryFolder.newFolder();
            Path logFile = Files.createFile(new File(logDir, "jobmanager.log").toPath());
            Files.createFile(new File(logDir, "jobmanager.out").toPath());

            final Configuration config = new Configuration();
            config.setInteger(ConfigConstants.JOB_MANAGER_WEB_PORT_KEY, 0);
            config.setString(ConfigConstants.JOB_MANAGER_WEB_LOG_PATH_KEY, logFile.toString());
            config.setString(ConfigConstants.RECOVERY_MODE, "ZOOKEEPER");
            config.setString(ConfigConstants.ZOOKEEPER_QUORUM_KEY, zooKeeper.getConnectString());

            actorSystem = AkkaUtils.createDefaultActorSystem();

            LeaderRetrievalService leaderRetrievalService = mock(LeaderRetrievalService.class);
            webRuntimeMonitor = new WebRuntimeMonitor(config, leaderRetrievalService, actorSystem);

            webRuntimeMonitor.start("akka://schmakka");

            try (HttpTestClient client = new HttpTestClient("localhost", webRuntimeMonitor.getServerPort())) {

                client.sendGetRequest("index.html", deadline.timeLeft());

                HttpTestClient.SimpleHttpResponse response = client.getNextResponse();

                assertEquals(HttpResponseStatus.SERVICE_UNAVAILABLE, response.getStatus());
                assertEquals(MimeTypes.getMimeTypeForExtension("txt"), response.getType());
                assertTrue(response.getContent().contains("refresh"));
            }
        } finally {
            if (actorSystem != null) {
                actorSystem.shutdown();
            }

            if (webRuntimeMonitor != null) {
                webRuntimeMonitor.stop();
            }
        }
    }

    // ------------------------------------------------------------------------
    // Tests that access outside of the web root is not allowed
    // ------------------------------------------------------------------------

    /**
     * Files are copied from the flink-dist jar to a temporary directory and
     * then served from there. Only allow to access files in this temporary
     * directory.
     */
    @Test
    public void testNoEscape() throws Exception {
        final Deadline deadline = TestTimeout.fromNow();

        TestingCluster flink = null;
        WebRuntimeMonitor webMonitor = null;

        try {
            flink = new TestingCluster(new Configuration());
            flink.start(true);

            ActorSystem jmActorSystem = flink.jobManagerActorSystems().get().head();
            ActorRef jmActor = flink.jobManagerActors().get().head();

            // Needs to match the leader address from the leader retrieval service
            String jobManagerAddress = AkkaUtils.getAkkaURL(jmActorSystem, jmActor);

            // Web frontend on random port
            Configuration config = new Configuration();
            config.setInteger(ConfigConstants.JOB_MANAGER_WEB_PORT_KEY, 0);

            webMonitor = new WebRuntimeMonitor(config, flink.createLeaderRetrievalService(), jmActorSystem);

            webMonitor.start(jobManagerAddress);

            try (HttpTestClient client = new HttpTestClient("localhost", webMonitor.getServerPort())) {
                String expectedIndex = new Scanner(new File(MAIN_RESOURCES_PATH + "/index.html"))
                        .useDelimiter("\\A").next();

                // 1) Request index.html from web server
                client.sendGetRequest("index.html", deadline.timeLeft());

                HttpTestClient.SimpleHttpResponse response = client.getNextResponse(deadline.timeLeft());
                assertEquals(HttpResponseStatus.OK, response.getStatus());
                assertEquals(response.getType(), MimeTypes.getMimeTypeForExtension("html"));
                assertEquals(expectedIndex, response.getContent());

                // 2) Request file outside of web root
                // Create a test file in the web base dir (parent of web root)
                File illegalFile = new File(webMonitor.getBaseDir(new Configuration()),
                        "test-file-" + UUID.randomUUID());
                illegalFile.deleteOnExit();

                assertTrue("Failed to create test file", illegalFile.createNewFile());

                // Request the created file from the web server
                client.sendGetRequest("../" + illegalFile.getName(), deadline.timeLeft());
                response = client.getNextResponse(deadline.timeLeft());
                assertEquals("Unexpected status code " + response.getStatus() + " for file outside of web root.",
                        HttpResponseStatus.NOT_FOUND, response.getStatus());

                // 3) Request non-existing file
                client.sendGetRequest("not-existing-resource", deadline.timeLeft());
                response = client.getNextResponse(deadline.timeLeft());
                assertEquals("Unexpected status code " + response.getStatus() + " for file outside of web root.",
                        HttpResponseStatus.NOT_FOUND, response.getStatus());
            }
        } finally {
            if (flink != null) {
                flink.shutdown();
            }

            if (webMonitor != null) {
                webMonitor.stop();
            }
        }
    }

    /**
     * Files are copied from the flink-dist jar to a temporary directory and
     * then served from there. Only allow to copy files from <code>flink-dist.jar:/web</code>
     */
    @Test
    public void testNoCopyFromJar() throws Exception {
        final Deadline deadline = TestTimeout.fromNow();

        TestingCluster flink = null;
        WebRuntimeMonitor webMonitor = null;

        try {
            flink = new TestingCluster(new Configuration());
            flink.start(true);

            ActorSystem jmActorSystem = flink.jobManagerActorSystems().get().head();
            ActorRef jmActor = flink.jobManagerActors().get().head();

            // Needs to match the leader address from the leader retrieval service
            String jobManagerAddress = AkkaUtils.getAkkaURL(jmActorSystem, jmActor);

            // Web frontend on random port
            Configuration config = new Configuration();
            config.setInteger(ConfigConstants.JOB_MANAGER_WEB_PORT_KEY, 0);

            webMonitor = new WebRuntimeMonitor(config, flink.createLeaderRetrievalService(), jmActorSystem);

            webMonitor.start(jobManagerAddress);

            try (HttpTestClient client = new HttpTestClient("localhost", webMonitor.getServerPort())) {
                String expectedIndex = new Scanner(new File(MAIN_RESOURCES_PATH + "/index.html"))
                        .useDelimiter("\\A").next();

                // 1) Request index.html from web server
                client.sendGetRequest("index.html", deadline.timeLeft());

                HttpTestClient.SimpleHttpResponse response = client.getNextResponse(deadline.timeLeft());
                assertEquals(HttpResponseStatus.OK, response.getStatus());
                assertEquals(response.getType(), MimeTypes.getMimeTypeForExtension("html"));
                assertEquals(expectedIndex, response.getContent());

                // 2) Request file from class loader
                client.sendGetRequest("../log4j-test.properties", deadline.timeLeft());

                response = client.getNextResponse(deadline.timeLeft());
                assertEquals("Returned status code " + response.getStatus() + " for file outside of web root.",
                        HttpResponseStatus.NOT_FOUND, response.getStatus());

                assertFalse("Did not respond with the file, but still copied it from the JAR.",
                        new File(webMonitor.getBaseDir(new Configuration()), "log4j-test.properties").exists());

                // 3) Request non-existing file
                client.sendGetRequest("not-existing-resource", deadline.timeLeft());
                response = client.getNextResponse(deadline.timeLeft());
                assertEquals("Unexpected status code " + response.getStatus() + " for file outside of web root.",
                        HttpResponseStatus.NOT_FOUND, response.getStatus());
            }
        } finally {
            if (flink != null) {
                flink.shutdown();
            }

            if (webMonitor != null) {
                webMonitor.stop();
            }
        }
    }

    // ------------------------------------------------------------------------

    private void waitForLeaderNotification(ActorSystem system, ActorRef expectedLeader,
            JobManagerRetriever retriever, Deadline deadline) throws Exception {

        String expectedJobManagerUrl = AkkaUtils.getAkkaURL(system, expectedLeader);

        while (deadline.hasTimeLeft()) {
            ActorRef leaderRef = retriever.awaitJobManagerGatewayAndWebPort()._1().actor();

            if (AkkaUtils.getAkkaURL(system, leaderRef).equals(expectedJobManagerUrl)) {
                return;
            } else {
                Thread.sleep(100);
            }
        }
    }
}