/*
* Copyright (c) 2012 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.systemservices.impl.upgrade;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.services.util.Strings;
import com.emc.storageos.coordinator.client.model.RepositoryInfo;
import com.emc.storageos.coordinator.client.model.SoftwareVersion;
public class SyncInfoBuilder {
private static final Logger log = LoggerFactory.getLogger(SyncInfoBuilder.class);
public static final int MAX_SOFTWARE_VERSIONS = 3;
/**
* Select a SoftwareVersion from the leader repository list that can be installed,
* and a list of versions that have to be removed from the local repository.
*
* @return SyncInfo - An immutable Selector object with a SoftwareVersion toInstall and
* a list of SoftwareVersions to toRemove. The toInstall might be null.
* The toRemove might be empty (but not null).
* @throws IOException
* @see SyncInfo
*/
public static SyncInfo getTargetSyncInfo(final RepositoryInfo local, final RepositoryInfo target) {
final SoftwareVersion localCurrent = (local != null) ? local.getCurrentVersion() : null;
final List<SoftwareVersion> localVersions = (local != null && local.getVersions() != null) ?
local.getVersions() : new ArrayList<SoftwareVersion>();
final SoftwareVersion targetCurrent = (target != null) ? target.getCurrentVersion() : null;
final List<SoftwareVersion> targetVersions = (target != null && target.getVersions() != null) ?
target.getVersions() : new ArrayList<SoftwareVersion>();
// Basic validations
if (localCurrent == null || localVersions == null || localVersions.isEmpty()) {
log.error("inconsistent local repository state");
return new SyncInfo();
}
if (targetCurrent == null || targetVersions == null || targetVersions.isEmpty()) {
log.error("inconsistent target repository state");
return new SyncInfo();
}
final String args = MessageFormat.format("local: [{0}/{1}] current={2} versions={3} " +
"remote: current {4} versions={5}", localVersions.size(), MAX_SOFTWARE_VERSIONS,
localCurrent, Strings.repr(localVersions), targetCurrent, Strings.repr(targetVersions));
final String prefix = "getTargetSyncInfo(): " + args + " : ";
// Special case 1 - current on remote is not same as current on local, make sure they are still upgradable
// Set force to true as we already did the version check during set_target call
if (targetCurrent != null && !targetCurrent.equals(localCurrent) && !localVersions.contains(targetCurrent)) {
try {
if (!localCurrent.isSwitchableTo(targetCurrent)) {
// we are not expecting this case - don't change anything, wait for help!
log.error("{} local current not upgradable to current on leader", prefix);
return new SyncInfo();
}
} catch (IOException e) {
log.error("Error occured when extracting version metadata from the image file", e);
}
}
// To Install - what version do the target state have that I don't have
List<SoftwareVersion> toInstallCandidates = new ArrayList<SoftwareVersion>(targetVersions);
Collections.sort(toInstallCandidates);
for (SoftwareVersion version : toInstallCandidates) {
if (localVersions.contains(version)) {
continue;
}
return new SyncInfo(version, new ArrayList<SoftwareVersion>());
}
// if we are here, we didn't find anything to install - see if we need to remove any
// To Remove - what versions do I have that the target does not have
// Set force to true as we already did the version check during set_target call
List<SoftwareVersion> toRemoveCandidates = new ArrayList<SoftwareVersion>();
try {
toRemoveCandidates = findToRemove(localVersions, localCurrent, null, null, true);
} catch (IOException e) {
log.error("Error occured when extracting version metadata from the image file", e);
}
List<SoftwareVersion> toRemove = new ArrayList<SoftwareVersion>();
for (SoftwareVersion version : toRemoveCandidates) {
if (targetVersions.contains(version)) {
continue;
}
toRemove.add(version);
return new SyncInfo(toRemove);
}
log.info(prefix + " Nothing to do.");
return new SyncInfo();
}
public static List<SoftwareVersion> getInstallableRemoteVersions(final RepositoryInfo local,
Map<SoftwareVersion, List<SoftwareVersion>> remoteVersions,
final boolean forceInstall) {
final SoftwareVersion localCurrent = (local != null) ? local.getCurrentVersion() : null;
final List<SoftwareVersion> localVersions = (local != null && local.getVersions() != null) ?
local.getVersions() : new ArrayList<SoftwareVersion>();
final String args = MessageFormat.format(
"Number of local versions/maximum software versions allowed: [{0}/{1}] current version: {2} local versions: {3} ",
localVersions.size(), MAX_SOFTWARE_VERSIONS, localCurrent, Strings.repr(localVersions));
final String prefix = "getUpgradeableRemoteVersions(): " + args + " : ";
log.info("Getting remote new versions: " + prefix);
List<SoftwareVersion> tempList = new ArrayList<SoftwareVersion>();
List<SoftwareVersion> toInstallCandidates = new ArrayList<SoftwareVersion>(remoteVersions.keySet());
Collections.sort(toInstallCandidates);
Collections.reverse(toInstallCandidates);
log.debug("Test if a version is upgradeable");
ToInstallLoop: for (SoftwareVersion toInstall : toInstallCandidates) {
log.debug(" try=" + toInstall);
// skip version lower than current version
if (!forceInstall && localCurrent.compareTo(toInstall) > 0) {
log.debug(" try=" + toInstall +
": lower than or equal to existing. Skipping.");
continue;
}
// skip local versions
for (SoftwareVersion version : localVersions) {
if (version.compareTo(toInstall) == 0) {
log.debug(" try=" + toInstall +
": already downloaded {}. Skipping.", version);
continue ToInstallLoop;
}
}
if (localCurrent.isNaturallySwitchableTo(toInstall)) {
tempList.add(toInstall);
continue;
}
for (SoftwareVersion v : remoteVersions.get(toInstall)) {
for (SoftwareVersion s : localVersions) {
if (v.weakEquals(s)) {
log.debug(" try=" + toInstall +
": can be ungraded from one of the local versions, it's upgradeable.");
tempList.add(toInstall);
continue ToInstallLoop;
}
}
}
}
return tempList;
}
/**
* Get the list of removable versions in the local repository
*
* @param local the local repository information
* @param forceRemove whether versions should be in the list if
* they can be fore removed
* @return the list of software versions that can be removed from the local repository
* @throws IOException
*/
public static SyncInfo removableVersions(final RepositoryInfo local,
final boolean forceRemove) throws IOException {
final SoftwareVersion localCurrent = (local != null) ? local.getCurrentVersion() : null;
final List<SoftwareVersion> localVersions = (local != null && local.getVersions() != null) ?
local.getVersions() : new ArrayList<SoftwareVersion>();
return new SyncInfo(findToRemove(localVersions, localCurrent, null, null, forceRemove));
}
// Method to test if a version is removable.
// The first or the last version in the list is removable.
// Version that cannot be upgraded from its previous versions is removable.
// Version that cannot upgrade to its following versions is removable.
// Version a in a list x can upgrade to a list of versions. Those versions form a list y. If any version from list y have at least one
// version other than target
// version to upgrade from, the version a is removable otherwise it's not removable
// Note that the maximum versions allowed is 4.
private static boolean isRemovable(List<SoftwareVersion> versions,
SoftwareVersion version) throws IOException {
int index = versions.indexOf(version);
int size = versions.size();
if (index == 0 || index == size - 1) {
return true; // if the tested version is the first or last version in the list it will not cause further inconsistency for sure
}
// Get lists of versions that target version can upgrade from and upgrade to
List<SoftwareVersion> upgradeFromVersionList = new ArrayList<SoftwareVersion>();
List<SoftwareVersion> upgradeToVersionList = new ArrayList<SoftwareVersion>();
for (int i = 0; i < index; i++) {
SoftwareVersion tempVersion = versions.get(i);
if (tempVersion.isSwitchableTo(version)) {
upgradeFromVersionList.add(tempVersion);
}
}
for (int i = index + 1; i < size; i++) {
SoftwareVersion tempVersion = versions.get(i);
if (version.isSwitchableTo(tempVersion)) {
upgradeToVersionList.add(tempVersion);
}
}
if (upgradeFromVersionList.isEmpty() || upgradeToVersionList.isEmpty()) {
return true; // If no version can upgrade to the target version or no version can be upgrade from the target version, target
// version is removable
}
OUTLOOP: for (SoftwareVersion v : upgradeToVersionList) {
int position = versions.indexOf(v);
for (int i = 0; i < position; i++) {
if (i != index && versions.get(i).isSwitchableTo(v))
{
continue OUTLOOP;
// If this version can be upgrade from a version other than the target continue to test the next version
}
}
return false; // If this version doesn't have any version to upgrade from, false should be returned
}
return true; // All versions from the upgradeToVersionList have versions other than the target version to upgrade from, true should
// be returned
}
public static List<SoftwareVersion> findToRemove(final List<SoftwareVersion> localVersions,
final SoftwareVersion localCurrent,
final SoftwareVersion remoteCurrent,
final SoftwareVersion toInstall,
final boolean force) throws IOException {
List<SoftwareVersion> sortedList = new ArrayList<SoftwareVersion>(localVersions);
Collections.sort(sortedList);
List<SoftwareVersion> toRemove = new ArrayList<SoftwareVersion>();
if (force || sortedList.size() > MAX_SOFTWARE_VERSIONS) {
for (SoftwareVersion v : sortedList) {
if (!v.equals(localCurrent) && !v.equals(remoteCurrent) && isRemovable(sortedList, v)) {
toRemove.add(v);
}
}
}
return toRemove;
}
}