org.elasticsearch.plugins.PluginManagerIT.java Source code

Java tutorial

Introduction

Here is the source code for org.elasticsearch.plugins.PluginManagerIT.java

Source

/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch 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.elasticsearch.plugins;

import org.apache.http.impl.client.HttpClients;
import org.apache.lucene.util.LuceneTestCase;
import org.elasticsearch.Version;
import org.elasticsearch.common.Base64;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliTool.ExitStatus;
import org.elasticsearch.common.cli.CliToolTestCase.CaptureOutputTerminal;
import org.elasticsearch.common.hash.MessageDigests;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.node.internal.InternalSettingsPreparer;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
import org.elasticsearch.test.junit.annotations.Network;
import org.elasticsearch.test.rest.client.http.HttpRequestBuilder;
import org.elasticsearch.test.rest.client.http.HttpResponse;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.ssl.SslContext;
import org.jboss.netty.handler.ssl.SslHandler;
import org.jboss.netty.handler.ssl.util.InsecureTrustManagerFactory;
import org.jboss.netty.handler.ssl.util.SelfSignedCertificate;
import org.junit.After;
import org.junit.Before;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;

import java.io.BufferedWriter;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static org.elasticsearch.common.cli.CliTool.ExitStatus.USAGE;
import static org.elasticsearch.common.cli.CliToolTestCase.args;
import static org.elasticsearch.common.io.FileSystemUtilsTests.assertFileContent;
import static org.elasticsearch.common.settings.Settings.settingsBuilder;
import static org.elasticsearch.plugins.PluginInfoTests.writeProperties;
import static org.elasticsearch.test.ESIntegTestCase.Scope;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertDirectoryExists;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertFileExists;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertFileNotExists;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;

@ClusterScope(scope = Scope.TEST, numDataNodes = 0, transportClientRatio = 0.0)
@LuceneTestCase.SuppressFileSystems("*") // TODO: clean up this test to allow extra files
// TODO: jimfs is really broken here (throws wrong exception from detection method).
// if its in your classpath, then do not use plugins!!!!!!
public class PluginManagerIT extends ESIntegTestCase {

    private Environment environment;
    private CaptureOutputTerminal terminal = new CaptureOutputTerminal();

    @Before
    public void setup() throws Exception {
        environment = buildInitialSettings();
        System.setProperty("es.default.path.home", environment.settings().get("path.home"));
        Path binDir = environment.binFile();
        if (!Files.exists(binDir)) {
            Files.createDirectories(binDir);
        }
        Path configDir = environment.configFile();
        if (!Files.exists(configDir)) {
            Files.createDirectories(configDir);
        }
    }

    @After
    public void clearPathHome() {
        System.clearProperty("es.default.path.home");
    }

    private void writeSha1(Path file, boolean corrupt) throws IOException {
        String sha1Hex = MessageDigests.toHexString(MessageDigests.sha1().digest(Files.readAllBytes(file)));
        try (BufferedWriter out = Files.newBufferedWriter(file.resolveSibling(file.getFileName() + ".sha1"),
                StandardCharsets.UTF_8)) {
            out.write(sha1Hex);
            if (corrupt) {
                out.write("bad");
            }
        }
    }

    private void writeMd5(Path file, boolean corrupt) throws IOException {
        String md5Hex = MessageDigests.toHexString(MessageDigests.md5().digest(Files.readAllBytes(file)));
        try (BufferedWriter out = Files.newBufferedWriter(file.resolveSibling(file.getFileName() + ".md5"),
                StandardCharsets.UTF_8)) {
            out.write(md5Hex);
            if (corrupt) {
                out.write("bad");
            }
        }
    }

