/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.db.common;
import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.coordinator.client.model.ProductName;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.URIUtil;
import com.emc.storageos.db.client.model.DataObject;
import com.emc.storageos.db.client.model.VdcVersion;
import com.emc.storageos.db.client.model.GeoVisibleResource;
import com.emc.storageos.db.client.model.VirtualDataCenter;
import com.emc.storageos.db.client.util.KeyspaceUtil;
/**
* Utility class for determining the id of the local VDC,
* and for performing related operations
*/
public class VdcUtil {
private static final Logger log = LoggerFactory.getLogger(VdcUtil.class);
private static DbClient dbClient;
private static volatile VirtualDataCenter localVdc;
/**
* the short id of first vdc in the geo-federation
* Any object URL with a missing vdc id will be assumed to belong to this vdc
*
* If this vdc was upgraded from a pre-geo-fedration supported ViPR version,
* there will be object URL's missing the vdc id. This is the only vdc where it's
* possible to have missing vdc ids in object URL's because all other vdc's added
* to the federation are required to have no local resources.
*/
private static final String FIRST_VDC_ID = "vdc1";
/**
* Cache the short VDC id to VDC URN mapping for performance
*/
private static final Map<String, URI> vdcIdMap = new HashMap<String, URI>();
private static volatile boolean rebuildVdcIdMap = true;
private VdcUtil() {
// no instances
}
public static void setDbClient(DbClient dbclient) {
// Suppress Sonar violation of Lazy initialization of static fields should be synchronized
// only called once when spring initialization, so it's safe to ignore sonar violation
dbClient = dbclient; // NOSONAR (squid:S2444)
}
public static String getFirstVdcId() {
return FIRST_VDC_ID;
}
public static String getLocalShortVdcId() {
buildUrnMap();
if (localVdc == null) {
return FIRST_VDC_ID;
}
return localVdc.getShortId();
}
public static VirtualDataCenter getLocalVdc() {
return dbClient.queryObject(VirtualDataCenter.class, getVdcUrn(getLocalShortVdcId()));
}
/**
* Determine if an object is "remote" to this VDC, meaning it is
* geo-visible and originated in the DB on a remote VDC
*
* @param o the object to test
* @return true if the object originated remotely
*/
public static boolean isRemoteObject(DataObject o) {
if ((o instanceof GeoVisibleResource) == false) {
return false;
}
buildUrnMap();
if (localVdc == null) {
throw new IllegalStateException("No local VirtualDataCenter object found");
}
String objectVdc = URIUtil.parseVdcIdFromURI(o.getId());
objectVdc = StringUtils.isNotBlank(objectVdc) ? objectVdc : FIRST_VDC_ID;
return !localVdc.getShortId().toString().equals(objectVdc);
}
public static URI getVdcUrn(String shortVdcId) {
buildUrnMap();
if (localVdc == null) {
throw new IllegalStateException("No local VirtualDataCenter object found");
}
synchronized (vdcIdMap) {
return vdcIdMap.get(shortVdcId);
}
}
public static void invalidateVdcUrnCache() {
rebuildVdcIdMap = true;
}
public static URI getVdcId(Class<? extends DataObject> clazz, URI uri) {
if (KeyspaceUtil.isGlobal(clazz)) {
return URI.create(VdcUtil.getLocalShortVdcId());
}
String vdcFromUri = URIUtil.parseVdcIdFromURI(uri);
vdcFromUri = StringUtils.isNotBlank(vdcFromUri) ? vdcFromUri : FIRST_VDC_ID;
return URI.create(vdcFromUri);
}
/**
* if there is any active vdc which is a remote vdc, return false.
*
* @return
*/
public static boolean isLocalVdcSingleSite() {
List<URI> ids = dbClient.queryByType(VirtualDataCenter.class, true);
for (URI vdcId : ids) {
VirtualDataCenter vdc = dbClient.queryObject(VirtualDataCenter.class, vdcId);
if (!vdc.getLocal()) {
if ((vdc.getConnectionStatus() == VirtualDataCenter.ConnectionStatus.ISOLATED)
|| vdc.getRepStatus() == VirtualDataCenter.GeoReplicationStatus.REP_NONE) {
continue; // failed to add the remote vdc
}
return false;
}
}
return true;
}
private static void buildUrnMap() {
if (rebuildVdcIdMap) {
// When running unit test, prevents NPEs when creating URIs
if (dbClient == null) {
return;
}
synchronized (vdcIdMap) {
if (rebuildVdcIdMap) {
log.info("Rebuilding the vdcIdMap from the database");
List<URI> vdcIds = dbClient.queryByType(VirtualDataCenter.class, true);
Iterator<VirtualDataCenter> vdcIter = dbClient.queryIterativeObjects(VirtualDataCenter.class, vdcIds);
localVdc = null;
vdcIdMap.clear();
while (vdcIter.hasNext()) {
VirtualDataCenter vdc = vdcIter.next();
vdcIdMap.put(vdc.getShortId(), vdc.getId());
if (Boolean.TRUE.equals(vdc.getLocal())) {
localVdc = vdc;
}
}
if (!vdcIdMap.isEmpty()) {
rebuildVdcIdMap = false;
}
}
}
}
}
public static String getMinimalVdcVersion() {
List<URI> vdcIds = getVdcIds();
List<URI> geoVerIds = dbClient.queryByType(VdcVersion.class, true);
List<VdcVersion> geoVersions = dbClient.queryObject(VdcVersion.class, geoVerIds);
if (!hasAnyGeoVersion(geoVersions)) {
log.info("GeoVersion doesn't exist, return default version");
return DbConfigConstants.DEFAULT_VDC_DB_VERSION;
}
if (missVersionFor(vdcIds, geoVersions)) {
log.info("GeoVersion not exist for vdcs, return default version");
return DbConfigConstants.DEFAULT_VDC_DB_VERSION;
}
String minimalVersion = null;
for (VdcVersion geoVersion : geoVersions) {
if ((minimalVersion == null) || (VdcVersionComparator.compare(minimalVersion, geoVersion.getVersion()) > 0)) {
minimalVersion = geoVersion.getVersion();
}
}
log.info("minimal Geo version {}", minimalVersion);
return minimalVersion;
}
/**
* Check if geo version of all other vdcs(excluding local vdc) are equal to or higher than target version
*
* @param targetVersion
* @return
*/
public static boolean checkGeoCompatibleOfOtherVdcs(String targetVersion) {
URI localVdcId = getLocalVdc().getId();
List<URI> geoVerIds = dbClient.queryByType(VdcVersion.class, true);
List<VdcVersion> geoVersions = dbClient.queryObject(VdcVersion.class, geoVerIds);
for (VdcVersion geoVersion : geoVersions) {
URI vdcId = geoVersion.getVdcId();
if (vdcId.equals(localVdcId)) {
continue; // skip current vdc
}
if (VdcVersionComparator.compare(geoVersion.getVersion(), targetVersion) < 0) {
log.info("Vdc {} version is less than {}", new Object[]{vdcId, targetVersion});
return false;
}
}
return true;
}
private static boolean hasAnyGeoVersion(List<VdcVersion> geoVersions) {
return geoVersions != null && geoVersions.iterator().hasNext();
}
private static boolean missVersionFor(final List<URI> vdcIds, final List<VdcVersion> geoVersions) {
List<URI> geoVerVdcIds = new ArrayList<URI>();
for (VdcVersion geoVersion : geoVersions) {
geoVerVdcIds.add(geoVersion.getVdcId());
}
return !geoVerVdcIds.containsAll(vdcIds);
}
private static List<URI> getVdcIds() {
List<URI> vdcIds = dbClient.queryByType(VirtualDataCenter.class, true);
if (vdcIds == null || !vdcIds.iterator().hasNext()) {
return new ArrayList<URI>();
}
return vdcIds;
}
public static class VdcVersionComparator {
public static int compare(final String version1, final String version2) {
if (version1.equals(version2)) {
return 0;
}
String[] parts1 = StringUtils.split(version1, DbConfigConstants.VERSION_PART_SEPERATOR);
String[] parts2 = StringUtils.split(version2, DbConfigConstants.VERSION_PART_SEPERATOR);
int index = 0;
while (index < parts1.length && index < parts2.length) {
String part1 = parts1[index];
String part2 = parts2[index];
int result = 0;
if (StringUtils.isNumeric(part1) && StringUtils.isNumeric(part2)) {
result = (Integer.valueOf(part1).compareTo(Integer.valueOf(part2)));
} else {
result = part1.compareToIgnoreCase(part2);
}
if (result != 0) {
return result;
}
index++;
}
return parts1.length > parts2.length ? 1 : (parts1.length == parts2.length ? 0 : -1);
}
}
public static String getDbSchemaVersion(String softwareVersion) {
if (StringUtils.isBlank(softwareVersion)
|| !softwareVersion.contains(ProductName.getName())) {
log.error("Unrecognized software version {}", softwareVersion);
return null;
}
String prefix = ProductName.getName() + "-";
String versionNumber = softwareVersion.substring(prefix.length());
String numbers[] = versionNumber.split("\\.");
if (numbers.length > 2) {
return numbers[0] + "." + numbers[1];
}
log.error("Unrecognized software version {}", softwareVersion);
// Unexpected software version number
return null;
}
}