// 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 com.cloud.upgrade; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import com.google.common.collect.ImmutableList; import org.apache.cloudstack.utils.CloudStackVersion; import com.cloud.upgrade.dao.DbUpgrade; /** * @since 4.12.0.0 */ public final class DatabaseVersionHierarchy { private final ImmutableList hierarchy; private DatabaseVersionHierarchy(ImmutableList hierarchy) { this.hierarchy = hierarchy; } public static DatabaseVersionHierarchyBuilder builder() { return new DatabaseVersionHierarchyBuilder(); } /** * Check if current hierarchy of Database Versions contains version. * * @param version The version to check if hierarchy contains it * * @return true if hierarchy contains the version, false if not */ public boolean contains(final CloudStackVersion version) { return toList().contains(version); } /** * Calculates an upgrade path for the passed fromVersion. If the fromVersion * doesn't exist in list of available VersionNode hierarchy, then calculation assumes that * the fromVersion required no schema migrations or data conversions and no upgrade path was * defined for it. Therefore, we find the most recent version with database migrations before the * fromVersion and adopt that upgrade path list. * * @param fromVersion The version from which the upgrade will occur * * @return The upgrade path from fromVersion to LATEST version. */ public DbUpgrade[] getPath(final CloudStackVersion fromVersion) { return getPath(fromVersion, null); } /** * Calculates an upgrade path for the passed fromVersion and toVersion. If the * fromVersion doesn't exist in list of available VersionNode hierarchy, then * calculation assumes that the fromVersion required no schema migrations or data conversions * and no upgrade path was defined for it. Therefore, we find the most recent version with database * migrations before the fromVersion and adopt that upgrade path list up to toVersion. * If toVersion is null, we're going to find the upgrade path up to the latest available version. * * @param fromVersion The version from which the upgrade will occur * @param toVersion The version up to which the upgrade will occur (can be null) * * @return The upgrade path from fromVersion to toVersion */ public DbUpgrade[] getPath(final CloudStackVersion fromVersion, final CloudStackVersion toVersion) { if (fromVersion == null) { return new DbUpgrade[0]; } // The CloudStack version is latest or higher than latest if (fromVersion.compareTo(getLatestVersion()) >= 0) { return new DbUpgrade[0]; } // we cannot find the version specified, so get the // most recent one immediately before this version if (!contains(fromVersion)) { return getPath(getRecentVersion(fromVersion), toVersion); } final Predicate predicate; if (toVersion == null) { // all the available versions greater than or equal to fromVersion predicate = node -> node.version.compareTo(fromVersion) > -1; } else { // all the available versions greater than or equal to fromVersion AND less than toVersion predicate = node -> node.version.compareTo(fromVersion) > -1 && node.version.compareTo(toVersion) < 0; } // get upgrade path from version forward (include version itself in the path) return hierarchy .stream() .filter(predicate) .filter(distinct(node -> node.upgrader.getUpgradedVersion())) .map(node -> node.upgrader) .toArray(DbUpgrade[]::new); } /** * Find the most recent CloudStackVersion immediately before fromVersion * * @param fromVersion The version to look up its immediate previous available version * * @return The CloudStackVersion or null * * @since 4.8.2.0 (refactored in 4.11.1.0) */ protected CloudStackVersion getRecentVersion(final CloudStackVersion fromVersion) { if (fromVersion == null) { return null; } // find the most recent version immediately before fromVersion return toList() .reverse() .stream() .filter(version -> fromVersion.compareTo(version) > 0) .findFirst() .orElse(null); } /** * Generate immutable list of available CloudstackVersion in the hierarchy * * @return list of available versions */ public ImmutableList toList() { List versions = hierarchy .stream() .map(node -> node.version) .collect(Collectors.toList()); return ImmutableList.copyOf(versions); } /** * Find the distinct VersionNode based on the provided getUpgradedVersion() */ private Predicate distinct(Function keyExtractor) { Map seen = new ConcurrentHashMap<>(); return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; } protected static class VersionNode { final CloudStackVersion version; final DbUpgrade upgrader; protected VersionNode(final CloudStackVersion version, final DbUpgrade upgrader) { this.version = version; this.upgrader = upgrader; } } public static final class DatabaseVersionHierarchyBuilder { private final List hierarchyBuilder = new LinkedList<>(); private DatabaseVersionHierarchyBuilder() { } public DatabaseVersionHierarchyBuilder next(final String version, final DbUpgrade upgrader) { hierarchyBuilder.add(new VersionNode(CloudStackVersion.parse(version), upgrader)); return this; } public DatabaseVersionHierarchy build() { return new DatabaseVersionHierarchy(ImmutableList.copyOf(hierarchyBuilder)); } } public CloudStackVersion getLatestVersion() { return CloudStackVersion.parse(hierarchy.get(hierarchy.size() - 1).upgrader.getUpgradedVersion()); } }