/*
* Copyright (c) 2014 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.geo.vdccontroller.impl;
import java.net.URI;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.crypto.SecretKey;
import com.emc.storageos.coordinator.client.service.DrUtil;
import com.emc.storageos.coordinator.client.service.impl.DualInetAddress;
import com.emc.storageos.geomodel.VdcNatCheckParam;
import com.emc.storageos.geomodel.VdcNatCheckResponse;
import com.emc.storageos.model.property.PropertyConstants;
import com.emc.storageos.security.authorization.BasePermissionsHelper;
import com.emc.storageos.db.common.DbConfigConstants;
import com.emc.storageos.db.common.VdcUtil;
import com.emc.storageos.security.ipsec.IPsecConfig;
import com.emc.storageos.svcs.errorhandling.resources.APIException;
import com.emc.storageos.security.geo.GeoServiceHelper;
import com.emc.storageos.security.geo.GeoServiceJob;
import com.emc.storageos.security.geo.GeoServiceJob.JobType;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.net.InetAddresses;
import com.emc.storageos.coordinator.client.model.RepositoryInfo;
import com.emc.storageos.coordinator.client.model.Site;
import com.emc.storageos.coordinator.client.model.SiteInfo;
import com.emc.storageos.coordinator.client.model.SoftwareVersion;
import com.emc.storageos.coordinator.common.Service;
import com.emc.storageos.coordinator.exceptions.InvalidSoftwareVersionException;
import com.emc.storageos.db.client.model.VdcVersion;
import com.emc.storageos.db.client.model.VirtualDataCenter;
import com.emc.storageos.db.client.model.VirtualDataCenter.ConnectionStatus;
import com.emc.storageos.db.exceptions.DatabaseException;
import com.emc.storageos.db.client.URIUtil;
import com.emc.storageos.security.geo.exceptions.GeoException;
import com.emc.storageos.geo.service.impl.util.VdcConfigHelper;
import com.emc.storageos.geomodel.VdcCertListParam;
import com.emc.storageos.geomodel.VdcConfig;
import com.emc.storageos.geomodel.VdcConfigSyncParam;
import com.emc.storageos.geomodel.VdcNodeCheckParam;
import com.emc.storageos.geomodel.VdcNodeCheckResponse;
import com.emc.storageos.geomodel.VdcPostCheckParam;
import com.emc.storageos.geomodel.VdcPreCheckResponse;
import com.emc.storageos.security.authentication.InternalApiSignatureKeyGenerator;
import com.emc.storageos.security.authentication.InternalApiSignatureKeyGenerator.SignatureKeyType;
import com.emc.storageos.security.geo.GeoClientCacheManager;
import com.emc.storageos.security.geo.GeoServiceClient;
/*
* Detail implementation of vdc connect operation
*/
public class ConnectVdcTaskOp extends AbstractVdcTaskOp {
private final static Logger log = LoggerFactory.getLogger(ConnectVdcTaskOp.class);
private final static int NODE_CHECK_TIMEOUT = 60 * 1000; // one minute
private final static SoftwareVersion netcheckMinVer = new SoftwareVersion("2.2.0.0.*");
private InternalApiSignatureKeyGenerator apiSignatureGenerator;
public ConnectVdcTaskOp(InternalDbClient dbClient, GeoClientCacheManager geoClientCache,
VdcConfigHelper helper, Service serviceInfo, VirtualDataCenter vdc, String taskId,
Properties vdcInfo, InternalApiSignatureKeyGenerator generator, KeyStore keystore, IPsecConfig ipsecConfig) {
super(dbClient, geoClientCache, helper, serviceInfo, vdc, taskId, vdcInfo, keystore, ipsecConfig);
this.apiSignatureGenerator = generator;
this.operatedVdc = dbClient.queryObject(VirtualDataCenter.class,
URI.create(vdcInfo.getProperty(GeoServiceJob.OPERATED_VDC_ID)));
if (operatedVdc == null) {
this.operatedVdcStatus = ConnectionStatus.CONNECTING;
}
else {
this.operatedVdcStatus = operatedVdc.getConnectionStatus();
}
}
private BasePermissionsHelper _permissionHelper;
public void setBasePermissionHelper(BasePermissionsHelper _permissionHelper) {
this._permissionHelper = _permissionHelper;
}
/**
* Precheck if vdc connect is permitted, then sync the new vdc config to all sites
*/
private void checkAndSync(InternalApiSignatureKeyGenerator apiSignatureGenerator, KeyStore keystore) {
String shortId = vdcInfo.getProperty(GeoServiceJob.VDC_SHORT_ID);
String vdcName = vdcInfo.getProperty(GeoServiceJob.VDC_NAME);
lockHelper.acquire(shortId);
log.info("Acquired global lock, go on with connect vdc");
geoClientCache.clearCache();
loadVdcInfo();
// Check & verify connection status of my current vdc
preSteps();
// Have the certificate for the to be added vdc
persistVdcCert(vdcName, vdcInfo.getProperty(GeoServiceJob.VDC_CERTIFICATE_CHAIN), true, shortId);
// precheck
VdcPreCheckResponse operatedVdcInfo = preCheck();
// remove root's Tenant Roles or project ownerships in local vdc
try {
_permissionHelper.removeRootRoleAssignmentOnTenantAndProject();
} catch (DatabaseException dbe) {
throw GeoException.fatals.connectVdcRemoveRootRolesFailed(dbe);
}
String currentVdcIpsecKey = ipsecConfig.getPreSharedKeyFromZK();
URI newVdcId = URIUtil.uri(vdcInfo.getProperty(GeoServiceJob.OPERATED_VDC_ID));
GeoServiceHelper.backupOperationVdc(dbClient, JobType.VDC_CONNECT_JOB, newVdcId, null);
VirtualDataCenter newVdc = GeoServiceHelper.prepareVirtualDataCenter(newVdcId, VirtualDataCenter.ConnectionStatus.CONNECTING,
VirtualDataCenter.GeoReplicationStatus.REP_NONE, vdcInfo);
dbClient.createObject(newVdc);
helper.createVdcConfigInZk(mergeVdcInfo(operatedVdcInfo), currentVdcIpsecKey);
// we should use uuid as cert name in trust store, but before we persist new vdc info
// into db, we use vdc name as cert name, after we persist new vdc into db, persist uuid
// as cert name and remove the one which use vdc name as cert name.
persistVdcCert(newVdc.getId().toString(), newVdc.getCertificateChain(), true, shortId);
removeVdcCert(vdcName, shortId);
// add new remote VDC to the list of VDC to sync
toBeSyncedVdc.add(newVdc);
allVdc.add(newVdc);
connectedVdc.add(newVdc);
VdcUtil.invalidateVdcUrnCache();
// Now set "operatedVdc as the newly created VDC
operatedVdc = newVdc;
// generate the cert chain to be synced
VdcCertListParam certListParam = genCertListParam(VdcCertListParam.CMD_ADD_CERT);
// from now on, vdc status will be marked as CONNECT_FAILED for any failure
failedVdcStatus = ConnectionStatus.CONNECT_FAILED;
// sync the new certificate to all connected sites
syncCerts(VdcCertListParam.CMD_ADD_CERT, certListParam);
VdcConfigSyncParam mergedVdcInfo = configMerge(operatedVdcInfo, currentVdcIpsecKey);
if (mergedVdcInfo == null) {
log.error("merge the vdc config of all sites failed");
throw GeoException.fatals.mergeConfigFail();
}
// from this point on, any errors will not be retryable and requires manual
// recovery
try {
configSync(mergedVdcInfo);
} catch (GeoException ex) {
throw ex;
} catch (Exception e) {
log.error("Failed to sync vdc config to all sites e=", e);
throw GeoException.fatals.syncConfigFail(e);
}
// do not release the global lock here; lock is released during post processing
}
private void removeVdcCert(String vdcName, String shortId) {
try {
// remove the operated vdc cert with vdcName
keystore.deleteEntry(vdcName);
} catch (KeyStoreException e) {
log.error("failed to delete the cert of {}.", vdcName);
throw GeoException.fatals.connectVdcSyncCertFail(shortId, e);
}
}
private void persistVdcCert(String vdcName, String certStr, boolean certchain, String shortId) {
try {
// add the target vdc cert into trust-store of local vdc
helper.persistVdcCert(keystore, vdcName, certStr, certchain);
} catch (APIException e) {
log.error("failed to persist the new cert of {}.", vdcName);
throw GeoException.fatals.connectVdcSyncCertFail(shortId, e);
}
}
/**
* PreSteps to be executed before initial connect vdc operation
*/
private void preSteps() {
// Check & verify connection status of my current vdc
ConnectionStatus status = myVdc.getConnectionStatus();
switch (status) {
case ISOLATED:
// current status is isolated, not connected with others, generate the inter-vdc secure key of my current vdc
SecretKey key = apiSignatureGenerator.getSignatureKey(SignatureKeyType.INTERVDC_API);
myVdc.setSecretKey(new String(Base64.encodeBase64(key.getEncoded()), Charset.forName("UTF-8")));
dbClient.updateAndReindexObject(myVdc);
break;
case CONNECTED:
// at least 2 connected record
// getAllConnectedVdc
if (getAllVdc().size() < 2) {
String errMsg = "incorrect connected vdc count";
log.error(errMsg);
throw GeoException.fatals.connectVdcPrecheckFail(myVdcId, errMsg);
}
// verify my secure key still valid
break;
default:
String errMsg = "Unexpected local vdc connection status";
log.error(errMsg);
throw GeoException.fatals.connectVdcPrecheckFail(myVdcId, errMsg);
}
}
/**
* The vdc config has been synced to all sites, post check if all sites connected as expected
*/
private void postSteps() {
// from now on, vdc status will be marked as CONNECT_FAILED for any failure
failedVdcStatus = ConnectionStatus.CONNECT_FAILED;
geoClientCache.clearCache();
loadVdcInfo();
try {
postCheck();
} catch (Exception e) {
log.error("wait for all sites db stable failed e=", e);
throw GeoException.fatals.connectVdcPostCheckFail(e);
}
statusUpdate();
// release the lock
// lock is released in error handling code if an exception is thrown before we get here
lockHelper.release(operatedVdc.getShortId());
}
/**
* Check whether geo could accept the new vdc or not
*/
private VdcPreCheckResponse preCheck() {
log.info("Starting precheck on vdc connect ...");
// Step 0: Get remote vdc version before send preCheck, since we modify the preCheckParam
// avoid to send preCheck from v2.3 or higher to v2.2 v2.1, v2.0
if (!isRemoteVdcVersionCompatible(vdcInfo)) {
throw GeoException.fatals.connectVdcPrecheckFail(myVdcId, "Software version from remote vdc is lower than v2.3.");
}
log.info("Send vdc precheck to remote vdc");
// step 1: 2 way communication to verify if link should be permitted
VdcPreCheckResponse vdcResp = sendVdcPrecheckRequest(vdcInfo, true);
log.info("Check VIP of remote vdc is used as the ApiEndpoint");
// verify if node IP address is used as the ApiEndpoint
String virtualIP = vdcInfo.getProperty(GeoServiceJob.VDC_API_ENDPOINT);
if (!InetAddresses.isInetAddress(virtualIP)) {
// FQDN used
log.info("FQDN or hostname used: {}", virtualIP);
try {
virtualIP = InetAddress.getByName(vdcInfo.getProperty(GeoServiceJob.VDC_API_ENDPOINT)).getHostAddress();
vdcInfo.setProperty(GeoServiceJob.VDC_API_ENDPOINT, virtualIP); // replace with real IP
log.info("virtual ip of new vdc {}", virtualIP);
} catch (UnknownHostException e) {
throw GeoException.fatals.invalidFQDNEndPoint(vdcInfo.getProperty(GeoServiceJob.VDC_NAME), virtualIP);
}
}
if (vdcResp.getHostIPv4AddressesMap().containsValue(virtualIP) || vdcResp.getHostIPv6AddressesMap().containsValue(virtualIP)) {
throw GeoException.fatals.wrongIPSpecification(vdcInfo.getProperty(GeoServiceJob.VDC_NAME));
}
log.info("Check vdc stable");
// check if the cluster is stable
if (!vdcResp.isClusterStable()) {
throw GeoException.fatals.unstableVdcFailure(vdcInfo.getProperty(GeoServiceJob.VDC_NAME));
}
URI unstable = checkAllVdcStable(false, false);
if (unstable != null) {
VirtualDataCenter vdc = dbClient.queryObject(VirtualDataCenter.class, unstable);
String vdcName = (vdc != null) ? vdc.getLabel() : "";
throw GeoException.fatals.unstableVdcFailure(vdcName);
}
log.info(
"vdc config retrieved: vip={}, IPv4Addresses={}, IPv6Addresses={} isHasData={}",
new Object[] { vdcResp.getApiEndpoint(), vdcResp.getHostIPv4AddressesMap(), vdcResp.getHostIPv6AddressesMap(),
vdcResp.isHasData() });
if (vdcResp.isHasData()) {
throw GeoException.fatals.remoteVDCContainData();
}
// verify the software version compatibility
if (!isGeoCompatible(vdcResp)) {
throw GeoException.fatals.remoteVDCInLowerVersion();
}
if (hasTripleVdcVersionsInFederation(vdcResp)) {
throw GeoException.fatals.hasTripleVDCVersionsInFederation();
}
if (!isCompatibleVersion(vdcResp)) {
throw GeoException.fatals.remoteVDCIncompatibleVersion();
}
if (!checkNodeConnectivity(vdcResp)) {
throw GeoException.fatals.failedToCheckConnectivity(errMsg);
}
return vdcResp;
}
private String checkNetworkTopology(VdcPreCheckResponse vdcBeingAdded) {
SoftwareVersion remoteVer = new SoftwareVersion(vdcBeingAdded.getSoftwareVersion());
if (remoteVer.compareTo(netcheckMinVer) >= 0) {
String nodeId = this.dbClient.getCoordinatorClient().getPropertyInfo().getProperty("node_id");
log.info("Retrieving IP addresses of local node: {}, and let remote VDC {} check if we're behind a NAT",
nodeId, vdcBeingAdded.getShortId());
DualInetAddress inetAddress = this.dbClient.getCoordinatorClient().getInetAddessLookupMap().getDualInetAddress();
String ipv4 = inetAddress.getInet4();
String ipv6 = inetAddress.getInet6();
log.info("Got local node's IP addresses, IPv4 = {}, IPv6 = {}", ipv4, ipv6);
VdcNatCheckParam checkParam = new VdcNatCheckParam();
checkParam.setIPv4Address(ipv4);
checkParam.setIPv6Address(ipv6);
VdcNatCheckResponse resp = geoClientCache.getGeoClient(vdcInfo).vdcNatCheck(checkParam);
if (resp.isBehindNAT()) {
return String
.format("The remote VDC %s seen this node's IP is %s, which is different from what we think: %s or %s, we may behind a NAT",
vdcBeingAdded.getShortId(), resp.getSeenIp(), ipv4, ipv6);
}
} else {
log.info("Remote VDC is of version {}, lower than {}, NAT check skipped.", remoteVer, netcheckMinVer);
}
return null;
}
private boolean checkNodeConnectivity(VdcPreCheckResponse vdcBeingAdded) {
// check to make sure the current vdc can connect to all nodes on the vdc being added
if (!helper.areNodesReachable(vdcBeingAdded.getShortId(), vdcBeingAdded.getHostIPv4AddressesMap(),
vdcBeingAdded.getHostIPv6AddressesMap(), false)) {
errMsg = String.format("Current vdc, %s, cannot reach all nodes of vdc being added", myVdc.getLabel());
log.error(errMsg);
return false;
}
// check to make sure the vdc being added can connect to the current and all other vdc's
log.info("sending node check request to vdc: {}", vdcInfo.getProperty(GeoServiceJob.OPERATED_VDC_ID));
VdcNodeCheckResponse vdcResp = sendVdcNodeCheckRequest(vdcInfo, connectedVdc);
if (!vdcResp.isNodesReachable()) {
errMsg = "vdc to be added cannot connect to all nodes of at least one of the vdcs in the federation";
log.error(errMsg);
return false;
}
// check to make sure the vdc being added can connect to the current and all other vdc's
List<VdcConfig> vdcConfigs = new ArrayList<VdcConfig>();
vdcConfigs.add(mergeVdcInfo(vdcBeingAdded));
for (VirtualDataCenter other : connectedVdc) {
if (other.getShortId().equals(myVdc.getShortId())) {
continue;
}
log.info("sending node check request to vdc: {}", other.getShortId());
vdcResp = sendVdcNodeCheckRequest(GeoServiceHelper.getVDCInfo(other), vdcConfigs);
if (!vdcResp.isNodesReachable()) {
errMsg = String.format("vdc, %s, cannot connect to all nodes of the vdc being added", other.getLabel());
log.error(errMsg);
return false;
}
}
errMsg = checkNetworkTopology(vdcBeingAdded);
if (errMsg != null && errMsg.length() > 0) {
return false;
}
return true;
}
/**
* Send node check request to target vdc.
*
* @param vdcProp vdc to send msg to
* @param vdcsToCheck list of vdc's with nodes to check
* @return
* @throws Exception
*/
private VdcNodeCheckResponse sendVdcNodeCheckRequest(Properties vdcProp, Collection<VirtualDataCenter> vdcsToCheck) {
List<VdcConfig> virtualDataCenters = new ArrayList<VdcConfig>();
for (VirtualDataCenter vdc : vdcsToCheck) {
VdcConfig vdcConfig = new VdcConfig();
Site activeSite = drUtil.getActiveSite(vdc.getShortId());
vdcConfig.setId(vdc.getId());
vdcConfig.setShortId(vdc.getShortId());
if (activeSite.getHostIPv4AddressMap() != null && !activeSite.getHostIPv4AddressMap().isEmpty() && activeSite.isUsingIpv4()) {
HashMap<String, String> ipv4AddrMap = new HashMap<String, String>(activeSite.getHostIPv4AddressMap());
vdcConfig.setHostIPv4AddressesMap(ipv4AddrMap);
} else if (activeSite.getHostIPv6AddressMap() != null && !activeSite.getHostIPv6AddressMap().isEmpty()) {
HashMap<String, String> ipv6AddrMap = new HashMap<String, String>(activeSite.getHostIPv6AddressMap());
vdcConfig.setHostIPv6AddressesMap(ipv6AddrMap);
} else {
throw GeoException.fatals
.cannotPerformOperation(vdc.getId().toString(), " no nodes were found on VirtualDataCenter object");
}
virtualDataCenters.add(vdcConfig);
}
return sendVdcNodeCheckRequest(vdcProp, virtualDataCenters);
}
/**
* @param remoteVdcInfo
* @param virtualDataCenters
* @return
*/
private VdcNodeCheckResponse sendVdcNodeCheckRequest(Properties remoteVdcInfo, List<VdcConfig> virtualDataCenters) {
log.info("sending {} vdcs to {} to be checked", virtualDataCenters.size(), remoteVdcInfo.getProperty(GeoServiceJob.VDC_SHORT_ID));
VdcNodeCheckParam param = new VdcNodeCheckParam();
param.setVirtualDataCenters(virtualDataCenters);
try {
GeoServiceClient client = helper.resetGeoClientCacheTimeout(null, remoteVdcInfo, NODE_CHECK_TIMEOUT);
return client.vdcNodeCheck(param);
} finally {
// clear the client cache to reset timeouts that were altered above
geoClientCache.clearCache();
}
}
private VdcConfigSyncParam configMerge(VdcPreCheckResponse operatedVdcInfo, String ipsecKey) {
// step 2: merge the vdc config info of all sites, as the initiator, we should has all current vdc config info
VdcConfigSyncParam vdcConfigList = new VdcConfigSyncParam();
vdcConfigList.setVdcConfigVersion(DrUtil.newVdcConfigVersion());
List<VdcConfig> list = vdcConfigList.getVirtualDataCenters();
for (VirtualDataCenter vdc : getAllVdc()) {
if (vdc.getShortId().equals(vdcInfo.getProperty(GeoServiceJob.VDC_SHORT_ID))) {
continue;
}
log.info("add {} to the merged vdc config", vdc.getShortId());
VdcConfig vdcConfig = helper.toConfigParam(vdc);
list.add(vdcConfig);
}
VdcConfig operatedConfig = mergeVdcInfo(operatedVdcInfo);
list.add(operatedConfig);
vdcConfigList.setIpsecKey(ipsecKey);
return vdcConfigList;
}
private void configSync(VdcConfigSyncParam mergedVdcInfo) throws Exception {
// step 3: sync merged vdc config info to all sites in which property change triggered, all conf files updated after reboot.
// the vdc to be connected will reset the db and update the network strategy when startup.
// loop all current connected VDCs with latest vdc config info, shall be moved into geoclient
// geoclient shall responsible to retry all retryable errors, we have no need retry here
log.info("sync vdc config to all sites, total vdc entries {}", mergedVdcInfo.getVirtualDataCenters().size());
List<VirtualDataCenter> vdcList = getToBeSyncedVdc();
for (VirtualDataCenter vdc : vdcList) {
log.info("Loop vdc {}:{} to sync the latest vdc config info", vdc.getShortId(), vdc.getApiEndpoint());
if (vdc.getApiEndpoint() != null) {
URI vdcId = operatedVdc.getId();
if (vdc.getId().equals(vdcId)) { // vdc UUID assigned
mergedVdcInfo.setAssignedVdcId(vdcId.toString());
mergedVdcInfo.setGeoEncryptionKey(helper.getGeoEncryptionKey());
} else {
mergedVdcInfo.setAssignedVdcId(null);
mergedVdcInfo.setGeoEncryptionKey(null);
}
mergedVdcInfo.setConfigChangeType(changeType().toString());
geoClientCache.getGeoClient(vdc.getShortId()).syncVdcConfig(mergedVdcInfo, vdc.getLabel());
log.info("Sync vdc info succeed");
} else {
log.error("Fatal error: try to sync with a vdc without endpoint");
}
}
helper.addVdcToCassandraStrategyOptions(mergedVdcInfo.getVirtualDataCenters(), operatedVdc, false);
// notify local vdc to apply the new vdc config info
helper.syncVdcConfig(mergedVdcInfo.getVirtualDataCenters(), null,
mergedVdcInfo.getVdcConfigVersion(), mergedVdcInfo.getIpsecKey());
// update the current progress of connect vdc. the cluster would reboot later.
updateOpStatus(ConnectionStatus.CONNECTING_SYNCED);
helper.triggerVdcConfigUpdate(mergedVdcInfo.getVdcConfigVersion(), SiteInfo.GEO_OP_CONFIG_CHANGE);
}
private void postCheck() {
log.info("vdc config info already synced to all connected vdc, start post check");
if (myVdc == null) {
getMyVdcId();
}
// step 4: wait for the gossip status
dbClient.waitAllSitesDbStable();
// reload operated vdc from db
operatedVdc = dbClient.queryObject(VirtualDataCenter.class, operatedVdc.getId());
Site activeSite = drUtil.getActiveSite(operatedVdc.getShortId());
// check if network strategy updated successfully
dbClient.waitDbRingRebuildDone(operatedVdc.getShortId(), activeSite.getNodeCount());
}
private void statusUpdate() {
// step 5: update vdc connection status if post check succeed at each site
List<VirtualDataCenter> vdcList = getToBeSyncedVdc();
// fill the check param
VdcPostCheckParam checkParam = new VdcPostCheckParam();
checkParam.setConfigChangeType(changeType().toString());
checkParam.setRootTenantId(helper.getVdcRootTenantId());
// all connected vdc
List<URI> vdcIds = new ArrayList<>();
for (VirtualDataCenter vdc : vdcList) {
vdcIds.add(vdc.getId());
}
vdcIds.add(myVdc.getId());
checkParam.setVdcList(vdcIds);
log.info("status update to {}", checkParam.getVdcList());
sendPostCheckMsg(vdcList, checkParam);
}
private VdcConfig mergeVdcInfo(VdcPreCheckResponse vdcResp) {
log.info("add to be added vdc {} to the merged vdc config", vdcInfo.getProperty(GeoServiceJob.VDC_SHORT_ID));
VdcConfig vdcConfig = helper.toConfigParam(vdcInfo);
// Following items should be set according to remote site
log.info("get from remote {} {}", vdcResp.getHostCount(), vdcResp.getShortId());
vdcConfig.setHostCount(vdcResp.getHostCount());
vdcConfig.setHostIPv4AddressesMap(vdcResp.getHostIPv4AddressesMap());
vdcConfig.setHostIPv6AddressesMap(vdcResp.getHostIPv6AddressesMap());
if (operatedVdc == null) {
vdcConfig.setConnectionStatus(VirtualDataCenter.ConnectionStatus.CONNECTING.toString());
vdcConfig.setRepStatus(VirtualDataCenter.GeoReplicationStatus.REP_NONE.toString());
}
else {
vdcConfig.setConnectionStatus(operatedVdc.getConnectionStatus().toString());
vdcConfig.setRepStatus(operatedVdc.getRepStatus().toString());
}
Date addDate = new Date();
vdcConfig.setVersion(addDate.getTime()); // notify the vdc to pick up the latest info
vdcConfig.setActiveSiteId(vdcResp.getActiveSiteId());
return vdcConfig;
}
/**
* Check to be added vdc whether in compatible version
*/
private boolean isCompatibleVersion(VdcPreCheckResponse vdcResp) {
final SoftwareVersion version;
String shortId = vdcInfo.getProperty(GeoServiceJob.VDC_SHORT_ID);
try {
log.info("software version of vdc {} is {}", shortId, vdcResp.getSoftwareVersion());
version = new SoftwareVersion(vdcResp.getSoftwareVersion());
} catch (InvalidSoftwareVersionException e) {
log.error("software version of vdc {} is incorrect", shortId);
return false;
}
SoftwareVersion myVersion = null;
try {
myVersion = dbClient.getCoordinatorClient().getTargetInfo(RepositoryInfo.class).getCurrentVersion();
} catch (Exception e) {
String errMsg = "Not able to get the software version of current vdc";
log.error(errMsg, e);
throw GeoException.fatals.connectVdcPrecheckFail(shortId, errMsg);
}
log.info("Software version of current vdc: {}", myVersion.toString());
if (myVersion.compareTo(version) == 0) {
log.info("software version equals, pass the version check");
} else if (myVersion.compareTo(version) < 0) {
log.info("to be added vdc has larger version");
if (!vdcResp.getCompatible()) {
log.error("vdc to be added has larger version but incompatible with current vdc");
return false;
}
} else {
log.info("to be added vdc has smaller version");
if (!helper.isCompatibleVersion(version)) {
log.error("vdc to be added has smaller version but incompatible with current vdc");
return false;
}
}
return true;
}
private boolean isGeoCompatible(VdcPreCheckResponse vdcResp) {
String expectVersion = VdcUtil.getDbSchemaVersion(vdcResp.getSoftwareVersion());
if (expectVersion == null) {
return false;
}
log.info("Compare Vdc version: {}", expectVersion);
String minimalVdcVersion = VdcUtil.getMinimalVdcVersion();
boolean isGeoCompatible = VdcUtil.VdcVersionComparator.compare(expectVersion, minimalVdcVersion) >= 0;
if (!isGeoCompatible) {
log.error("The vdc version of the vdc to be added must be not less than current federation.");
}
return isGeoCompatible;
}
/**
* Check if we'll have 3 vdc versions after adding the vdc with given version
*
* @param vdcResp
* @return true if there are 3 vdc versions
*/
private boolean hasTripleVdcVersionsInFederation(VdcPreCheckResponse vdcResp) {
Set<String> allVersions = new HashSet<>();
allVersions.add(VdcUtil.getDbSchemaVersion(vdcResp.getSoftwareVersion()));
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 vdcVersion : vdcVersions) {
vdcIdVdcVersionMap.put(vdcVersion.getVdcId(), vdcVersion);
}
for (URI vdcId : vdcIds) {
if (vdcIdVdcVersionMap.containsKey(vdcId)) {
String schemaVersion = vdcIdVdcVersionMap.get(vdcId)
.getVersion();
log.info("Get vdc version {} on {}", schemaVersion, vdcId);
allVersions.add(schemaVersion);
} else {
log.info(
"Can not get vdc version on {}, will use default version instead",
vdcId);
allVersions.add(DbConfigConstants.DEFAULT_VDC_DB_VERSION);
}
}
log.info("Current vdc versions in federation {}", allVersions);
boolean hasTriple = allVersions.size() > 2;
if (hasTriple) {
log.error("Not allowed to have three different vdc versions in a federation.");
}
return hasTriple;
}
@Override
protected void process() {
String errMsg;
log.info("The operatedVdcStatus={}", operatedVdcStatus);
switch (operatedVdcStatus) {
case CONNECT_FAILED:
errMsg = String.format("Adding vdc operation failed already on %s, skip all other steps", operatedVdc.getId());
log.error(errMsg);
throw GeoException.fatals.connectVdcInvalidStatus(errMsg);
case CONNECTING:
checkAndSync(apiSignatureGenerator, keystore);
case CONNECTING_SYNCED:
// vdc config info already synced to all connected vdc, check if connect succeed
postSteps();
break;
default:
errMsg = "Vdc to be added in unexpected status, skip all other steps";
log.error(errMsg);
log.info("target vdc status: {}", operatedVdcStatus);
throw GeoException.fatals.connectVdcInvalidStatus(errMsg);
}
}
@Override
public VdcConfig.ConfigChangeType changeType() {
return VdcConfig.ConfigChangeType.CONNECT_VDC;
}
}