/* * Copyright (c) 2014 EMC Corporation * All Rights Reserved */ package com.emc.storageos.geo.vdccontroller.impl; import java.security.KeyStore; import java.security.cert.Certificate; import java.util.Date; import java.util.Iterator; import java.util.List; import java.net.URI; import java.util.Properties; import com.emc.storageos.security.geo.GeoServiceJob; import com.emc.storageos.security.ipsec.IPsecConfig; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.coordinator.common.Service; import com.emc.storageos.db.client.model.VirtualDataCenter; import com.emc.storageos.security.geo.exceptions.GeoException; import com.emc.storageos.geo.service.impl.util.VdcConfigHelper; import com.emc.storageos.geomodel.VdcConfigSyncParam; import com.emc.storageos.geomodel.VdcConfig; import com.emc.storageos.geomodel.VdcPreCheckResponse; import com.emc.storageos.geomodel.*; import com.emc.storageos.security.authentication.InternalApiSignatureKeyGenerator; import com.emc.storageos.security.geo.GeoClientCacheManager; import com.emc.storageos.security.geo.GeoServiceHelper; import static com.emc.storageos.db.client.model.VirtualDataCenter.ConnectionStatus; /* * Detail implementation of vdc update operation */ public class UpdateVdcTaskOp extends AbstractVdcTaskOp { private final static Logger log = LoggerFactory.getLogger(UpdateVdcTaskOp.class); private Properties updateInfo; List<Object> params = null; private InternalApiSignatureKeyGenerator apiSignatureKeyGenerator; public UpdateVdcTaskOp(InternalDbClient dbClient, GeoClientCacheManager geoClientCache, VdcConfigHelper helper, Service serviceInfo, VirtualDataCenter vdc, String taskId, List<Object> taskParams, InternalApiSignatureKeyGenerator generator, KeyStore keystore, IPsecConfig ipsecConfig) { super(dbClient, geoClientCache, helper, serviceInfo, vdc, taskId, null, keystore, ipsecConfig); params = taskParams; updateInfo = (Properties) taskParams.get(0); apiSignatureKeyGenerator = generator; } /** * Precheck if vdc update is permitted, then sync the vdc config to all sites to * update an existing vdc */ public void checkAndSync() { lockHelper.acquire(operatedVdc.getShortId()); geoClientCache.clearCache(); loadVdcInfo(); if (StringUtils.isNotEmpty(updateInfo.getProperty(GeoServiceJob.VDC_CERTIFICATE_CHAIN)) && (operatedVdc.getId().compareTo(myVdc.getId()) != 0)) { String errMsg = "could not update key certchain from remote VDC."; log.error(errMsg); throw GeoException.fatals.updateVdcPrecheckFail(errMsg); } VdcPreCheckResponse operatedVdcInfo = preCheck(); GeoServiceHelper.backupOperationVdc(dbClient, GeoServiceJob.JobType.VDC_UPDATE_JOB, operatedVdcInfo.getId(), params.toString()); failedVdcStatus = ConnectionStatus.UPDATE_FAILED; updateOperatedVdc(); operatedVdc.setConnectionStatus(VirtualDataCenter.ConnectionStatus.UPDATING); dbClient.updateAndReindexObject(operatedVdc); loadVdcInfo(); VdcConfigSyncParam mergedVdcInfo = mergeConfig(operatedVdcInfo); if (mergedVdcInfo == null) { log.error("merge the vdc config of all sites failed"); throw GeoException.fatals.mergeConfigFail(); } try { syncConfig(mergedVdcInfo); } catch (GeoException ex) { throw ex; } catch (Exception e) { log.error("Failed to sync vdc config to all sites : {}", e); throw GeoException.fatals.syncConfigFail(e); } String cert = updateInfo.getProperty(GeoServiceJob.VDC_CERTIFICATE_CHAIN); if (StringUtils.isNotEmpty(cert)) { VdcCertListParam certListParam = genCertOperationParam(VdcCertListParam.CMD_UPDATE_CERT); syncCerts(VdcCertListParam.CMD_UPDATE_CERT, certListParam); // set key and cert in local keystore Boolean selfsigned = (Boolean) params.get(1); byte[] key = (byte[]) params.get(2); Certificate[] certchain = (Certificate[]) params.get(3); helper.setKeyCertchain(selfsigned, key, certchain); } // lock is released in error handling code if an exception is thrown before we get // here. note that since there is no post processing for update, there is no way // to know if the sync operation is complete; lock must be released here before // sync is done. lockHelper.release(operatedVdc.getShortId()); } private VdcPreCheckResponse preCheck() { log.info("Starting precheck on vdc update ..."); // 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.updateVdcPrecheckFail("Software version from remote vdc is lower than v2.3."); } // BZ: // TODO It appears that this code assumes that update node is a remote node. // we need to modify it to make it simpler when updated node is local. log.info("Send vdc precheck to remote vdc"); VdcPreCheckResponse vdcResp = sendVdcPrecheckRequest(vdcInfo, false); log.info("Check vdc stable"); // check if the cluster is stable URI unstable = checkAllVdcStable(false, true); 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: {}, {} {}", new Object[] { vdcResp.getApiEndpoint(), vdcResp.getHostIPv4AddressesMap(), vdcResp.getHostIPv6AddressesMap() }); return vdcResp; } private VdcConfigSyncParam mergeConfig(VdcPreCheckResponse operatedVdcInfo) { // step 2: merge the vdc config info of all sites, as the initiator // we should have all current vdc config info VdcConfigSyncParam vdcConfigList = new VdcConfigSyncParam(); List<VdcConfig> list = vdcConfigList.getVirtualDataCenters(); for (VirtualDataCenter vdc : getAllVdc()) { log.info("add {} to the merged vdc config", vdc.getShortId()); VdcConfig vdcConfig = helper.toConfigParam(vdc); list.add(vdcConfig); } mergeVdcInfo(list, operatedVdc); return vdcConfigList; } private void syncConfig(VdcConfigSyncParam mergedVdcInfo) { // 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 cert info", vdc.getShortId(), vdc.getApiEndpoint()); if (vdc.getApiEndpoint() != null) { mergedVdcInfo.setAssignedVdcId(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"); } } // notify local vdc to apply the new vdc config info helper.syncVdcConfig(mergedVdcInfo.getVirtualDataCenters(), null, mergedVdcInfo.getVdcConfigVersion(), mergedVdcInfo.getIpsecKey()); } private void mergeVdcInfo(List<VdcConfig> list, VirtualDataCenter vdc) { log.info("add to be updated vdc {} to the merged vdc config", vdc.getShortId()); Iterator<VdcConfig> it = list.iterator(); while (it.hasNext()) { VdcConfig vdcSyncParam = it.next(); if (vdcSyncParam.getId().compareTo(vdc.getId()) == 0) { // update vdc // if this is a local,isolated vdc, update local vdc only, no other sites boolean isolated = isLocalIsolatedVdc(); log.info("Checking if this is a local isolated vdc->{}", isolated); if (isolated) { vdcSyncParam.setConnectionStatus(ConnectionStatus.ISOLATED.toString()); } else { vdcSyncParam.setConnectionStatus(ConnectionStatus.CONNECTED.toString()); } Date updateDate = new Date(); vdcSyncParam.setVersion(updateDate.getTime()); if ((vdc.getLabel() != null) && (!vdc.getLabel().isEmpty())) { vdcSyncParam.setName(vdc.getLabel()); } if ((vdc.getDescription() != null) && (!vdc.getDescription().isEmpty())) { vdcSyncParam.setDescription(vdc.getDescription()); } if ((vdc.getGeoCommandEndpoint() != null) && (!vdc.getGeoCommandEndpoint().isEmpty())) { vdcSyncParam.setGeoCommandEndpoint(vdc.getGeoCommandEndpoint()); } if ((vdc.getGeoDataEndpoint() != null) && (!vdc.getGeoDataEndpoint().isEmpty())) { vdcSyncParam.setGeoDataEndpoint(vdc.getGeoDataEndpoint()); } // TODO: set apiendpoint and seckey return; } } return; } @Override protected void process() { String errMsg; switch (operatedVdcStatus) { case UPDATE_FAILED: case ISOLATED: case CONNECTED: case UPDATING: checkAndSync(); break; default: errMsg = "Vdc to be updated in unexpected status, skip all other steps"; log.error(errMsg); log.info("target vdc status: {}", operatedVdcStatus); throw GeoException.fatals.updateVdcInvalidStatus(errMsg); } } /** * Verify if this is a local and isolated vdc * * @return true if status is isolated */ private boolean isLocalIsolatedVdc() { log.info("Checking if local vdc and operated vdc ids are the same ->{}.", operatedVdc.getId().equals(myVdc.getId())); return (operatedVdc.getId().equals(myVdc.getId()) && ((myVdc.getConnectionStatus() == VirtualDataCenter.ConnectionStatus.ISOLATED) || (myVdc .getRepStatus() == VirtualDataCenter.GeoReplicationStatus.REP_NONE))); } private void updateOperatedVdc() { String name = updateInfo.getProperty(GeoServiceJob.VDC_NAME); if (StringUtils.isNotEmpty(name)) { operatedVdc.setLabel(name); } String description = updateInfo.getProperty(GeoServiceJob.VDC_DESCRIPTION); if (StringUtils.isNotEmpty(description)) { operatedVdc.setDescription(description); } String geocommand = updateInfo.getProperty(GeoServiceJob.VDC_GEOCOMMAND_ENDPOINT); if (StringUtils.isNotEmpty(geocommand)) { operatedVdc.setGeoCommandEndpoint(geocommand); } String geodata = updateInfo.getProperty(GeoServiceJob.VDC_GEODATA_ENDPOINT); if (StringUtils.isNotEmpty(geodata)) { operatedVdc.setGeoDataEndpoint(geodata); } String certchain = updateInfo.getProperty(GeoServiceJob.VDC_CERTIFICATE_CHAIN); if (StringUtils.isNotEmpty(certchain)) { operatedVdc.setCertificateChain(certchain); } } @Override public VdcConfig.ConfigChangeType changeType() { return VdcConfig.ConfigChangeType.UPDATE_VDC; } }