org.elasticsearch.test.ESBackcompatTestCase.java Source code

Java tutorial

Introduction

Here is the source code for org.elasticsearch.test.ESBackcompatTestCase.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.test;

import com.carrotsearch.randomizedtesting.annotations.TestGroup;
import org.apache.commons.lang3.ArrayUtils;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.routing.IndexRoutingTable;
import org.elasticsearch.cluster.routing.IndexShardRoutingTable;
import org.elasticsearch.cluster.routing.ShardRouting;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.indices.recovery.RecoverySettings;
import org.elasticsearch.test.junit.annotations.TestLogging;
import org.elasticsearch.test.junit.listeners.LoggingListener;
import org.elasticsearch.test.transport.AssertingLocalTransport;
import org.elasticsearch.test.transport.MockTransportService;
import org.elasticsearch.transport.Transport;
import org.elasticsearch.transport.TransportModule;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.transport.netty.NettyTransport;
import org.junit.Ignore;

import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Random;

import static org.hamcrest.Matchers.is;

/**
 * Abstract base class for backwards compatibility tests. Subclasses of this class
 * can run tests against a mixed version cluster. A subset of the nodes in the cluster
 * are started in dedicated process running off a full fledged elasticsearch release.
 * Nodes can be "upgraded" from the "backwards" node to an "new" node where "new" nodes
 * version corresponds to current version.
 * The purpose of this test class is to run tests in scenarios where clusters are in an
 * intermediate state during a rolling upgrade as well as upgrade situations. The clients
 * accessed via #client() are random clients to the nodes in the cluster which might
 * execute requests on the "new" as well as the "old" nodes.
 * <p>
 *   Note: this base class is still experimental and might have bugs or leave external processes running behind.
 * </p>
 * Backwards compatibility tests are disabled by default via {@link Backwards} annotation.
 * The following system variables control the test execution:
 * <ul>
 *     <li>
 *          <tt>{@value #TESTS_BACKWARDS_COMPATIBILITY}</tt> enables / disables
 *          tests annotated with {@link Backwards} (defaults to
 *          <tt>false</tt>)
 *     </li>
 *     <li>
 *          <tt>{@value #TESTS_BACKWARDS_COMPATIBILITY_VERSION}</tt>
 *          sets the version to run the external nodes from formatted as <i>X.Y.Z</i>.
 *          The tests class will try to locate a release folder <i>elasticsearch-X.Y.Z</i>
 *          within path passed via {@value #TESTS_BACKWARDS_COMPATIBILITY_PATH}
 *          depending on this system variable.
 *     </li>
 *     <li>
 *          <tt>{@value #TESTS_BACKWARDS_COMPATIBILITY_PATH}</tt> the path to the
 *          elasticsearch releases to run backwards compatibility tests against.
 *     </li>
 * </ul>
 *
 */
// the transportClientRatio is tricky here since we don't fully control the cluster nodes
@ESBackcompatTestCase.Backwards
@ESIntegTestCase.ClusterScope(minNumDataNodes = 0, maxNumDataNodes = 2, scope = ESIntegTestCase.Scope.SUITE, numClientNodes = 0, transportClientRatio = 0.0)
public abstract class ESBackcompatTestCase extends ESIntegTestCase {

    /**
     * Key used to set the path for the elasticsearch executable used to run backwards compatibility tests from
     * via the commandline -D{@value #TESTS_BACKWARDS_COMPATIBILITY}
     */
    public static final String TESTS_BACKWARDS_COMPATIBILITY = "tests.bwc";
    public static final String TESTS_BACKWARDS_COMPATIBILITY_VERSION = "tests.bwc.version";
    /**
     * Key used to set the path for the elasticsearch executable used to run backwards compatibility tests from
     * via the commandline -D{@value #TESTS_BACKWARDS_COMPATIBILITY_PATH}
     */
    public static final String TESTS_BACKWARDS_COMPATIBILITY_PATH = "tests.bwc.path";
    /**
     * Property that allows to adapt the tests behaviour to older features/bugs based on the input version
     */
    private static final String TESTS_COMPATIBILITY = "tests.compatibility";

    private static final Version GLOABL_COMPATIBILITY_VERSION = Version.fromString(compatibilityVersionProperty());

    private static Path backwardsCompatibilityPath() {
        String path = System.getProperty(TESTS_BACKWARDS_COMPATIBILITY_PATH);
        if (path == null || path.isEmpty()) {
            throw new IllegalArgumentException(
                    "Must specify backwards test path with property " + TESTS_BACKWARDS_COMPATIBILITY_PATH);
        }
        String version = System.getProperty(TESTS_BACKWARDS_COMPATIBILITY_VERSION);
        if (version == null || version.isEmpty()) {
            throw new IllegalArgumentException(
                    "Must specify backwards test version with property " + TESTS_BACKWARDS_COMPATIBILITY_VERSION);
        }
        if (Version.fromString(version).before(Version.CURRENT.minimumCompatibilityVersion())) {
            throw new IllegalArgumentException(
                    "Backcompat elasticsearch version must be same major version as current. " + "backcompat: "
                            + version + ", current: " + Version.CURRENT.toString());
        }
        Path file = PathUtils.get(path, "elasticsearch-" + version);
        if (!Files.exists(file)) {
            throw new IllegalArgumentException("Backwards tests location is missing: " + file.toAbsolutePath());
        }
        if (!Files.isDirectory(file)) {
            throw new IllegalArgumentException(
                    "Backwards tests location is not a directory: " + file.toAbsolutePath());
        }
        return file;
    }

