All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opensearch.test.VersionUtils Maven / Gradle / Ivy

There is a newer version: 2.18.0
Show newest version
/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * The OpenSearch Contributors require contributions made to
 * this file be licensed under the Apache-2.0 license or a
 * compatible open source license.
 */

/*
 * 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.
 */

/*
 * Modifications Copyright OpenSearch Contributors. See
 * GitHub history for details.
 */

package org.opensearch.test;

import org.opensearch.LegacyESVersion;
import org.opensearch.Version;
import org.opensearch.common.Nullable;
import org.opensearch.common.collect.Tuple;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/** Utilities for selecting versions in tests */
public class VersionUtils {

    /**
     * Sort versions that have backwards compatibility guarantees from
     * those that don't. Doesn't actually check whether or not the versions
     * are released, instead it relies on gradle to have already checked
     * this which it does in {@code :core:verifyVersions}. So long as the
     * rules here match up with the rules in gradle then this should
     * produce sensible results.
     * @return a tuple containing versions with backwards compatibility
     * guarantees in v1 and versions without the guarantees in v2
     */
    static Tuple, List> resolveReleasedVersions(Version current, Class versionClass) {
        // group versions into major version
        Map> majorVersions = Version.getDeclaredVersions(versionClass)
            .stream()
            .collect(Collectors.groupingBy(v -> (int) v.major));
        // this breaks b/c 5.x is still in version list but cluster-manager doesn't care about it!
        // assert majorVersions.size() == 2;
        List> oldVersions = new ArrayList<>(0);
        List> previousMajor = new ArrayList<>(0);
        if (current.major == 2) {
            // add legacy first
            oldVersions.addAll(splitByMinor(majorVersions.getOrDefault(6, Collections.emptyList())));
            previousMajor.addAll(splitByMinor(majorVersions.getOrDefault(7, Collections.emptyList())));
        }
        // TODO: remove oldVersions, we should only ever have 2 majors in Version
        // rebasing OpenSearch to 1.0.0 means the previous major version was Legacy 7.0.0
        int previousMajorID = current.major == 1 ? 7 : current.major - 1;
        oldVersions.addAll(splitByMinor(majorVersions.getOrDefault(previousMajorID - 1, Collections.emptyList())));
        previousMajor.addAll(splitByMinor(majorVersions.getOrDefault(previousMajorID, Collections.emptyList())));

        List> currentMajor = splitByMinor(majorVersions.get((int) current.major));

        List unreleasedVersions = new ArrayList<>();
        final List> stableVersions;
        if (currentMajor.size() == 1) {
            // on main branch
            stableVersions = previousMajor;
            // remove current
            moveLastToUnreleased(currentMajor, unreleasedVersions);
        } else if (current.major != 1) {
            // on a stable or release branch, ie N.x
            stableVersions = currentMajor;
            // remove the next maintenance bugfix
            final Version prevMajorLastMinor = moveLastToUnreleased(previousMajor, unreleasedVersions);
            if (prevMajorLastMinor.revision == 0 && previousMajor.isEmpty() == false) {
                // The latest minor in the previous major is a ".0" release, so there must be an unreleased bugfix for the minor before that
                moveLastToUnreleased(previousMajor, unreleasedVersions);
            }
        } else {
            stableVersions = currentMajor;
        }

        // remove last minor unless it's the first OpenSearch version.
        // all Legacy ES versions are released, so we don't exclude any.
        if (current.equals(Version.V_1_0_0) == false) {
            List lastMinorLine = stableVersions.get(stableVersions.size() - 1);
            if (lastMinorLine.get(lastMinorLine.size() - 1) instanceof LegacyESVersion == false) {
                // if the last minor line is Legacy there are no more staged releases; do nothing
                Version lastMinor = moveLastToUnreleased(stableVersions, unreleasedVersions);
                if (lastMinor instanceof LegacyESVersion == false && lastMinor.revision == 0) {
                    // no more staged legacy versions
                    if (stableVersions.get(stableVersions.size() - 1).size() == 1) {
                        // a minor is being staged, which is also unreleased
                        moveLastToUnreleased(stableVersions, unreleasedVersions);
                    }
                    // remove the next bugfix
                    if (stableVersions.isEmpty() == false) {
                        moveLastToUnreleased(stableVersions, unreleasedVersions);
                    }
                }
            }
        }

        // If none of the previous major was released, then the last minor and bugfix of the old version was not released either.
        if (previousMajor.isEmpty()) {
            assert currentMajor.isEmpty() : currentMajor;
            // minor of the old version is being staged
            moveLastToUnreleased(oldVersions, unreleasedVersions);
            // bugix of the old version is also being staged
            moveLastToUnreleased(oldVersions, unreleasedVersions);
        }
        List releasedVersions = Stream.of(oldVersions, previousMajor, currentMajor)
            .flatMap(List::stream)
            .flatMap(List::stream)
            .collect(Collectors.toList());
        Collections.sort(unreleasedVersions); // we add unreleased out of order, so need to sort here
        return new Tuple<>(Collections.unmodifiableList(releasedVersions), Collections.unmodifiableList(unreleasedVersions));
    }