    /** creates a plugin .zip and returns the url for testing */
    private String createPlugin(final Path structure, String... properties) throws IOException {
        writeProperties(structure, properties);
        Path zip = createTempDir().resolve(structure.getFileName() + ".zip");
        try (ZipOutputStream stream = new ZipOutputStream(Files.newOutputStream(zip))) {
            Files.walkFileTree(structure, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    stream.putNextEntry(new ZipEntry(structure.relativize(file).toString()));
                    Files.copy(file, stream);
                    return FileVisitResult.CONTINUE;
                }
            });
        }
        if (randomBoolean()) {
            writeSha1(zip, false);
        } else if (randomBoolean()) {
            writeMd5(zip, false);
        }
        return zip.toUri().toURL().toString();
    }

    /** creates a plugin .zip and bad checksum file and returns the url for testing */
    private String createPluginWithBadChecksum(final Path structure, String... properties) throws IOException {
        writeProperties(structure, properties);
        Path zip = createTempDir().resolve(structure.getFileName() + ".zip");
        try (ZipOutputStream stream = new ZipOutputStream(Files.newOutputStream(zip))) {
            Files.walkFileTree(structure, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    stream.putNextEntry(new ZipEntry(structure.relativize(file).toString()));
                    Files.copy(file, stream);
                    return FileVisitResult.CONTINUE;
                }
            });
        }
        if (randomBoolean()) {
            writeSha1(zip, true);
        } else {
            writeMd5(zip, true);
        }
        return zip.toUri().toURL().toString();
    }

    public void testThatPluginNameMustBeSupplied() throws IOException {
        Path pluginDir = createTempDir().resolve("fake-plugin");
        String pluginUrl = createPlugin(pluginDir, "description", "fake desc", "name", "fake-plugin", "version",
                "1.0", "elasticsearch.version", Version.CURRENT.toString(), "java.version",
                System.getProperty("java.specification.version"), "jvm", "true", "classname", "FakePlugin");
        assertStatus("install", USAGE);
    }

    public void testLocalPluginInstallWithBinAndConfig() throws Exception {
        String pluginName = "fake-plugin";
        Path pluginDir = createTempDir().resolve(pluginName);
        // create bin/tool and config/file
        Files.createDirectories(pluginDir.resolve("bin"));
        Files.createFile(pluginDir.resolve("bin").resolve("tool"));
        Files.createDirectories(pluginDir.resolve("config"));
        Files.createFile(pluginDir.resolve("config").resolve("file"));

        String pluginUrl = createPlugin(pluginDir, "description", "fake desc", "name", pluginName, "version", "1.0",
                "elasticsearch.version", Version.CURRENT.toString(), "java.version",
                System.getProperty("java.specification.version"), "jvm", "true", "classname", "FakePlugin");

        Path binDir = environment.binFile();
        Path pluginBinDir = binDir.resolve(pluginName);

        Path pluginConfigDir = environment.configFile().resolve(pluginName);
        assertStatusOk("install " + pluginUrl + " --verbose");

        terminal.getTerminalOutput().clear();
        assertStatusOk("list");
        assertThat(terminal.getTerminalOutput(), hasItem(containsString(pluginName)));

        assertDirectoryExists(pluginBinDir);
        assertDirectoryExists(pluginConfigDir);
        Path toolFile = pluginBinDir.resolve("tool");
        assertFileExists(toolFile);

        // check that the file is marked executable, without actually checking that we can execute it.
        PosixFileAttributeView view = Files.getFileAttributeView(toolFile, PosixFileAttributeView.class);
        // the view might be null, on e.g. windows, there is nothing to check there!
        if (view != null) {
            PosixFileAttributes attributes = view.readAttributes();
            assertThat(attributes.permissions(), hasItem(PosixFilePermission.OWNER_EXECUTE));
            assertThat(attributes.permissions(), hasItem(PosixFilePermission.OWNER_READ));
        }
    }

    /**
     * Test for #7890
     */
    public void testLocalPluginInstallWithBinAndConfigInAlreadyExistingConfigDir_7890() throws Exception {
        String pluginName = "fake-plugin";
        Path pluginDir = createTempDir().resolve(pluginName);
        // create config/test.txt with contents 'version1'
        Files.createDirectories(pluginDir.resolve("config"));
        Files.write(pluginDir.resolve("config").resolve("test.txt"), "version1".getBytes(StandardCharsets.UTF_8));

        String pluginUrl = createPlugin(pluginDir, "description", "fake desc", "name", pluginName, "version", "1.0",
                "elasticsearch.version", Version.CURRENT.toString(), "java.version",
                System.getProperty("java.specification.version"), "jvm", "true", "classname", "FakePlugin");

        Path pluginConfigDir = environment.configFile().resolve(pluginName);

        assertStatusOk(String.format(Locale.ROOT, "install %s --verbose", pluginUrl));

        /*
        First time, our plugin contains:
        - config/test.txt (version1)
         */
        assertFileContent(pluginConfigDir, "test.txt", "version1");

        // We now remove the plugin
        assertStatusOk("remove " + pluginName);

        // We should still have test.txt
        assertFileContent(pluginConfigDir, "test.txt", "version1");

        // Installing a new plugin version
        /*
        Second time, our plugin contains:
        - config/test.txt (version2)
        - config/dir/testdir.txt (version1)
        - config/dir/subdir/testsubdir.txt (version1)
         */
        Files.write(pluginDir.resolve("config").resolve("test.txt"), "version2".getBytes(StandardCharsets.UTF_8));
        Files.createDirectories(pluginDir.resolve("config").resolve("dir").resolve("subdir"));
        Files.write(pluginDir.resolve("config").resolve("dir").resolve("testdir.txt"),
                "version1".getBytes(StandardCharsets.UTF_8));
        Files.write(pluginDir.resolve("config").resolve("dir").resolve("subdir").resolve("testsubdir.txt"),
                "version1".getBytes(StandardCharsets.UTF_8));
        pluginUrl = createPlugin(pluginDir, "description", "fake desc", "name", pluginName, "version", "2.0",
                "elasticsearch.version", Version.CURRENT.toString(), "java.version",
                System.getProperty("java.specification.version"), "jvm", "true", "classname", "FakePlugin");

        assertStatusOk(String.format(Locale.ROOT, "install %s --verbose", pluginUrl));
        assertFileContent(pluginConfigDir, "test.txt", "version1");
        assertFileContent(pluginConfigDir, "test.txt.new", "version2");
        assertFileContent(pluginConfigDir, "dir/testdir.txt", "version1");
        assertFileContent(pluginConfigDir, "dir/subdir/testsubdir.txt", "version1");

        // Removing
        assertStatusOk("remove " + pluginName);
        assertFileContent(pluginConfigDir, "test.txt", "version1");
        assertFileContent(pluginConfigDir, "test.txt.new", "version2");
        assertFileContent(pluginConfigDir, "dir/testdir.txt", "version1");
        assertFileContent(pluginConfigDir, "dir/subdir/testsubdir.txt", "version1");

        // Installing a new plugin version
        /*
        Third time, our plugin contains:
        - config/test.txt (version3)
        - config/test2.txt (version1)
        - config/dir/testdir.txt (version2)
        - config/dir/testdir2.txt (version1)
        - config/dir/subdir/testsubdir.txt (version2)
         */
        Files.write(pluginDir.resolve("config").resolve("test.txt"), "version3".getBytes(StandardCharsets.UTF_8));
        Files.write(pluginDir.resolve("config").resolve("test2.txt"), "version1".getBytes(StandardCharsets.UTF_8));
        Files.write(pluginDir.resolve("config").resolve("dir").resolve("testdir.txt"),
                "version2".getBytes(StandardCharsets.UTF_8));
        Files.write(pluginDir.resolve("config").resolve("dir").resolve("testdir2.txt"),
                "version1".getBytes(StandardCharsets.UTF_8));
        Files.write(pluginDir.resolve("config").resolve("dir").resolve("subdir").resolve("testsubdir.txt"),
                "version2".getBytes(StandardCharsets.UTF_8));
        pluginUrl = createPlugin(pluginDir, "description", "fake desc", "name", pluginName, "version", "3.0",
                "elasticsearch.version", Version.CURRENT.toString(), "java.version",
                System.getProperty("java.specification.version"), "jvm", "true", "classname", "FakePlugin");

        assertStatusOk(String.format(Locale.ROOT, "install %s --verbose", pluginUrl));

        assertFileContent(pluginConfigDir, "test.txt", "version1");
        assertFileContent(pluginConfigDir, "test2.txt", "version1");
        assertFileContent(pluginConfigDir, "test.txt.new", "version3");
        assertFileContent(pluginConfigDir, "dir/testdir.txt", "version1");
        assertFileContent(pluginConfigDir, "dir/testdir.txt.new", "version2");
        assertFileContent(pluginConfigDir, "dir/testdir2.txt", "version1");
        assertFileContent(pluginConfigDir, "dir/subdir/testsubdir.txt", "version1");
        assertFileContent(pluginConfigDir, "dir/subdir/testsubdir.txt.new", "version2");
    }

    // For #7152
    public void testLocalPluginInstallWithBinOnly_7152() throws Exception {
        String pluginName = "fake-plugin";
        Path pluginDir = createTempDir().resolve(pluginName);
        // create bin/tool
        Files.createDirectories(pluginDir.resolve("bin"));
        Files.createFile(pluginDir.resolve("bin").resolve("tool"));
        ;
        String pluginUrl = createPlugin(pluginDir, "description", "fake desc", "name", "fake-plugin", "version",
                "1.0", "elasticsearch.version", Version.CURRENT.toString(), "java.version",
                System.getProperty("java.specification.version"), "jvm", "true", "classname", "FakePlugin");

        Path binDir = environment.binFile();
        Path pluginBinDir = binDir.resolve(pluginName);

        assertStatusOk(String.format(Locale.ROOT, "install %s --verbose", pluginUrl));
        assertThatPluginIsListed(pluginName);
        assertDirectoryExists(pluginBinDir);
    }

    public void testListInstalledEmpty() throws IOException {
        assertStatusOk("list");
        assertThat(terminal.getTerminalOutput(), hasItem(containsString("No plugin detected")));
    }

    public void testListInstalledEmptyWithExistingPluginDirectory() throws IOException {
        Files.createDirectory(environment.pluginsFile());
        assertStatusOk("list");
        assertThat(terminal.getTerminalOutput(), hasItem(containsString("No plugin detected")));
    }

    public void testInstallPluginVerbose() throws IOException {
        String pluginName = "fake-plugin";
        Path pluginDir = createTempDir().resolve(pluginName);
        String pluginUrl = createPlugin(pluginDir, "description", "fake desc", "name", pluginName, "version", "1.0",
                "elasticsearch.version", Version.CURRENT.toString(), "java.version",
                System.getProperty("java.specification.version"), "jvm", "true", "classname", "FakePlugin");
        ExitStatus status = new PluginManagerCliParser(terminal)
                .execute(args("install " + pluginUrl + " --verbose"));
        assertThat("Terminal output was: " + terminal.getTerminalOutput(), status, is(ExitStatus.OK));
        assertThat(terminal.getTerminalOutput(), hasItem(containsString("Name: fake-plugin")));
        assertThat(terminal.getTerminalOutput(), hasItem(containsString("Description: fake desc")));
        assertThat(terminal.getTerminalOutput(), hasItem(containsString("Site: false")));
        assertThat(terminal.getTerminalOutput(), hasItem(containsString("Version: 1.0")));
        assertThat(terminal.getTerminalOutput(), hasItem(containsString("JVM: true")));
        assertThatPluginIsListed(pluginName);
    }

    public void testInstallPlugin() throws IOException {
        String pluginName = "fake-plugin";
        Path pluginDir = createTempDir().resolve(pluginName);
        String pluginUrl = createPlugin(pluginDir, "description", "fake desc", "name", pluginName, "version", "1.0",
                "elasticsearch.version", Version.CURRENT.toString(), "java.version",
                System.getProperty("java.specification.version"), "jvm", "true", "classname", "FakePlugin");
        ExitStatus status = new PluginManagerCliParser(terminal).execute(args("install " + pluginUrl));
        assertThat("Terminal output was: " + terminal.getTerminalOutput(), status, is(ExitStatus.OK));
        assertThat(terminal.getTerminalOutput(), not(hasItem(containsString("Name: fake-plugin"))));
        assertThat(terminal.getTerminalOutput(), not(hasItem(containsString("Description:"))));
        assertThat(terminal.getTerminalOutput(), not(hasItem(containsString("Site:"))));
        assertThat(terminal.getTerminalOutput(), not(hasItem(containsString("Version:"))));
        assertThat(terminal.getTerminalOutput(), not(hasItem(containsString("JVM:"))));
        assertThatPluginIsListed(pluginName);
    }

    /**
     * @deprecated support for this is not going to stick around, seriously.
     */
    @Deprecated
    public void testAlreadyInstalledNotIsolated() throws Exception {
        String pluginName = "fake-plugin";
        Path pluginDir = createTempDir().resolve(pluginName);
        Files.createDirectories(pluginDir);
        // create a jar file in the plugin
        Path pluginJar = pluginDir.resolve("fake-plugin.jar");
        try (ZipOutputStream out = new JarOutputStream(
                Files.newOutputStream(pluginJar, StandardOpenOption.CREATE))) {
            out.putNextEntry(new ZipEntry("foo.class"));
            out.closeEntry();
        }
        String pluginUrl = createPlugin(pluginDir, "description", "fake desc", "name", pluginName, "version", "1.0",
                "elasticsearch.version", Version.CURRENT.toString(), "java.version",
                System.getProperty("java.specification.version"), "isolated", "false", "jvm", "true", "classname",
                "FakePlugin");

        // install
        ExitStatus status = new PluginManagerCliParser(terminal).execute(args("install " + pluginUrl));
        assertEquals("unexpected exit status: output: " + terminal.getTerminalOutput(), ExitStatus.OK, status);

        // install again
        status = new PluginManagerCliParser(terminal).execute(args("install " + pluginUrl));
        List<String> output = terminal.getTerminalOutput();
        assertEquals("unexpected exit status: output: " + output, ExitStatus.IO_ERROR, status);
        boolean foundExpectedMessage = false;
        for (String line : output) {
            foundExpectedMessage |= line.contains("already exists");
        }
        assertTrue(foundExpectedMessage);
    }

    public void testInstallSitePluginVerbose() throws IOException {
        String pluginName = "fake-plugin";
        Path pluginDir = createTempDir().resolve(pluginName);
        Files.createDirectories(pluginDir.resolve("_site"));
        Files.createFile(pluginDir.resolve("_site").resolve("somefile"));
        String pluginUrl = createPlugin(pluginDir, "description", "fake desc", "name", pluginName, "version", "1.0",
                "site", "true");
        ExitStatus status = new PluginManagerCliParser(terminal)
                .execute(args("install " + pluginUrl + " --verbose"));
        assertThat("Terminal output was: " + terminal.getTerminalOutput(), status, is(ExitStatus.OK));
        assertThat(terminal.getTerminalOutput(), hasItem(containsString("Name: fake-plugin")));
        assertThat(terminal.getTerminalOutput(), hasItem(containsString("Description: fake desc")));
        assertThat(terminal.getTerminalOutput(), hasItem(containsString("Site: true")));
        assertThat(terminal.getTerminalOutput(), hasItem(containsString("Version: 1.0")));
        assertThat(terminal.getTerminalOutput(), hasItem(containsString("JVM: false")));
        assertThatPluginIsListed(pluginName);
        // We want to check that Plugin Manager moves content to _site
        assertFileExists(environment.pluginsFile().resolve(pluginName).resolve("_site"));
    }

    public void testInstallSitePlugin() throws IOException {
        String pluginName = "fake-plugin";
        Path pluginDir = createTempDir().resolve(pluginName);
        Files.createDirectories(pluginDir.resolve("_site"));
        Files.createFile(pluginDir.resolve("_site").resolve("somefile"));
        String pluginUrl = createPlugin(pluginDir, "description", "fake desc", "name", pluginName, "version", "1.0",
                "site", "true");
        ExitStatus status = new PluginManagerCliParser(terminal).execute(args("install " + pluginUrl));
        assertThat("Terminal output was: " + terminal.getTerminalOutput(), status, is(ExitStatus.OK));
        assertThat(terminal.getTerminalOutput(), not(hasItem(containsString("Name: fake-plugin"))));
        assertThat(terminal.getTerminalOutput(), not(hasItem(containsString("Description:"))));
        assertThat(terminal.getTerminalOutput(), not(hasItem(containsString("Site:"))));
        assertThat(terminal.getTerminalOutput(), not(hasItem(containsString("Version:"))));
        assertThat(terminal.getTerminalOutput(), not(hasItem(containsString("JVM:"))));
        assertThatPluginIsListed(pluginName);
        // We want to check that Plugin Manager moves content to _site
        assertFileExists(environment.pluginsFile().resolve(pluginName).resolve("_site"));
    }

    public void testInstallPluginWithBadChecksum() throws IOException {
        String pluginName = "fake-plugin";
        Path pluginDir = createTempDir().resolve(pluginName);
        Files.createDirectories(pluginDir.resolve("_site"));
        Files.createFile(pluginDir.resolve("_site").resolve("somefile"));
        String pluginUrl = createPluginWithBadChecksum(pluginDir, "description", "fake desc", "version", "1.0",
                "site", "true");
        assertStatus(String.format(Locale.ROOT, "install %s --verbose", pluginUrl), ExitStatus.IO_ERROR);
        assertThatPluginIsNotListed(pluginName);
        assertFileNotExists(environment.pluginsFile().resolve(pluginName).resolve("_site"));
    }

    private void singlePluginInstallAndRemove(String pluginDescriptor, String pluginName, String pluginCoordinates)
            throws IOException {
        logger.info("--> trying to download and install [{}]", pluginDescriptor);
        if (pluginCoordinates == null) {
            assertStatusOk(String.format(Locale.ROOT, "install %s --verbose", pluginDescriptor));
        } else {
            assertStatusOk(String.format(Locale.ROOT, "install %s --verbose", pluginCoordinates));
        }
        assertThatPluginIsListed(pluginName);

        terminal.getTerminalOutput().clear();
        assertStatusOk("remove " + pluginDescriptor);
        assertThat(terminal.getTerminalOutput(), hasItem(containsString("Removing " + pluginDescriptor)));

        // not listed anymore
        terminal.getTerminalOutput().clear();
        assertStatusOk("list");
        assertThat(terminal.getTerminalOutput(), not(hasItem(containsString(pluginName))));
    }

    /**
     * We are ignoring by default these tests as they require to have an internet access
     * To activate the test, use -Dtests.network=true
     * We test regular form: username/reponame/version
     * It should find it in download.elasticsearch.org service
     */
    @Network
    @AwaitsFix(bugUrl = "fails with jar hell failures - http://build-us-00.elastic.co/job/es_core_master_oracle_6/519/testReport/")
    public void testInstallPluginWithElasticsearchDownloadService() throws IOException {
        assumeTrue("download.elastic.co is accessible",
                isDownloadServiceWorking("download.elastic.co", 80, "/elasticsearch/ci-test.txt"));
        singlePluginInstallAndRemove("elasticsearch/elasticsearch-transport-thrift/2.4.0",
                "elasticsearch-transport-thrift", null);
    }

    /**
     * We are ignoring by default these tests as they require to have an internet access
     * To activate the test, use -Dtests.network=true
     * We test regular form: groupId/artifactId/version
     * It should find it in maven central service
     */
    @Network
    @AwaitsFix(bugUrl = "fails with jar hell failures - http://build-us-00.elastic.co/job/es_core_master_oracle_6/519/testReport/")
    public void testInstallPluginWithMavenCentral() throws IOException {
        assumeTrue("search.maven.org is accessible", isDownloadServiceWorking("search.maven.org", 80, "/"));
        assumeTrue("repo1.maven.org is accessible", isDownloadServiceWorking("repo1.maven.org", 443,
                "/maven2/org/elasticsearch/elasticsearch-transport-thrift/2.4.0/elasticsearch-transport-thrift-2.4.0.pom"));
        singlePluginInstallAndRemove("org.elasticsearch/elasticsearch-transport-thrift/2.4.0",
                "elasticsearch-transport-thrift", null);
    }

    /**
     * We are ignoring by default these tests as they require to have an internet access
     * To activate the test, use -Dtests.network=true
     * We test site plugins from github: userName/repoName
     * It should find it on github
     */
    @Network
    @AwaitsFix(bugUrl = "needs to be adapted to 2.0")
    public void testInstallPluginWithGithub() throws IOException {
        assumeTrue("github.com is accessible", isDownloadServiceWorking("github.com", 443, "/"));
        singlePluginInstallAndRemove("elasticsearch/kibana", "kibana", null);
    }

    private boolean isDownloadServiceWorking(String host, int port, String resource) {
        try {
            String protocol = port == 443 ? "https" : "http";
            HttpResponse response = new HttpRequestBuilder(HttpClients.createDefault()).protocol(protocol)
                    .host(host).port(port).path(resource).execute();
            if (response.getStatusCode() != 200) {
                logger.warn("[{}{}] download service is not working. Disabling current test.", host, resource);
                return false;
            }
            return true;
        } catch (Throwable t) {
            logger.warn("[{}{}] download service is not working. Disabling current test.", host, resource);
        }
        return false;
    }

    public void testRemovePlugin() throws Exception {
        String pluginName = "plugintest";
        Path pluginDir = createTempDir().resolve(pluginName);
        String pluginUrl = createPlugin(pluginDir, "description", "fake desc", "name", pluginName, "version",
                "1.0.0", "elasticsearch.version", Version.CURRENT.toString(), "java.version",
                System.getProperty("java.specification.version"), "jvm", "true", "classname", "FakePlugin");

        // We want to remove plugin with plugin short name
        singlePluginInstallAndRemove("plugintest", "plugintest", pluginUrl);

        // We want to remove plugin with groupid/artifactid/version form
        singlePluginInstallAndRemove("groupid/plugintest/1.0.0", "plugintest", pluginUrl);

        // We want to remove plugin with groupid/artifactid form
        singlePluginInstallAndRemove("groupid/plugintest", "plugintest", pluginUrl);
    }

    public void testRemovePlugin_NullName_ThrowsException() throws IOException {
        assertStatus("remove ", USAGE);
    }

    public void testRemovePluginWithURLForm() throws Exception {
        assertStatus("remove file://whatever", USAGE);
        assertThat(terminal.getTerminalOutput(), hasItem(containsString("Illegal plugin name")));
    }

    public void testForbiddenPluginNames() throws IOException {
        assertStatus("remove elasticsearch", USAGE);
        assertStatus("remove elasticsearch.bat", USAGE);
        assertStatus("remove elasticsearch.in.sh", USAGE);
        assertStatus("remove plugin", USAGE);
        assertStatus("remove plugin.bat", USAGE);
        assertStatus("remove service.bat", USAGE);
        assertStatus("remove ELASTICSEARCH", USAGE);
        assertStatus("remove ELASTICSEARCH.IN.SH", USAGE);
    }

    public void testOfficialPluginName_ThrowsException() throws IOException {
        PluginManager.checkForOfficialPlugins("analysis-icu");
        PluginManager.checkForOfficialPlugins("analysis-kuromoji");
        PluginManager.checkForOfficialPlugins("analysis-phonetic");
        PluginManager.checkForOfficialPlugins("analysis-smartcn");
        PluginManager.checkForOfficialPlugins("analysis-stempel");
        PluginManager.checkForOfficialPlugins("cloud-aws");
        PluginManager.checkForOfficialPlugins("cloud-azure");
        PluginManager.checkForOfficialPlugins("cloud-gce");
        PluginManager.checkForOfficialPlugins("delete-by-query");
        PluginManager.checkForOfficialPlugins("lang-javascript");
        PluginManager.checkForOfficialPlugins("lang-python");
        PluginManager.checkForOfficialPlugins("mapper-attachments");
        PluginManager.checkForOfficialPlugins("mapper-murmur3");
        PluginManager.checkForOfficialPlugins("mapper-size");
        PluginManager.checkForOfficialPlugins("discovery-multicast");

        try {
            PluginManager.checkForOfficialPlugins("elasticsearch-mapper-attachment");
            fail("elasticsearch-mapper-attachment should not be allowed");
        } catch (IllegalArgumentException e) {
            // We expect that error
        }
    }

    public void testThatBasicAuthIsRejectedOnHttp() throws Exception {
        assertStatus(String.format(Locale.ROOT, "install http://user:pass@localhost:12345/foo.zip --verbose"),
                CliTool.ExitStatus.IO_ERROR);
        assertThat(terminal.getTerminalOutput(),
                hasItem(containsString("Basic auth is only supported for HTTPS!")));
    }

    public void testThatBasicAuthIsSupportedWithHttps() throws Exception {
        assumeTrue("test requires security manager to be disabled", System.getSecurityManager() == null);

        SSLSocketFactory defaultSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
        ServerBootstrap serverBootstrap = new ServerBootstrap(new NioServerSocketChannelFactory());
        SelfSignedCertificate ssc = new SelfSignedCertificate("localhost");

        try {

            //  Create a trust manager that does not validate certificate chains:
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, InsecureTrustManagerFactory.INSTANCE.getTrustManagers(), null);
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

            final List<HttpRequest> requests = new ArrayList<>();
            final SslContext sslContext = SslContext.newServerContext(ssc.certificate(), ssc.privateKey());

            serverBootstrap.setPipelineFactory(new ChannelPipelineFactory() {
                @Override
                public ChannelPipeline getPipeline() throws Exception {
                    return Channels.pipeline(new SslHandler(sslContext.newEngine()), new HttpRequestDecoder(),
                            new HttpResponseEncoder(), new LoggingServerHandler(requests));
                }
            });

            Channel channel = serverBootstrap.bind(new InetSocketAddress(InetAddress.getByName("localhost"), 0));
            int port = ((InetSocketAddress) channel.getLocalAddress()).getPort();
            // IO_ERROR because there is no real file delivered...
            assertStatus(
                    String.format(Locale.ROOT,
                            "install https://user:pass@localhost:%s/foo.zip --verbose --timeout 1s", port),
                    ExitStatus.IO_ERROR);

            // ensure that we did not try any other data source like download.elastic.co, in case we specified our own local URL
            assertThat(terminal.getTerminalOutput(), not(hasItem(containsString("download.elastic.co"))));

            assertThat(requests, hasSize(1));
            String msg = String.format(Locale.ROOT,
                    "Request header did not contain Authorization header, terminal output was: %s",
                    terminal.getTerminalOutput());
            assertThat(msg, requests.get(0).headers().contains("Authorization"), is(true));
            assertThat(msg, requests.get(0).headers().get("Authorization"),
                    is("Basic " + Base64.encodeBytes("user:pass".getBytes(StandardCharsets.UTF_8))));
        } finally {
            HttpsURLConnection.setDefaultSSLSocketFactory(defaultSocketFactory);
            serverBootstrap.releaseExternalResources();
            ssc.delete();
        }
    }

    private static class LoggingServerHandler extends SimpleChannelUpstreamHandler {

        private List<HttpRequest> requests;

        public LoggingServerHandler(List<HttpRequest> requests) {
            this.requests = requests;
        }

        @Override
        public void messageReceived(final ChannelHandlerContext ctx, final MessageEvent e)
                throws InterruptedException {
            final HttpRequest request = (HttpRequest) e.getMessage();
            requests.add(request);
            final org.jboss.netty.handler.codec.http.HttpResponse response = new DefaultHttpResponse(HTTP_1_1,
                    HttpResponseStatus.BAD_REQUEST);
            ctx.getChannel().write(response);
        }
    }

    private Environment buildInitialSettings() throws IOException {
        Settings settings = settingsBuilder().put("http.enabled", true).put("path.home", createTempDir()).build();
        return InternalSettingsPreparer.prepareEnvironment(settings, null);
    }

    private void assertStatusOk(String command) {
        assertStatus(command, ExitStatus.OK);
    }

    private void assertStatus(String command, ExitStatus exitStatus) {
        ExitStatus status = new PluginManagerCliParser(terminal).execute(args(command));
        assertThat("Terminal output was: " + terminal.getTerminalOutput(), status, is(exitStatus));
    }

    private void assertThatPluginIsListed(String pluginName) {
        terminal.getTerminalOutput().clear();
        assertStatusOk("list");
        String message = String.format(Locale.ROOT, "Terminal output was: %s", terminal.getTerminalOutput());
        assertThat(message, terminal.getTerminalOutput(), hasItem(containsString(pluginName)));
    }

    private void assertThatPluginIsNotListed(String pluginName) {
        terminal.getTerminalOutput().clear();
        assertStatusOk("list");
        String message = String.format(Locale.ROOT, "Terminal output was: %s", terminal.getTerminalOutput());
        assertFalse(message, terminal.getTerminalOutput().contains(pluginName));
    }
}