    @Override
    protected Settings.Builder setRandomIndexSettings(Random random, Settings.Builder builder) {
        if (globalCompatibilityVersion().before(Version.V_1_3_2)) {
            // if we test against nodes before 1.3.2 we disable all the compression due to a known bug
            // see #7210
            builder.put(RecoverySettings.INDICES_RECOVERY_COMPRESS, false);
        }
        return builder;
    }

    /**
     * Retruns the tests compatibility version.
     */
    public Version compatibilityVersion() {
        return compatibilityVersion(getClass());
    }

    private Version compatibilityVersion(Class<?> clazz) {
        if (clazz == Object.class || clazz == ESIntegTestCase.class) {
            return globalCompatibilityVersion();
        }
        CompatibilityVersion annotation = clazz.getAnnotation(CompatibilityVersion.class);
        if (annotation != null) {
            return Version.smallest(Version.fromId(annotation.version()),
                    compatibilityVersion(clazz.getSuperclass()));
        }
        return compatibilityVersion(clazz.getSuperclass());
    }

    /**
     * Returns a global compatibility version that is set via the
     * {@value #TESTS_COMPATIBILITY} or {@value #TESTS_BACKWARDS_COMPATIBILITY_VERSION} system property.
     * If both are unset the current version is used as the global compatibility version. This
     * compatibility version is used for static randomization. For per-suite compatibility version see
     * {@link #compatibilityVersion()}
     */
    public static Version globalCompatibilityVersion() {
        return GLOABL_COMPATIBILITY_VERSION;
    }

    private static String compatibilityVersionProperty() {
        final String version = System.getProperty(TESTS_COMPATIBILITY);
        if (Strings.hasLength(version)) {
            return version;
        }
        return System.getProperty(TESTS_BACKWARDS_COMPATIBILITY_VERSION);
    }

    public CompositeTestCluster backwardsCluster() {
        return (CompositeTestCluster) cluster();
    }

    @Override
    protected TestCluster buildTestCluster(Scope scope, long seed) throws IOException {
        TestCluster cluster = super.buildTestCluster(scope, seed);
        ExternalNode externalNode = new ExternalNode(backwardsCompatibilityPath(), randomLong(),
                new SettingsSource() {
                    @Override
                    public Settings node(int nodeOrdinal) {
                        return externalNodeSettings(nodeOrdinal);
                    }

                    @Override
                    public Settings transportClient() {
                        return transportClientSettings();
                    }
                });
        return new CompositeTestCluster((InternalTestCluster) cluster,
                between(minExternalNodes(), maxExternalNodes()), externalNode);
    }

    private Settings addLoggerSettings(Settings externalNodesSettings) {
        TestLogging logging = getClass().getAnnotation(TestLogging.class);
        Map<String, String> loggingLevels = LoggingListener.getLoggersAndLevelsFromAnnotation(logging);
        Settings.Builder finalSettings = Settings.settingsBuilder();
        if (loggingLevels != null) {
            for (Map.Entry<String, String> level : loggingLevels.entrySet()) {
                finalSettings.put("logger." + level.getKey(), level.getValue());
            }
        }
        finalSettings.put(externalNodesSettings);
        return finalSettings.build();
    }

    protected int minExternalNodes() {
        return 1;
    }

    protected int maxExternalNodes() {
        return 2;
    }

    @Override
    protected int maximumNumberOfReplicas() {
        return 1;
    }

    protected Settings requiredSettings() {
        return ExternalNode.REQUIRED_SETTINGS;
    }

    @Override
    protected Settings nodeSettings(int nodeOrdinal) {
        return commonNodeSettings(nodeOrdinal);
    }

    public void assertAllShardsOnNodes(String index, String pattern) {
        ClusterState clusterState = client().admin().cluster().prepareState().execute().actionGet().getState();
        for (IndexRoutingTable indexRoutingTable : clusterState.routingTable()) {
            for (IndexShardRoutingTable indexShardRoutingTable : indexRoutingTable) {
                for (ShardRouting shardRouting : indexShardRoutingTable) {
                    if (shardRouting.currentNodeId() != null && index.equals(shardRouting.getIndex())) {
                        String name = clusterState.nodes().get(shardRouting.currentNodeId()).name();
                        assertThat("Allocated on new node: " + name, Regex.simpleMatch(pattern, name), is(true));
                    }
                }
            }
        }
    }

    protected Settings commonNodeSettings(int nodeOrdinal) {
        Settings.Builder builder = Settings.builder().put(requiredSettings());
        builder.removeArrayElement("plugin.types", MockTransportService.Plugin.class.getName());
        builder.removeArrayElement("plugin.types", AssertingLocalTransport.class.getName());
        builder.put(TransportModule.TRANSPORT_TYPE_KEY, "netty"); // run same transport  / disco as external
        if (compatibilityVersion().before(Version.V_1_3_2)) {
            // if we test against nodes before 1.3.2 we disable all the compression due to a known bug
            // see #7210
            builder.put(Transport.TransportSettings.TRANSPORT_TCP_COMPRESS, false)
                    .put(RecoverySettings.INDICES_RECOVERY_COMPRESS, false);
        }
        return builder.build();
    }

    protected Settings externalNodeSettings(int nodeOrdinal) {
        return addLoggerSettings(commonNodeSettings(nodeOrdinal));
    }

    /**
     * Annotation for backwards compat tests
     */
    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @TestGroup(enabled = false, sysProperty = ESBackcompatTestCase.TESTS_BACKWARDS_COMPATIBILITY)
    public @interface Backwards {
    }

    /**
     * If a test is annotated with {@link CompatibilityVersion}
     * all randomized settings will only contain settings or mappings which are compatible with the specified version ID.
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.TYPE })
    public @interface CompatibilityVersion {
        int version();
    }
}