    // split the given versions into sub lists grouped by minor version
    private static List> splitByMinor(List versions) {
        Map> byMinor = versions.stream().collect(Collectors.groupingBy(v -> (int) v.minor));
        return byMinor.entrySet().stream().sorted(Map.Entry.comparingByKey()).map(Map.Entry::getValue).collect(Collectors.toList());
    }

    // move the last version of the last minor in versions to the unreleased versions
    private static Version moveLastToUnreleased(List> versions, List unreleasedVersions) {
        List lastMinor = new ArrayList<>(versions.get(versions.size() - 1));
        Version lastVersion = lastMinor.remove(lastMinor.size() - 1);
        if (lastMinor.isEmpty()) {
            versions.remove(versions.size() - 1);
        } else {
            versions.set(versions.size() - 1, lastMinor);
        }
        unreleasedVersions.add(lastVersion);
        return lastVersion;
    }

    private static final List RELEASED_VERSIONS;
    private static final List UNRELEASED_VERSIONS;
    private static final List ALL_VERSIONS;
    private static final List ALL_OPENSEARCH_VERSIONS;
    private static final List ALL_LEGACY_VERSIONS;

    static {
        Tuple, List> versions = resolveReleasedVersions(Version.CURRENT, LegacyESVersion.class);
        RELEASED_VERSIONS = versions.v1();
        UNRELEASED_VERSIONS = versions.v2();
        List allVersions = new ArrayList<>(RELEASED_VERSIONS.size() + UNRELEASED_VERSIONS.size());
        allVersions.addAll(RELEASED_VERSIONS);
        allVersions.addAll(UNRELEASED_VERSIONS);
        Collections.sort(allVersions);
        ALL_VERSIONS = Collections.unmodifiableList(allVersions);
        // @todo remove this when legacy support is no longer needed
        ALL_OPENSEARCH_VERSIONS = ALL_VERSIONS.stream().filter(v -> v.major < 6).collect(Collectors.toList());
        ALL_LEGACY_VERSIONS = ALL_VERSIONS.stream().filter(v -> v.major >= 6).collect(Collectors.toList());
    }

    /**
     * Returns an immutable, sorted list containing all released versions.
     */
    public static List allReleasedVersions() {
        return RELEASED_VERSIONS;
    }

    /**
     * Returns an immutable, sorted list containing all unreleased versions.
     */
    public static List allUnreleasedVersions() {
        return UNRELEASED_VERSIONS;
    }

    /**
     * Returns an immutable, sorted list containing all versions, both released and unreleased.
     */
    public static List allVersions() {
        return ALL_VERSIONS;
    }

    /** Returns an immutable, sorted list containing all opensearch versions; released and unreleased */
    public static List allOpenSearchVersions() {
        return ALL_OPENSEARCH_VERSIONS;
    }

    /** Returns an immutable, sorted list containing all legacy versions; released and unreleased */
    public static List allLegacyVersions() {
        return ALL_LEGACY_VERSIONS;
    }

    /**
     * Get the released version before {@code version}.
     */
    public static Version getPreviousVersion(Version version) {
        for (int i = RELEASED_VERSIONS.size() - 1; i >= 0; i--) {
            Version v = RELEASED_VERSIONS.get(i);
            if (v.before(version)) {
                return v;
            }
        }
        throw new IllegalArgumentException("couldn't find any released versions before [" + version + "]");
    }

    /**
     * Get the released version before {@link Version#CURRENT}.
     */
    public static Version getPreviousVersion() {
        Version version = getPreviousVersion(Version.CURRENT);
        assert version.before(Version.CURRENT);
        return version;
    }

    /**
     * Returns the released {@link Version} before the {@link Version#CURRENT}
     * where the minor version is less than the currents minor version.
     */
    public static Version getPreviousMinorVersion() {
        for (int i = RELEASED_VERSIONS.size() - 1; i >= 0; i--) {
            Version v = RELEASED_VERSIONS.get(i);
            if (v.minor < Version.CURRENT.minor || v.major < Version.CURRENT.major) {
                return v;
            }
        }
        throw new IllegalArgumentException("couldn't find any released versions of the minor before [" + Version.CURRENT + "]");
    }

    /** Returns the oldest released {@link Version} */
    public static Version getFirstVersion() {
        return RELEASED_VERSIONS.get(0);
    }

    public static Version getFirstVersionOfMajor(List versions, int major) {
        Map> majorVersions = versions.stream().collect(Collectors.groupingBy(v -> (int) v.major));
        return majorVersions.get(major).get(0);
    }

    /** Returns a random {@link Version} from all available versions. */
    public static Version randomVersion(Random random) {
        return ALL_VERSIONS.get(random.nextInt(ALL_VERSIONS.size()));
    }

