/*
* Copyright (c) 2014 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.security.geo;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.emc.storageos.security.geo.exceptions.GeoException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.emc.storageos.db.client.model.VdcVersion;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.impl.DbClientImpl;
import com.emc.storageos.db.client.impl.GlobalLockImpl;
import com.emc.storageos.db.client.model.GlobalLock;
import com.emc.storageos.db.client.model.VirtualDataCenter;
import com.emc.storageos.db.common.DbConfigConstants;
import com.emc.storageos.db.common.VdcUtil;
import com.emc.storageos.security.upgradevoter.UpgradeVoter;
/**
* Upgrade voter for geosvc. Disallow upgrade in the following cases, unless it's a
* SP/patch/hotfix upgrade, in which case there's no expected db schema changes:
* 1) any vdc operation in progress
* 2) more than 2 geodb schema versions
* 3) there are any ongoing upgrades in the federation
* 4) there are any VDCs not in the CONNECTED state
*/
public class GeoUpgradeVoter implements UpgradeVoter {
private static Logger log = LoggerFactory.getLogger(GeoUpgradeVoter.class);
@Autowired
private DbClient dbClient;
@Autowired
private GeoClientCacheManager geoClientCache;
@Override
public void isOKForUpgrade(String currentVersion, String targetVersion) {
if (isCurrentVdcIsolated()) {
log.info("The current VDC is isolated, skipping all pre-checks");
return;
}
if (isMinorVersionUpgrade(currentVersion, targetVersion)) {
log.info("This is a SP/patch/hotfix upgrade, skipping further pre-checks");
return;
}
StringBuffer msg = new StringBuffer();
if (isVdcOpLockHold(msg)) {
throw GeoException.fatals.geoOperationDetected(msg.toString());
}
List<String> unstableVdcs = getUnstableVdcs();
if (unstableVdcs != null && !unstableVdcs.isEmpty()) {
throw GeoException.fatals.vdcNotStable(
unstableVdcs.toString());
}
if (hasTripleDbVersionsInFederation(targetVersion)) {
throw GeoException.fatals.versionIsNotUpgradableInGeo(
targetVersion);
}
}
/**
* Check if the current VDC is isolated.
*
* @return True if the current VDC is isolated, false otherwise.
*/
private boolean isCurrentVdcIsolated() {
VirtualDataCenter localVdc = VdcUtil.getLocalVdc();
return VirtualDataCenter.ConnectionStatus.ISOLATED.equals(
localVdc.getConnectionStatus());
}
/**
* Check if any vdc op lock is hold - it means some vdc op is in progress
*
* @return True if a vdc op lock is hold
* @throws Exception
*/
private boolean isVdcOpLockHold(StringBuffer msg) {
try {
// it doesn't matter if it's nodesvcshared or vdcshared
GlobalLockImpl glock = new GlobalLockImpl((DbClientImpl) dbClient,
GeoServiceClient.VDCOP_LOCK_NAME, GlobalLock.GL_Mode.GL_NodeSvcShared_MODE,
GeoServiceClient.VDCOP_LOCK_TIMEOUT, VdcUtil.getLocalShortVdcId());
String owner = glock.getOwner();
boolean locked = owner != null && !owner.isEmpty();
log.info("Vdc op lock is locked {}", locked);
if (locked && msg != null && !StringUtils.isEmpty(glock.getErrorMessage())) {
msg.append(glock.getErrorMessage());
}
return locked;
} catch (Exception ex) {
log.error("Unexpected exception during check vdc lock", ex);
throw GeoException.fatals.accessGlobalLockFail();
}
}
/**
* Check if we'll have 3 geodb schema version after upgrading to give version.
*
* @param targetVersion target ViPR version that current instance is going to upgrade
* @return true if there are 3 geodb schema versions
*/
private boolean hasTripleDbVersionsInFederation(String targetVersion) {
Set<String> allSchemaVersions = new HashSet<>();
allSchemaVersions.add(VdcUtil.getDbSchemaVersion(targetVersion));
List<URI> vdcIds = dbClient.queryByType(VirtualDataCenter.class, true);
List<URI> vdcVersionIds = dbClient.queryByType(VdcVersion.class, true);
List<VdcVersion> vdcVersions = dbClient.queryObject(VdcVersion.class, vdcVersionIds);
Map<URI, VdcVersion> vdcIdVdcVersionMap = new HashMap<>();
for (VdcVersion geoVersion : vdcVersions) {
vdcIdVdcVersionMap.put(geoVersion.getVdcId(), geoVersion);
}
for (URI vdcId : vdcIds) {
if (vdcIdVdcVersionMap.containsKey(vdcId)) {
String schemaVersion = vdcIdVdcVersionMap.get(vdcId).getVersion();
log.info("Get db schema version {} on {}", schemaVersion, vdcId);
allSchemaVersions.add(schemaVersion);
} else {
log.info(
"Can not get db schema version on {}, will use default version instead",
vdcId);
allSchemaVersions.add(DbConfigConstants.DEFAULT_VDC_DB_VERSION);
}
}
log.info("Current geodb schema versions in federation {}", allSchemaVersions);
return allSchemaVersions.size() > 2;
}
/**
* Check if the current upgrade is a SP/patch/hotfix upgrade.
*
* @param currentVersion
* @param targetVersion
* @return true if the current upgrade is a SP/patch/hotfix upgrade, false otherwise
*/
private boolean isMinorVersionUpgrade(String currentVersion, String targetVersion) {
String currentDbSchemaVersion = VdcUtil.getDbSchemaVersion(currentVersion);
if (currentDbSchemaVersion == null) {
return false;
}
return currentDbSchemaVersion.equals(VdcUtil.getDbSchemaVersion(targetVersion));
}
/**
* Return a list of unstable/non-connected VDCs if any.
* Throw different exceptions accordingly if such a VDC is found.
*/
private List<String> getUnstableVdcs() {
List<URI> vdcIdIter = dbClient.queryByType(VirtualDataCenter.class, true);
List<String> unstableVdcs = new ArrayList<>();
for (URI vdcId : vdcIdIter) {
VirtualDataCenter vdc = dbClient.queryObject(VirtualDataCenter.class, vdcId);
// Iterated each remote vdc to check stability
if (vdc.getRepStatus().equals(VirtualDataCenter.GeoReplicationStatus.REP_ALL)
&& !vdc.getLocal()) {
if (!VirtualDataCenter.ConnectionStatus.CONNECTED.equals(
vdc.getConnectionStatus())) {
throw GeoException.fatals.vdcNotConnected(
vdc.getShortId());
}
try {
GeoServiceClient client = geoClientCache.getGeoClient(vdc.getShortId());
if (!client.isVdcStable()) {
log.info("VDC {} is not stable", vdc.getShortId());
unstableVdcs.add(vdc.getShortId());
}
} catch (Exception ex) {
log.error("Unexpected exception during VDC stability check", ex);
throw GeoException.fatals.vdcNotReachable(
vdc.getShortId());
}
}
}
return unstableVdcs;
}
}