    /**
     * Return a random {@link Version} from all available opensearch versions.
     **/
    public static Version randomOpenSearchVersion(Random random) {
        return ALL_OPENSEARCH_VERSIONS.get(random.nextInt(ALL_OPENSEARCH_VERSIONS.size()));
    }

    /**
     * Return a random {@link LegacyESVersion} from all available legacy versions.
     **/
    public static LegacyESVersion randomLegacyVersion(Random random) {
        return (LegacyESVersion) ALL_LEGACY_VERSIONS.get(random.nextInt(ALL_LEGACY_VERSIONS.size()));
    }

    /** Returns the first released (e.g., patch version 0) {@link Version} of the last minor from the requested major version
     *  e.g., for version 1.0.0 this would be legacy version (7.10.0); the first release (patch 0), of the last
     *  minor (for 7.x that is minor version 10) for the desired major version (7)
     **/
    public static Version lastFirstReleasedMinorFromMajor(List allVersions, int major) {
        Map> majorVersions = allVersions.stream().collect(Collectors.groupingBy(v -> (int) v.major));
        Map> groupedByMinor = majorVersions.get(major).stream().collect(Collectors.groupingBy(v -> (int) v.minor));
        List candidates = Collections.max(groupedByMinor.entrySet(), Comparator.comparing(Map.Entry::getKey)).getValue();
        return candidates.get(0);
    }

    /** Returns a random {@link Version} from all available versions, that is compatible with the given version. */
    public static Version randomCompatibleVersion(Random random, Version version) {
        final List compatible = ALL_VERSIONS.stream().filter(version::isCompatible).collect(Collectors.toList());
        return compatible.get(random.nextInt(compatible.size()));
    }

    /** Returns a random {@link Version} between minVersion and maxVersion (inclusive). */
    public static Version randomVersionBetween(Random random, @Nullable Version minVersion, @Nullable Version maxVersion) {
        int minVersionIndex = 0;
        if (minVersion != null) {
            minVersionIndex = ALL_VERSIONS.indexOf(minVersion);
        }
        int maxVersionIndex = ALL_VERSIONS.size() - 1;
        if (maxVersion != null) {
            maxVersionIndex = ALL_VERSIONS.indexOf(maxVersion);
        }
        if (minVersionIndex == -1) {
            throw new IllegalArgumentException("minVersion [" + minVersion + "] does not exist.");
        } else if (maxVersionIndex == -1) {
            throw new IllegalArgumentException("maxVersion [" + maxVersion + "] does not exist.");
        } else if (minVersionIndex > maxVersionIndex) {
            throw new IllegalArgumentException("maxVersion [" + maxVersion + "] cannot be less than minVersion [" + minVersion + "]");
        } else {
            // minVersionIndex is inclusive so need to add 1 to this index
            int range = maxVersionIndex + 1 - minVersionIndex;
            return ALL_VERSIONS.get(minVersionIndex + random.nextInt(range));
        }
    }

    /** returns the first future incompatible version */
    public static Version incompatibleFutureVersion(Version version) {
        final Optional opt = ALL_VERSIONS.stream().filter(version::before).filter(v -> v.isCompatible(version) == false).findAny();
        assert opt.isPresent() : "no future incompatible version for " + version;
        return opt.get();
    }

    /** returns the first future compatible version */
    public static Version compatibleFutureVersion(Version version) {
        final Optional opt = ALL_VERSIONS.stream().filter(version::before).filter(v -> v.isCompatible(version)).findAny();
        assert opt.isPresent() : "no future compatible version for " + version;
        return opt.get();
    }

    /** Returns the maximum {@link Version} that is compatible with the given version. */
    public static Version maxCompatibleVersion(Version version) {
        final List compatible = ALL_VERSIONS.stream()
            .filter(version::isCompatible)
            .filter(version::onOrBefore)
            .collect(Collectors.toList());
        assert compatible.size() > 0;
        return compatible.get(compatible.size() - 1);
    }

    /**
     * Returns a random version index compatible with the current version.
     */
    public static Version randomIndexCompatibleVersion(Random random) {
        return randomVersionBetween(random, Version.CURRENT.minimumIndexCompatibilityVersion(), Version.CURRENT);
    }

    /**
     * Returns a random version index compatible with the given version, but not the given version.
     */
    public static Version randomPreviousCompatibleVersion(Random random, Version version) {
        // TODO: change this to minimumCompatibilityVersion(), but first need to remove released/unreleased
        // versions so getPreviousVerison returns the *actual* previous version. Otherwise eg 8.0.0 returns say 7.0.2 for previous,
        // but 7.2.0 for minimum compat
        return randomVersionBetween(random, version.minimumIndexCompatibilityVersion(), getPreviousVersion(version));
    }

    /**
     * Returns a {@link Version} with a given major, minor and revision version.
     * Build version is skipped for the sake of simplicity.
     */
    public static Version getVersion(byte major, byte minor, byte revision) {
        StringBuilder sb = new StringBuilder();
        sb.append(major).append('.').append(minor).append('.').append(revision);
        return Version.fromString(sb.toString());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy