/******************************************************************************* *Copyright (c) 2009-2014 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, only version 3 of the License. * * * This file is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see <http://www.gnu.org/licenses/>. * * Please contact Eucalyptus Systems, Inc., 130 Castilian * Dr., Goleta, CA 93101 USA or visit <http://www.eucalyptus.com/licenses/> * if you need additional information or have any questions. * * This file may incorporate work covered under the following copyright and * permission notice: * * Software License Agreement (BSD License) * * Copyright (c) 2008, Regents of the University of California * All rights reserved. * * Redistribution and use of this software in source and binary forms, with * or without modification, are permitted provided that the following * conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. USERS OF * THIS SOFTWARE ACKNOWLEDGE THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE * LICENSED MATERIAL, COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS * SOFTWARE, AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING * IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, SANTA * BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, WHICH IN * THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION, REPLACEMENT * OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO IDENTIFIED, OR * WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT NEEDED TO COMPLY WITH * ANY SUCH LICENSES OR RIGHTS. *******************************************************************************/ /* * * Author: Neil Soman neil@eucalyptus.com */ package com.eucalyptus.blockstorage.san.common; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import javax.persistence.EntityTransaction; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import com.eucalyptus.blockstorage.LogicalStorageManager; import com.eucalyptus.blockstorage.Storage; import com.eucalyptus.blockstorage.StorageManagers; import com.eucalyptus.blockstorage.StorageResource; import com.eucalyptus.blockstorage.StorageResourceWithCallback; import com.eucalyptus.blockstorage.config.StorageControllerConfiguration; import com.eucalyptus.blockstorage.entities.StorageInfo; import com.eucalyptus.blockstorage.exceptions.ConnectionInfoNotFoundException; import com.eucalyptus.blockstorage.exceptions.NoSuchRecordException; import com.eucalyptus.blockstorage.san.common.entities.SANInfo; import com.eucalyptus.blockstorage.san.common.entities.SANVolumeInfo; import com.eucalyptus.blockstorage.util.StorageProperties; import com.eucalyptus.component.Component; import com.eucalyptus.component.Components; import com.eucalyptus.component.ServiceConfiguration; import com.eucalyptus.configurable.ConfigurableClass; import com.eucalyptus.configurable.ConfigurableProperty; import com.eucalyptus.configurable.ConfigurablePropertyException; import com.eucalyptus.configurable.PropertyDirectory; import com.eucalyptus.entities.Entities; import com.eucalyptus.entities.TransactionException; import com.eucalyptus.entities.TransactionResource; import com.eucalyptus.entities.Transactions; import com.eucalyptus.storage.common.CheckerTask; import com.eucalyptus.system.BaseDirectory; import com.eucalyptus.util.EucalyptusCloudException; import com.eucalyptus.util.Exceptions; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Strings; import edu.ucsb.eucalyptus.cloud.VolumeAlreadyExistsException; import edu.ucsb.eucalyptus.msgs.ComponentProperty; public class SANManager implements LogicalStorageManager { private SANProvider connectionManager; private static SANManager singleton; private static Logger LOG = Logger.getLogger(SANManager.class); public static LogicalStorageManager getInstance() { synchronized (SANManager.class) { if (singleton == null) { singleton = new SANManager(); } } return singleton; } public SANManager() { Component sc = Components.lookup(Storage.class); if (sc == null) { throw Exceptions.toUndeclared("Cannot instantiate SANManager, no SC component found"); } ServiceConfiguration scConfig = sc.getLocalServiceConfiguration(); if (scConfig == null) { throw Exceptions.toUndeclared("Cannot instantiate SANManager without SC service configuration"); } String sanProvider = null; EntityTransaction trans = Entities.get(StorageControllerConfiguration.class); try { StorageControllerConfiguration config = Entities.uniqueResult((StorageControllerConfiguration) scConfig); sanProvider = config.getBlockStorageManager(); trans.commit(); } catch (Exception e) { throw Exceptions.toUndeclared("Cannot get backend configuration for SC."); } finally { trans.rollback(); } if (sanProvider == null) { throw Exceptions.toUndeclared("Cannot instantiate SAN Provider, none specified"); } Class providerClass = StorageManagers.lookupProvider(sanProvider); if (providerClass != null && SANProvider.class.isAssignableFrom(providerClass)) { try { connectionManager = (SANProvider) providerClass.newInstance(); } catch (IllegalAccessException e) { throw Exceptions.toUndeclared("Cannot create SANManager.", e); } catch (InstantiationException e) { throw Exceptions.toUndeclared("Cannot create SANManager. Cannot instantiate the SAN Provider", e); } } else { throw Exceptions.toUndeclared("Provider not of correct type or not found."); } } /** * used for unit testing, allows a (mock) connectionManager to be injected * * @param connectionManager */ SANManager(SANProvider connectionManager) { this.connectionManager = connectionManager; } public void addSnapshot(String snapshotId) throws EucalyptusCloudException { finishVolume(snapshotId); } public void checkPreconditions() throws EucalyptusCloudException { if (!new File(BaseDirectory.LIB.toString() + File.separator + "connect_iscsitarget_sc.pl").exists()) { throw new EucalyptusCloudException("connect_iscsitarget_sc.pl not found"); } if (!new File(BaseDirectory.LIB.toString() + File.separator + "disconnect_iscsitarget_sc.pl").exists()) { throw new EucalyptusCloudException("disconnect_iscsitarget_sc.pl not found"); } if (connectionManager != null) { connectionManager.checkPreconditions(); } else { LOG.warn("Invalid/uninitialized reference to blockstorage backend"); throw new EucalyptusCloudException("Invalid/uninitialized reference to blockstorage backend"); } } @Override public void cleanSnapshot(String snapshotId, String snapshotPointId) { SANVolumeInfo sanSnapshot = null; String sanSnapshotId = null; String iqn = null; try { sanSnapshot = lookup(snapshotId); sanSnapshotId = sanSnapshot.getSanVolumeId(); iqn = sanSnapshot.getIqn(); } catch (Exception e) { LOG.debug("Skipping clean up for " + snapshotId); return; } LOG.info("Deleting " + sanSnapshotId + " on backend"); if (connectionManager.deleteSnapshot(sanSnapshotId, iqn, snapshotPointId)) { try (TransactionResource tran = Entities.transactionFor(SANVolumeInfo.class)) { SANVolumeInfo snapInfo = Entities.uniqueResult(new SANVolumeInfo(snapshotId).withSanVolumeId(sanSnapshotId)); Entities.delete(snapInfo); tran.commit(); } catch (TransactionException | NoSuchElementException ex) { LOG.warn("Unable to clean failed backend resource " + snapshotId, ex); return; } } } public void cleanVolume(String volumeId) { SANVolumeInfo sanVolume = null; String sanVolumeId = null; try { sanVolume = lookup(volumeId); sanVolumeId = sanVolume.getSanVolumeId(); } catch (Exception e) { LOG.debug("Skipping clean up for " + volumeId); return; } LOG.info("Deleting " + sanVolumeId + " on backend"); if (connectionManager.deleteVolume(sanVolumeId, sanVolume.getIqn())) { try (TransactionResource tran = Entities.transactionFor(SANVolumeInfo.class)) { SANVolumeInfo volumeInfo = Entities.uniqueResult(new SANVolumeInfo(volumeId).withSanVolumeId(sanVolumeId)); Entities.delete(volumeInfo); tran.commit(); } catch (NoSuchElementException | TransactionException ex) { LOG.warn("Unable to clean failed backend resource " + volumeId); return; } } } public void configure() throws EucalyptusCloudException { // configure provider if (connectionManager != null) { try { LOG.info("Checking backend connection information for cluster: " + StorageInfo.getStorageInfo().getName()); connectionManager.checkConnectionInfo(); } catch (ConnectionInfoNotFoundException e) { LOG.warn("Cannot configure SC blockstorage backend due to missing properties. " + e.getMessage()); throw new EucalyptusCloudException("Cannot configure SC blockstorage backend due to missing properties. " + e.getMessage()); } LOG.info("Configuring block storage backend for cluster: " + StorageInfo.getStorageInfo().getName()); connectionManager.configure(); } else { LOG.warn("Invalid/uninitialized reference to blockstorage backend"); throw new EucalyptusCloudException("Invalid/uninitialized reference to blockstorage backend"); } } public StorageResource createSnapshot(String volumeId, String snapshotId, String snapshotPointId, Boolean shouldTransferSnapshots) throws EucalyptusCloudException { String sanSnapshotId = resourceIdOnSan(snapshotId); SANVolumeInfo snapInfo = new SANVolumeInfo(snapshotId); StorageResource storageResource = null; // Look up source volume in the database and get the backend volume ID SANVolumeInfo volumeInfo = lookup(volumeId); String sanVolumeId = volumeInfo.getSanVolumeId(); int size = volumeInfo.getSize(); // Check to make sure that snapshot does not already exist on the backend try (TransactionResource tran = Entities.transactionFor(SANVolumeInfo.class)) { SANVolumeInfo existingSnap = Entities.uniqueResult(snapInfo); LOG.info("Checking for " + existingSnap.getSanVolumeId() + " on backend"); if (connectionManager.snapshotExists(existingSnap.getSanVolumeId(), existingSnap.getIqn())) { throw new VolumeAlreadyExistsException("Existing resource found on backend for " + existingSnap.getSanVolumeId()); } else { LOG.debug("Found database record but resource does not exist on backend. Deleting database record for " + snapshotId); Entities.delete(existingSnap); tran.commit(); } } catch (NoSuchElementException ex) { // intentional no-op } catch (VolumeAlreadyExistsException ex) { throw ex; } catch (Exception ex) { throw new EucalyptusCloudException(ex); } try { Transactions.save(snapInfo.withSanVolumeId(sanSnapshotId).withSize(size).withSnapshotOf(volumeId)); } catch (Exception ex) { LOG.warn("Failed to persist database record for " + snapshotId, ex); throw new EucalyptusCloudException("Failed to persist database record for " + snapshotId, ex); } LOG.info("Creating " + sanSnapshotId + " from " + sanVolumeId + " using snapshot point " + snapshotPointId + " on backend"); String iqn = connectionManager.createSnapshot(sanVolumeId, sanSnapshotId, snapshotPointId); if (iqn != null) { if (shouldTransferSnapshots) { String scIqn = StorageProperties.getStorageIqn(); if (scIqn == null) { LOG.warn("Storage Controller IQN not found"); throw new EucalyptusCloudException("Storage Controller IQN not found"); } // Ensure that the SC can attach to the volume. String lun = null; try { LOG.info("Exporting " + sanSnapshotId + " on backend to Storage Controller host IQN " + scIqn); lun = connectionManager.exportResource(sanSnapshotId, scIqn, iqn); } catch (EucalyptusCloudException attEx) { LOG.warn("Failed to export " + sanSnapshotId + " on backend to Storage Controller", attEx); throw new EucalyptusCloudException( "Failed to create " + snapshotId + " due to error exporting " + sanSnapshotId + " on backend to Storage Controller", attEx); } if (lun == null) { LOG.warn("Invalid value found for LUN upon exporting " + sanSnapshotId + " on backend"); throw new EucalyptusCloudException( "Failed to create " + snapshotId + " due to invalid value for LUN upon exporting " + sanSnapshotId + " on backend"); } // Moved this to before the connection is attempted since the volume does exist, it may need to be cleaned try (TransactionResource tran = Entities.transactionFor(SANVolumeInfo.class)) { SANVolumeInfo existingSnap = Entities.uniqueResult(snapInfo); existingSnap.setIqn(iqn + ',' + lun); // Store the lun ID in the iqn string, its needed for disconnecting the snapshot from SC later Entities.merge(existingSnap); tran.commit(); } catch (Exception e) { LOG.warn("Failed to update database record with IQN and LUN post creation for " + snapshotId); throw new EucalyptusCloudException("Failed to update database entity with IQN and LUN post creation for " + snapshotId, e); } // Run the connect try { LOG.info("Connecting " + sanSnapshotId + " on backend to Storage Controller for transfer"); storageResource = connectionManager.connectTarget(iqn, lun); storageResource.setId(snapshotId); } catch (Exception connEx) { LOG.warn("Failed to connect " + sanSnapshotId + " on backend to Storage Controller. Detaching and cleaning up", connEx); try { LOG.info("Unexporting " + sanSnapshotId + " on backend from Storage Controller host IQN " + scIqn); connectionManager.unexportResource(sanSnapshotId, scIqn); } catch (EucalyptusCloudException detEx) { LOG.debug("Could not unexport " + sanSnapshotId + " during cleanup of failed connection"); } throw new EucalyptusCloudException( "Failed to create " + snapshotId + " due to an error connecting " + sanSnapshotId + " on backend to Storage Controller", connEx); } } else { // Just save the iqn of the snapshot, nothing more to do since upload is not necessary try (TransactionResource tran = Entities.transactionFor(SANVolumeInfo.class)) { SANVolumeInfo existingSnap = Entities.uniqueResult(snapInfo); existingSnap.setIqn(iqn); Entities.merge(existingSnap); tran.commit(); } catch (Exception ex) { LOG.warn("Failed to update database record with IQN post creation for " + snapshotId); throw new EucalyptusCloudException("Failed to update database record with IQN post creation for " + snapshotId, ex); } } } else { LOG.warn("Invalid IQN from backend for " + sanSnapshotId); throw new EucalyptusCloudException("Failed to create " + snapshotId + " due to invalid IQN from backend for " + sanSnapshotId); } return storageResource; } public void createSnapshot(String volumeId, String snapshotId, String snapshotPointId) throws EucalyptusCloudException { String sanSnapshotId = resourceIdOnSan(snapshotId); SANVolumeInfo snapInfo = new SANVolumeInfo(snapshotId); // Look up source volume in the database and get the backend volume ID SANVolumeInfo volumeInfo = lookup(volumeId); String sanVolumeId = volumeInfo.getSanVolumeId(); int size = volumeInfo.getSize(); // Check to make sure that snapshot does not already exist on the backend try (TransactionResource tran = Entities.transactionFor(SANVolumeInfo.class)) { SANVolumeInfo existingSnap = Entities.uniqueResult(snapInfo); LOG.info("Checking for " + existingSnap.getSanVolumeId() + " on backend"); if (connectionManager.snapshotExists(existingSnap.getSanVolumeId(), existingSnap.getIqn())) { throw new VolumeAlreadyExistsException("Existing resource found on backend for " + existingSnap.getSanVolumeId()); } else { LOG.debug("Found database record but resource does not exist on backend. Deleting database record for " + snapshotId); Entities.delete(existingSnap); tran.commit(); } } catch (NoSuchElementException ex) { // intentional no-op } catch (VolumeAlreadyExistsException ex) { throw ex; } catch (Exception ex) { throw new EucalyptusCloudException(ex); } try { Transactions.save(snapInfo.withSanVolumeId(sanSnapshotId).withSize(size).withSnapshotOf(volumeId)); } catch (Exception ex) { LOG.warn("Failed to persist database record for " + snapshotId, ex); throw new EucalyptusCloudException("Failed to persist database record for " + snapshotId, ex); } LOG.info("Creating " + sanSnapshotId + " from " + sanVolumeId + " using snapshot point " + snapshotPointId + " on backend"); String iqn = connectionManager.createSnapshot(sanVolumeId, sanSnapshotId, snapshotPointId); if (iqn != null) { // Just save the iqn of the snapshot, nothing more to do since upload is not necessary try (TransactionResource tran = Entities.transactionFor(SANVolumeInfo.class)) { SANVolumeInfo existingSnap = Entities.uniqueResult(snapInfo); existingSnap.setIqn(iqn); Entities.merge(existingSnap); tran.commit(); } catch (Exception ex) { LOG.warn("Failed to update database record with IQN post creation for " + snapshotId); throw new EucalyptusCloudException("Failed to update database record with IQN post creation for " + snapshotId, ex); } } else { LOG.warn("Invalid IQN from backend for " + sanSnapshotId); throw new EucalyptusCloudException("Failed to create " + snapshotId + " due to invalid IQN from backend for " + sanSnapshotId); } } public void createVolume(String volumeId, int size) throws EucalyptusCloudException { String sanVolumeId = resourceIdOnSan(volumeId); SANVolumeInfo volumeInfo = new SANVolumeInfo(volumeId); // Check to make sure that volume does not already exist on the backend try (TransactionResource tran = Entities.transactionFor(SANVolumeInfo.class)) { SANVolumeInfo existingVol = Entities.uniqueResult(volumeInfo); LOG.info("Checking for " + existingVol.getSanVolumeId() + " on backend"); if (connectionManager.volumeExists(existingVol.getSanVolumeId(), existingVol.getIqn())) { throw new VolumeAlreadyExistsException("Existing resource found on backend for " + existingVol.getSanVolumeId()); } else { LOG.debug("Found database record but resource does not exist on backend. Deleting database record for " + volumeId); Entities.delete(existingVol); tran.commit(); } } catch (NoSuchElementException ex) { // intentional no-op } catch (VolumeAlreadyExistsException ex) { throw ex; } catch (Exception ex) { throw new EucalyptusCloudException(ex); } try { Transactions.save(volumeInfo.withSanVolumeId(sanVolumeId).withSize(size)); } catch (Exception ex) { LOG.warn("Failed to persist database record for " + volumeId, ex); throw new EucalyptusCloudException("Failed to persist database record for " + volumeId, ex); } LOG.info("Creating " + sanVolumeId + " on backend"); String iqn = connectionManager.createVolume(sanVolumeId, size); if (iqn != null) { try (TransactionResource tran = Entities.transactionFor(SANVolumeInfo.class)) { SANVolumeInfo existingVol = Entities.uniqueResult(volumeInfo); existingVol.setIqn(iqn); tran.commit(); } catch (Exception ex) { LOG.warn("Failed to update database record with IQN post creation for " + volumeId); throw new EucalyptusCloudException("Failed to update database record with IQN post creation for " + volumeId, ex); } } else { LOG.warn("Invalid IQN from backend for " + sanVolumeId); throw new EucalyptusCloudException("Failed to create " + volumeId + " due to invalid IQN from backend for " + sanVolumeId); } } private String resourceIdOnSan(String resourceId) { try { SANInfo sanInfo = Transactions.one(new SANInfo(), Functions.<SANInfo>identity()); return (StringUtils.trimToEmpty(sanInfo.getResourcePrefix()) + resourceId + StringUtils.trimToEmpty(sanInfo.getResourceSuffix())); } catch (TransactionException ex) { LOG.warn("Unable to retrieve resource prefix/suffix from databse", ex); return resourceId; } } public int createVolume(String volumeId, String snapshotId, int size) throws EucalyptusCloudException { String sanVolumeId = resourceIdOnSan(volumeId); SANVolumeInfo volumeInfo = new SANVolumeInfo(volumeId); // Look up source snapshot in the database and get the backend snapshot ID SANVolumeInfo snapInfo = lookup(snapshotId); String sanSnapshotId = snapInfo.getSanVolumeId(); int snapSize = snapInfo.getSize(); if (size <= 0) { size = snapSize; } // Check to make sure that volume does not already exist on the backend try (TransactionResource tran = Entities.transactionFor(SANVolumeInfo.class)) { SANVolumeInfo existingVol = Entities.uniqueResult(volumeInfo); LOG.info("Checking for " + existingVol.getSanVolumeId() + " on backend"); if (connectionManager.volumeExists(existingVol.getSanVolumeId(), existingVol.getIqn())) { throw new VolumeAlreadyExistsException("Existing resource found on backend for " + existingVol.getSanVolumeId()); } else { LOG.debug("Found database record but resource does not exist on backend. Deleting database record for " + volumeId); Entities.delete(existingVol); tran.commit(); } } catch (NoSuchElementException ex) { // intentional no-op } catch (VolumeAlreadyExistsException ex) { throw ex; } catch (Exception ex) { throw new EucalyptusCloudException(ex); } try { Transactions.save(volumeInfo.withSanVolumeId(sanVolumeId).withSize(size)); } catch (Exception ex) { LOG.warn("Failed to persist database record for " + volumeId, ex); throw new EucalyptusCloudException("Failed to persist database record for " + volumeId, ex); } LOG.info("Creating " + sanVolumeId + " from " + sanSnapshotId + " on backend"); String iqn = connectionManager.createVolume(sanVolumeId, sanSnapshotId, snapSize, size, snapInfo.getIqn()); if (iqn != null) { try (TransactionResource tran = Entities.transactionFor(SANVolumeInfo.class)) { SANVolumeInfo existingVol = Entities.uniqueResult(volumeInfo); existingVol.setIqn(iqn); Entities.merge(existingVol); tran.commit(); } catch (Exception ex) { LOG.warn("Failed to update database record with IQN post creation for " + volumeId); throw new EucalyptusCloudException("Failed to update database record with IQN post creation for " + volumeId, ex); } } else { LOG.warn("Invalid IQN from backend for " + sanVolumeId); throw new EucalyptusCloudException("Failed to create " + volumeId + " due to invalid IQN from backend for " + sanVolumeId); } return size; } public void cloneVolume(String volumeId, String parentVolumeId) throws EucalyptusCloudException { String sanVolumeId = resourceIdOnSan(volumeId); SANVolumeInfo volInfo = new SANVolumeInfo(volumeId); // Look up source volume in the database and get the backend volume ID SANVolumeInfo parentVolumeInfo = lookup(parentVolumeId); String sanParentVolumeId = parentVolumeInfo.getSanVolumeId(); int size = parentVolumeInfo.getSize(); // Check to make sure that cloned volume does not already exist on the backend try (TransactionResource tran = Entities.transactionFor(SANVolumeInfo.class)) { SANVolumeInfo existingVol = Entities.uniqueResult(volInfo); LOG.info("Checking for " + existingVol.getSanVolumeId() + " on backend"); if (connectionManager.snapshotExists(existingVol.getSanVolumeId(), existingVol.getIqn())) { throw new VolumeAlreadyExistsException("Existing resource found on backend for " + existingVol.getSanVolumeId()); } else { LOG.debug("Found database record but resource does not exist on backend. Deleting database record for " + volumeId); Entities.delete(existingVol); tran.commit(); } } catch (NoSuchElementException ex) { // intentional no-op } catch (VolumeAlreadyExistsException ex) { throw ex; } catch (Exception ex) { throw new EucalyptusCloudException(ex); } try { Transactions.save(volInfo.withSanVolumeId(sanVolumeId).withSize(size)); } catch (Exception ex) { LOG.warn("Failed to persist database record for " + volumeId, ex); throw new EucalyptusCloudException("Failed to persist database record for " + volumeId, ex); } LOG.info("Cloning " + sanVolumeId + " from " + sanParentVolumeId + " on backend"); String iqn = connectionManager.cloneVolume(sanVolumeId, sanParentVolumeId, parentVolumeInfo.getIqn()); if (iqn != null) { try (TransactionResource tran = Entities.transactionFor(SANVolumeInfo.class)) { SANVolumeInfo existingVol = Entities.uniqueResult(volInfo); existingVol.setIqn(iqn); Entities.merge(existingVol); tran.commit(); } catch (Exception ex) { LOG.warn("Failed to update database record with IQN post creation for " + volumeId); throw new EucalyptusCloudException("Failed to update database record with IQN post creation for " + volumeId, ex); } } else { LOG.warn("Invalid IQN from backend for " + sanVolumeId); throw new EucalyptusCloudException("Failed to create " + volumeId + " due to invalid IQN from backend for " + sanVolumeId); } } @Override public void deleteSnapshot(String snapshotId, String snapshotPointId) throws EucalyptusCloudException { SANVolumeInfo sanSnapshot = null; String sanSnapshotId = null; String iqn = null; // Look up source snapshot in the database and get the backend snapshot ID try { sanSnapshot = lookup(snapshotId); sanSnapshotId = sanSnapshot.getSanVolumeId(); iqn = sanSnapshot.getIqn(); } catch (Exception ex) { LOG.debug("Skipping deletion for " + snapshotId); return; } boolean deleteEntity = false; // Try deleting the snapshot. It might fail as snapshots are global and another SC may have already deleted it LOG.info("Deleting " + sanSnapshotId + " on backend"); if (connectionManager.deleteSnapshot(sanSnapshotId, iqn, snapshotPointId)) { deleteEntity = true; } else { // If snapshot deletion failed, check to see if the snapshot even exists LOG.debug("Unable to delete " + sanSnapshotId + " on backend. Checking to see if the resource exists"); if (!connectionManager.snapshotExists(sanSnapshotId, iqn)) { LOG.debug("Resource not found on backend for " + sanSnapshotId + ". Safe to delete database record for " + snapshotId); deleteEntity = true; } else { LOG.warn("Failed to delete backend resource " + sanSnapshotId); throw new EucalyptusCloudException("Failed to delete backend resource " + sanSnapshotId); } } if (deleteEntity) { try { Transactions.delete(new SANVolumeInfo(snapshotId).withSanVolumeId(sanSnapshotId)); } catch (Exception e) { LOG.warn("Failed to remove database record post deletion for " + snapshotId, e); throw new EucalyptusCloudException("Failed to remove database record post deletion for " + snapshotId, e); } } } public void deleteVolume(String volumeId) throws EucalyptusCloudException { SANVolumeInfo sanVolume = null; String sanVolumeId = null; // Look up source volume in the database and get the backend snapshot ID try { sanVolume = lookup(volumeId); sanVolumeId = sanVolume.getSanVolumeId(); } catch (Exception ex) { LOG.debug("Skipping deletion for " + volumeId); return; } LOG.info("Deleting " + sanVolumeId + " on backend"); if (connectionManager.deleteVolume(sanVolumeId, sanVolume.getIqn())) { try { Transactions.delete(new SANVolumeInfo(volumeId).withSanVolumeId(sanVolumeId)); } catch (Exception e) { LOG.warn("Failed to remove database record post deletion for " + volumeId, e); throw new EucalyptusCloudException("Failed to remove database record post deletion for " + volumeId, e); } } else { LOG.warn("Failed to delete backend resource " + sanVolumeId); throw new EucalyptusCloudException("Failed to delete backend resource " + sanVolumeId); } } public int getSnapshotSize(String snapshotId) throws EucalyptusCloudException { try { SANVolumeInfo snapInfo = Transactions.find(new SANVolumeInfo(snapshotId)); return snapInfo.getSize(); } catch (Exception ex) { LOG.warn("Encountered error during lookup for " + snapshotId, ex); throw new EucalyptusCloudException("Encountered error during lookup for " + snapshotId, ex); } } public String getVolumeConnectionString(String volumeId) throws EucalyptusCloudException { return connectionManager.getVolumeConnectionString(volumeId); } public void initialize() throws EucalyptusCloudException { LOG.info("Initializing SANInfo entity"); SANInfo.getStorageInfo(); connectionManager.initialize(); } public void loadSnapshots(List<String> snapshotSet, List<String> snapshotFileNames) throws EucalyptusCloudException { // Nothing to do here } public List<String> prepareForTransfer(String snapshotId) throws EucalyptusCloudException { // Nothing to do here return new ArrayList<String>(); } public void reload() {} public void startupChecks() throws EucalyptusCloudException { connectionManager.checkConnection(); } public void finishVolume(String snapshotId) throws EucalyptusCloudException { SANVolumeInfo snapInfo = null; String iqnAndLun = null; String sanVolumeId = null; try { snapInfo = lookup(snapshotId); iqnAndLun = snapInfo.getIqn(); sanVolumeId = snapInfo.getSanVolumeId(); } catch (NoSuchRecordException e) { LOG.debug("Skipping finish up for " + snapshotId); return; } if (iqnAndLun != null && iqnAndLun.contains(",")) { // disconnect and unexport the snapshot from SC String[] parts = iqnAndLun.split(","); if (parts.length == 2) { // disconnect SC-snapshot iscsi connection try { LOG.info("Disconnecting " + sanVolumeId + " on backend from Storage Controller"); connectionManager.disconnectTarget(sanVolumeId, parts[0], parts[1]); } catch (Exception e) { LOG.warn("Failed to disconnect iscsi session between " + sanVolumeId + " and SC", e); } } else { LOG.warn("Unable to disconnect " + sanVolumeId + " from Storage Controller due to invalid iqn format. Expected iqn to contain one ',' but got: " + iqnAndLun); throw new EucalyptusCloudException("Unable to disconnect " + sanVolumeId + " from Storage Controller due to invalid iqn format"); } // Unexport volume from SC String scIqn = StorageProperties.getStorageIqn(); try { LOG.info("Unexporting " + sanVolumeId + " on backend from Storage Controller host IQN " + scIqn); connectionManager.unexportResource(sanVolumeId, scIqn); } catch (Exception e) { LOG.warn("Could not unexport " + sanVolumeId + " on backend from Storage Controller"); } // Remove lun and update snapshot IQN try (TransactionResource tran = Entities.transactionFor(SANVolumeInfo.class)) { SANVolumeInfo existingSnap = Entities.uniqueResult(snapInfo); existingSnap.setIqn(parts[0]); // Store the lun ID in the iqn string, its needed for disconnecting the snapshot from SC later Entities.merge(existingSnap); tran.commit(); } catch (Exception e) { LOG.warn("Failed to update IQN after disconnecting " + snapshotId + " from Storage Controller", e); // Warn and move on, no need to throw exception } } else { // snapshot was never exported to SC, no need to disconnect it and un-export it // this could happen if snapshot upload was not necessary LOG.debug("Skipping disconnect and unexport operations for " + sanVolumeId); } // wait for snapshot operation to complete try { LOG.info("Waiting for " + sanVolumeId + " to complete on backend"); connectionManager.waitAndComplete(sanVolumeId, iqnAndLun); } catch (EucalyptusCloudException e) { LOG.warn("Failed during wait for " + sanVolumeId + " to complete on backend", e); throw e; } catch (Exception e) { LOG.warn("Failed during wait for " + sanVolumeId + " to complete on backend", e); throw new EucalyptusCloudException("Failed during wait for " + sanVolumeId + " to complete on backend", e); } } public StorageResourceWithCallback prepSnapshotForDownload(final String snapshotId, int sizeExpected, long actualSizeInMB) throws EucalyptusCloudException { try { // If any record for the snapshot exists, just copy that info lookup(snapshotId); LOG.debug("Found existing database record for " + snapshotId + ". Will use that lun and record."); return null; } catch (NoSuchRecordException e) { // going forward with the assumption that snapshot record is not found for this SC LOG.debug( "Backend database record for " + snapshotId + " not found. Setting up holder on backend to hold the snapshot content downloaded from OSG"); } String sanSnapshotId = resourceIdOnSan(snapshotId); String iqn = null; try { // TODO Create a database record first before firing off the volume creation LOG.info("Creating " + sanSnapshotId + " of size " + actualSizeInMB + " MB on backend"); iqn = connectionManager.createSnapshotHolder(sanSnapshotId, actualSizeInMB); } catch (EucalyptusCloudException e) { LOG.warn("Failed to create backend resource for " + snapshotId); iqn = null; } if (iqn != null) { try { String scIqn = StorageProperties.getStorageIqn(); if (scIqn == null) { throw new EucalyptusCloudException("Could not get the SC's initiator IQN, found null."); } // Ensure that the SC can attach to the volume. String lun = null; try { LOG.info("Exporting " + sanSnapshotId + " on backend to Storage Controller host IQN " + scIqn); lun = connectionManager.exportResource(sanSnapshotId, scIqn, iqn); } catch (EucalyptusCloudException attEx) { LOG.warn("Failed to export " + sanSnapshotId + " on backend to Storage Controller", attEx); throw new EucalyptusCloudException( "Failed to create " + snapshotId + " due to error exporting " + sanSnapshotId + " on backend to Storage Controller", attEx); } if (lun == null) { LOG.warn("Invalid value found for LUN upon exporting " + sanSnapshotId + " on backend"); throw new EucalyptusCloudException( "Failed to create " + snapshotId + " due to invalid value for LUN upon exporting " + sanSnapshotId + " on backend"); } // Store the lun ID in the iqn string, its needed for disconnecting the snapshot from SC later SANVolumeInfo snapInfo = new SANVolumeInfo(snapshotId, iqn + ',' + lun, sizeExpected).withSanVolumeId(sanSnapshotId); try { Transactions.save(snapInfo); } catch (TransactionException e) { LOG.warn("Failed to update database record with IQN and LUN post creation for " + snapshotId); throw new EucalyptusCloudException("Failed to update database entity with IQN and LUN post creation for " + snapshotId, e); } // Run the connect StorageResource storageResource = null; try { LOG.info("Connecting " + sanSnapshotId + " on backend to Storage Controller for transfer"); storageResource = connectionManager.connectTarget(iqn, lun); storageResource.setId(snapshotId); } catch (Exception connEx) { LOG.warn("Failed to connect " + sanSnapshotId + " on backend to Storage Controller. Detaching and cleaning up", connEx); try { LOG.info("Unexporting " + sanSnapshotId + " on backend from Storage Controller host IQN " + scIqn); connectionManager.unexportResource(sanSnapshotId, scIqn); } catch (EucalyptusCloudException detEx) { LOG.debug("Could not unexport " + sanSnapshotId + " during cleanup of failed connection"); } throw new EucalyptusCloudException( "Failed to create " + snapshotId + " due to an error connecting " + sanSnapshotId + " on backend to Storage Controller", connEx); } return new StorageResourceWithCallback(storageResource, new Function<StorageResource, String>() { @Override public String apply(StorageResource arg0) { try { LOG.debug("Executing callback for prepareSnapshotForDownload() with " + snapshotId); SANVolumeInfo snapInfo = null; String iqnAndLun = null; String sanVolumeId = null; try { snapInfo = lookup(snapshotId); iqnAndLun = snapInfo.getIqn(); sanVolumeId = snapInfo.getSanVolumeId(); } catch (NoSuchRecordException e) { LOG.debug("Skipping finish up for " + snapshotId); return null; } if (iqnAndLun != null && iqnAndLun.contains(",")) { // disconnect and unexport the snapshot from SC String[] parts = iqnAndLun.split(","); if (parts.length == 2) { // disconnect SC-snapshot iscsi connection try { LOG.info("Disconnecting " + sanVolumeId + " on backend from Storage Controller"); connectionManager.disconnectTarget(sanVolumeId, parts[0], parts[1]); } catch (Exception e) { LOG.warn("Failed to disconnect iscsi session between " + sanVolumeId + " and SC", e); } } else { LOG.warn("Unable to disconnect " + sanVolumeId + " from Storage Controller due to invalid iqn format. Expected iqn to contain one ',' but got: " + iqnAndLun); throw new EucalyptusCloudException("Unable to disconnect " + sanVolumeId + " from Storage Controller due to invalid iqn format"); } // Unexport volume from SC String scIqn = StorageProperties.getStorageIqn(); try { LOG.info("Unexporting " + sanVolumeId + " on backend from Storage Controller host IQN " + scIqn); connectionManager.unexportResource(sanVolumeId, scIqn); } catch (Exception e) { LOG.warn("Could not unexport " + sanVolumeId + " on backend from Storage Controller"); } // Remove lun and update snapshot IQN try (TransactionResource tran = Entities.transactionFor(SANVolumeInfo.class)) { SANVolumeInfo existingSnap = Entities.uniqueResult(snapInfo); existingSnap.setIqn(parts[0]); // Store the lun ID in the iqn string, its needed for disconnecting the snapshot from SC later Entities.merge(existingSnap); tran.commit(); } catch (Exception e) { LOG.warn("Failed to update IQN after disconnecting " + snapshotId + " from Storage Controller", e); // Warn and move on, no need to throw exception } } else { // snapshot was never exported to SC, no need to disconnect it and un-export it // this could happen if snapshot upload was not necessary LOG.debug("Skipping disconnect and unexport operations for " + sanVolumeId); } // wait for snapshot operation to complete try { LOG.info("Waiting for " + sanVolumeId + " to complete on backend"); connectionManager.waitAndComplete(sanVolumeId, iqnAndLun); } catch (EucalyptusCloudException e) { LOG.warn("Failed during wait for " + sanVolumeId + " to complete on backend", e); throw e; } catch (Exception e) { LOG.warn("Failed during wait for " + sanVolumeId + " to complete on backend", e); throw new EucalyptusCloudException("Failed during wait for " + sanVolumeId + " to complete on backend", e); } } catch (Exception e) { Exceptions.toException("", e); } return null; } }); } catch (EucalyptusCloudException e) { LOG.debug("Deleting " + sanSnapshotId + " on backend"); if (!connectionManager.deleteVolume(sanSnapshotId, iqn)) { LOG.debug("Failed to delete backend resource " + snapshotId); } throw e; } } return null; } public ArrayList<ComponentProperty> getStorageProps() { ArrayList<ComponentProperty> componentProperties = null; ConfigurableClass configurableClass = StorageInfo.class.getAnnotation(ConfigurableClass.class); if (configurableClass != null) { String root = configurableClass.root(); String alias = configurableClass.alias(); componentProperties = (ArrayList<ComponentProperty>) PropertyDirectory.getComponentPropertySet(StorageProperties.NAME + "." + root, alias); } configurableClass = SANInfo.class.getAnnotation(ConfigurableClass.class); if (configurableClass != null) { String root = configurableClass.root(); String alias = configurableClass.alias(); if (componentProperties == null) componentProperties = (ArrayList<ComponentProperty>) PropertyDirectory.getComponentPropertySet(StorageProperties.NAME + "." + root, alias); else componentProperties.addAll(PropertyDirectory.getComponentPropertySet(StorageProperties.NAME + "." + root, alias)); } connectionManager.getStorageProps(componentProperties); return componentProperties; } public void setStorageProps(ArrayList<ComponentProperty> storageProps) { for (ComponentProperty prop : storageProps) { try { ConfigurableProperty entry = PropertyDirectory.getPropertyEntry(prop.getQualifiedName()); // type parser will correctly covert the value entry.setValue(prop.getValue()); } catch (IllegalAccessException | ConfigurablePropertyException e) { LOG.warn("Encountered error while setting storage properties", e); } } connectionManager.setStorageProps(storageProps); } public String getStorageRootDirectory() { return StorageProperties.storageRootDirectory; } public String getVolumePath(String volumeId) throws EucalyptusCloudException { try (TransactionResource tran = Entities.transactionFor(SANVolumeInfo.class)) { SANVolumeInfo volumeInfo = Entities.uniqueResult(new SANVolumeInfo(volumeId)); String iqn = volumeInfo.getIqn(); String deviceName = connectionManager.connectTarget(iqn, null).getPath(); return deviceName; } catch (TransactionException | NoSuchElementException ex) { LOG.warn("Block storage backend database record not found for " + volumeId); throw new EucalyptusCloudException("Block storage backend database record not found for " + volumeId, ex); } } public void importVolume(String volumeId, String volumePath, int size) throws EucalyptusCloudException { // may be throw an unsupported exception here } public String getSnapshotPath(String snapshotId) throws EucalyptusCloudException { return getVolumePath(snapshotId); } public void importSnapshot(String snapshotId, String volumeId, String snapPath, int size) throws EucalyptusCloudException { // may be throw an unsupported exception here } public String exportVolume(String volumeId, String nodeIqn) throws EucalyptusCloudException { SANVolumeInfo volumeInfo = lookup(volumeId); String sanVolumeId = volumeInfo.getSanVolumeId(); LOG.info("Exporting " + sanVolumeId + " on backend to Node Controller host IQN " + nodeIqn); String lun = connectionManager.exportResource(sanVolumeId, nodeIqn, volumeInfo.getIqn()); if (lun == null) { LOG.warn("Invalid value found for LUN upon exporting " + sanVolumeId + " on backend"); throw new EucalyptusCloudException("Invalid value found for LUN upon exporting " + sanVolumeId + " on backend"); } String volumeConnectionString = connectionManager.getVolumeConnectionString(volumeId); if (Strings.isNullOrEmpty(volumeConnectionString)) { throw new EucalyptusCloudException("Could not get valid volume property"); } String auth = connectionManager.getAuthType(); String optionalUser = connectionManager.getOptionalChapUser(); // Construct the correct connect string to return: // <user>,<authmode>,<lun string>,<volume property/SAN iqn> StringBuilder sb = new StringBuilder(); sb.append(connectionManager.getProtocol()).append(','); sb.append(connectionManager.getProviderName()).append(','); sb.append(optionalUser == null ? "" : optionalUser).append(','); sb.append(auth == null ? "" : auth).append(','); sb.append(lun).append(','); sb.append(volumeConnectionString); return sb.toString(); } public void unexportVolumeFromAll(String volumeId) throws EucalyptusCloudException { String sanVolumeId = lookup(volumeId).getSanVolumeId(); LOG.info("Unexporting " + sanVolumeId + " on backend from all hosts"); connectionManager.unexportResourceFromAll(sanVolumeId); } public void unexportVolume(String volumeId, String nodeIqn) throws EucalyptusCloudException, UnsupportedOperationException { String sanVolumeId = lookup(volumeId).getSanVolumeId(); LOG.info("Unexporting " + sanVolumeId + " on backend from Node Controller host IQN " + nodeIqn); connectionManager.unexportResource(sanVolumeId, nodeIqn); } public void checkReady() throws EucalyptusCloudException { if (Component.State.ENABLED.equals(Components.lookup(Storage.class).getState())) { connectionManager.checkConnection(); } } public void stop() throws EucalyptusCloudException { try { connectionManager.stop(); } catch (EucalyptusCloudException e) { LOG.warn("Encountered error stopping connection manager", e); throw e; } finally { connectionManager = null; } } public void disable() throws EucalyptusCloudException { connectionManager.stop(); } public void enable() throws EucalyptusCloudException { connectionManager.checkConnection(); } public boolean getFromBackend(String snapshotId, int size) throws EucalyptusCloudException { SANVolumeInfo snapInfo = new SANVolumeInfo(snapshotId); // Look for the unique snapshot entity for this partition. try (TransactionResource tran = Entities.transactionFor(SANVolumeInfo.class)) { SANVolumeInfo foundSnapInfo = Entities.uniqueResult(snapInfo); // Found the snapshot entity. Check if the snapshot really exists on SAN if (foundSnapInfo == null || StringUtils.isBlank(foundSnapInfo.getSanVolumeId())) { throw new EucalyptusCloudException("Backend ID not found for " + snapshotId); } LOG.info("Checking for " + foundSnapInfo.getSanVolumeId() + " on backend"); if (connectionManager.snapshotExists(foundSnapInfo.getSanVolumeId(), foundSnapInfo.getIqn())) { // Snapshot does exist. Nothing to do LOG.debug("Found database record and backend resource for " + snapshotId + ". Nothing to do here"); return true; } else { // Snapshot does not exist on SAN. Delete the record and move to the next part LOG.debug("Found database record but resource does not exist on backend. Deleting database record for " + snapshotId); Entities.delete(foundSnapInfo); tran.commit(); } } catch (Exception ex) { // Could be an error for snapshot lookup } // Either no unique snapshot entity was found for this partition or one did exist but the snapshot was not present on the SAN // Look for the snapshot in all partitions snapInfo.setScName(null); try (TransactionResource tran = Entities.transactionFor(SANVolumeInfo.class)) { List<SANVolumeInfo> foundSnapInfos = Entities.query(snapInfo); // Loop through the snapshot records and check if one of them exists on the SAN this partition is connected to for (SANVolumeInfo foundSnapInfo : foundSnapInfos) { LOG.info("Checking for " + foundSnapInfo.getSanVolumeId() + " on backend"); if (connectionManager.snapshotExists(foundSnapInfo.getSanVolumeId(), foundSnapInfo.getIqn())) { // Snapshot does exist on SAN. // Create a record for it in this partition SANVolumeInfo newSnapInfo = new SANVolumeInfo(snapshotId, foundSnapInfo.getIqn(), foundSnapInfo.getSize()) .withSanVolumeId(foundSnapInfo.getSanVolumeId()).withSnapshotOf(foundSnapInfo.getSnapshotOf()); Entities.persist(newSnapInfo); tran.commit(); return true; } } } catch (Exception ex) { // Could be an error for snapshot lookup } return false; } public void checkVolume(String volumeId) throws EucalyptusCloudException {} public List<CheckerTask> getCheckers() { return connectionManager.getCheckers(); } public String createSnapshotPoint(String parentVolumeId, String volumeId) throws EucalyptusCloudException { String snapshotPoint = resourceIdOnSan(volumeId); SANVolumeInfo sanParentVolume = lookup(parentVolumeId); String sanParentVolumeId = sanParentVolume.getSanVolumeId(); LOG.info("Creating snapshot point " + snapshotPoint + " on " + sanParentVolumeId); return connectionManager.createSnapshotPoint(sanParentVolumeId, snapshotPoint, sanParentVolume.getIqn()); } // TODO: zhill, should I removed the extra params or only allow the parent and vol Id and then calculate the snapPointId from that? // If the desire is to make this idempotent then a calculation is ideal since the original may have been lost (i.e. restart) public void deleteSnapshotPoint(String parentVolumeId, String volumeId, String snapshotPointId) throws EucalyptusCloudException { SANVolumeInfo sanParentVolume = lookup(parentVolumeId); String sanParentVolumeId = sanParentVolume.getSanVolumeId(); LOG.info("Deleting snapshot point " + snapshotPointId + " on " + sanParentVolumeId); connectionManager.deleteSnapshotPoint(sanParentVolumeId, snapshotPointId, sanParentVolume.getIqn()); } @Override public boolean supportsIncrementalSnapshots() throws EucalyptusCloudException { return connectionManager.supportsIncrementalSnapshots(); } @Override public StorageResourceWithCallback prepIncrementalSnapshotForUpload(String volumeId, String snapshotId, String snapPointId, String prevSnapshotId, String prevSnapPointId) throws EucalyptusCloudException { String sanVolId = lookup(volumeId).getSanVolumeId(); String sanSnapId = lookup(snapshotId).getSanVolumeId(); String prevSanSnapId = lookup(prevSnapshotId).getSanVolumeId(); // TODO lookup IDs to make sure they are there return new StorageResourceWithCallback(connectionManager.generateSnapshotDelta(sanVolId, sanSnapId, snapPointId, prevSanSnapId, prevSnapPointId), new Function<StorageResource, String>() { @Override public String apply(StorageResource arg0) { try { LOG.debug("Executing callback after generating incremental snapshot for " + snapshotId); connectionManager.cleanupSnapshotDelta(sanSnapId, arg0); } catch (Exception e) { LOG.warn("Failed to execute callback after generating incremental snapshot for " + snapshotId, e); } return null; } }); } @Override public StorageResource prepSnapshotForUpload(String volumeId, String snapshotId, String snapPointId) throws EucalyptusCloudException { // Look up snapshot in the database and get the backend snapshot ID SANVolumeInfo snapInfo = lookup(snapshotId); String snapIqn = snapInfo.getIqn(); String sanSnapshotId = snapInfo.getSanVolumeId(); StorageResource storageResource = null; String scIqn = StorageProperties.getStorageIqn(); if (scIqn == null) { LOG.warn("Storage Controller IQN not found"); throw new EucalyptusCloudException("Storage Controller IQN not found"); } // Ensure that the SC can attach to the volume. String lun = null; try { LOG.info("Exporting " + sanSnapshotId + " on backend to Storage Controller host IQN " + scIqn); lun = connectionManager.exportResource(sanSnapshotId, scIqn, snapIqn); } catch (EucalyptusCloudException attEx) { LOG.warn("Failed to export " + sanSnapshotId + " on backend to Storage Controller", attEx); throw new EucalyptusCloudException( "Failed to create " + snapshotId + " due to error exporting " + sanSnapshotId + " on backend to Storage Controller", attEx); } if (lun == null) { LOG.warn("Invalid value found for LUN upon exporting " + sanSnapshotId + " on backend"); throw new EucalyptusCloudException( "Failed to create " + snapshotId + " due to invalid value for LUN upon exporting " + sanSnapshotId + " on backend"); } // Moved this to before the connection is attempted since the volume does exist, it may need to be cleaned try (TransactionResource tran = Entities.transactionFor(SANVolumeInfo.class)) { SANVolumeInfo existingSnap = Entities.uniqueResult(snapInfo); existingSnap.setIqn(snapIqn + ',' + lun); // Store the lun ID in the iqn string, its needed for disconnecting the snapshot from SC later Entities.merge(existingSnap); tran.commit(); } catch (Exception e) { LOG.warn("Failed to update IQN after exporting " + snapshotId + " to Storage Controller", e); throw new EucalyptusCloudException("Failed to update IQN after exporting " + snapshotId + " to Storage Controller", e); } // Run the connect try { LOG.info("Connecting " + sanSnapshotId + " on backend to Storage Controller for transfer"); storageResource = connectionManager.connectTarget(snapIqn, lun); storageResource.setId(snapshotId); } catch (Exception connEx) { LOG.warn("Failed to connect " + sanSnapshotId + " on backend to Storage Controller. Detaching and cleaning up", connEx); try { LOG.info("Unexporting " + sanSnapshotId + " on backend from Storage Controller host IQN " + scIqn); connectionManager.unexportResource(sanSnapshotId, scIqn); } catch (EucalyptusCloudException detEx) { LOG.debug("Could not unexport " + sanSnapshotId + " during cleanup of failed connection"); } throw new EucalyptusCloudException( "Failed to create " + snapshotId + " due to an error connecting " + sanSnapshotId + " on backend to Storage Controller", connEx); } return storageResource; } @Override public StorageResourceWithCallback prepSnapshotBaseForRestore(final String snapshotId, final int size, final String snapshotPointId) throws EucalyptusCloudException { try { // If any record for the snapshot exists, just copy that info lookup(snapshotId); LOG.debug("Found existing database record for " + snapshotId + ". Will use that lun and record."); return null; } catch (NoSuchRecordException e) { // going forward with the assumption that snapshot record is not found for this SC LOG.debug( "Backend database record for " + snapshotId + " not found. Setting up holder on backend to hold the snapshot content downloaded from OSG"); } String sanSnapshotId = resourceIdOnSan(snapshotId); String iqn = null; long actualSizeInMB = size * 1024; try { // TODO Create a database record first before firing off the volume creation LOG.info("Creating " + sanSnapshotId + " of size " + actualSizeInMB + " MB on backend"); iqn = connectionManager.createSnapshotHolder(sanSnapshotId, actualSizeInMB); } catch (EucalyptusCloudException e) { LOG.warn("Failed to create backend resource for " + snapshotId); iqn = null; } if (iqn != null) { try { String scIqn = StorageProperties.getStorageIqn(); if (scIqn == null) { throw new EucalyptusCloudException("Could not get the SC's initiator IQN, found null."); } // Ensure that the SC can attach to the volume. String lun = null; try { LOG.info("Exporting " + sanSnapshotId + " on backend to Storage Controller host IQN " + scIqn); lun = connectionManager.exportResource(sanSnapshotId, scIqn, iqn); } catch (EucalyptusCloudException attEx) { LOG.warn("Failed to export " + sanSnapshotId + " on backend to Storage Controller", attEx); throw new EucalyptusCloudException( "Failed to create " + snapshotId + " due to error exporting " + sanSnapshotId + " on backend to Storage Controller", attEx); } if (lun == null) { LOG.warn("Invalid value found for LUN upon exporting " + sanSnapshotId + " on backend"); throw new EucalyptusCloudException( "Failed to create " + snapshotId + " due to invalid value for LUN upon exporting " + sanSnapshotId + " on backend"); } // Store the lun ID in the iqn string, its needed for disconnecting the snapshot from SC later SANVolumeInfo snapInfo = new SANVolumeInfo(snapshotId, iqn + ',' + lun, size).withSanVolumeId(sanSnapshotId); try { Transactions.save(snapInfo); } catch (TransactionException e) { LOG.warn("Failed to update database record with IQN and LUN post creation for " + snapshotId); throw new EucalyptusCloudException("Failed to update database entity with IQN and LUN post creation for " + snapshotId, e); } // Run the connect StorageResource storageResource = null; try { LOG.info("Connecting " + sanSnapshotId + " on backend to Storage Controller for transfer"); storageResource = connectionManager.connectTarget(iqn, lun); storageResource.setId(snapshotId); } catch (Exception connEx) { LOG.warn("Failed to connect " + sanSnapshotId + " on backend to Storage Controller. Detaching and cleaning up", connEx); try { LOG.info("Unexporting " + sanSnapshotId + " on backend from Storage Controller host IQN " + scIqn); connectionManager.unexportResource(sanSnapshotId, scIqn); } catch (EucalyptusCloudException detEx) { LOG.debug("Could not unexport " + sanSnapshotId + " during cleanup of failed connection"); } throw new EucalyptusCloudException( "Failed to create " + snapshotId + " due to an error connecting " + sanSnapshotId + " on backend to Storage Controller", connEx); } return new StorageResourceWithCallback(storageResource, new Function<StorageResource, String>() { @Override public String apply(StorageResource arg0) { try { LOG.debug("Executing callback after prepping base for restoration of " + snapshotId); SANVolumeInfo snapInfo = null; String iqnAndLun = null; String sanVolumeId = null; try { snapInfo = lookup(snapshotId); iqnAndLun = snapInfo.getIqn(); sanVolumeId = snapInfo.getSanVolumeId(); } catch (NoSuchRecordException e) { LOG.debug("Skipping cleanup for " + snapshotId); return null; } if (iqnAndLun != null && iqnAndLun.contains(",")) { // disconnect and unexport the snapshot from SC String[] parts = iqnAndLun.split(","); if (parts.length == 2) { // disconnect SC-snapshot iscsi connection try { LOG.info("Disconnecting " + sanVolumeId + " on backend from Storage Controller"); connectionManager.disconnectTarget(sanVolumeId, parts[0], parts[1]); } catch (Exception e) { LOG.warn("Failed to disconnect iscsi session between " + sanVolumeId + " and SC", e); } } else { LOG.warn("Unable to disconnect " + sanVolumeId + " from Storage Controller due to invalid iqn format. Expected iqn to contain one ',' but got: " + iqnAndLun); throw new EucalyptusCloudException("Unable to disconnect " + sanVolumeId + " from Storage Controller due to invalid iqn format"); } // Unexport volume from SC String scIqn = StorageProperties.getStorageIqn(); try { LOG.info("Unexporting " + sanVolumeId + " on backend from Storage Controller host IQN " + scIqn); connectionManager.unexportResource(sanVolumeId, scIqn); } catch (Exception e) { LOG.warn("Could not unexport " + sanVolumeId + " on backend from Storage Controller"); } // Remove lun and update snapshot IQN try (TransactionResource tran = Entities.transactionFor(SANVolumeInfo.class)) { SANVolumeInfo existingSnap = Entities.uniqueResult(snapInfo); existingSnap.setIqn(parts[0]); // Store the lun ID in the iqn string, its needed for disconnecting the snapshot from SC later Entities.merge(existingSnap); tran.commit(); } catch (Exception e) { LOG.warn("Failed to update IQN after disconnecting " + snapshotId + " from Storage Controller", e); // Warn and move on, no need to throw exception } // Complete snapshot base restoration return connectionManager.completeSnapshotBaseRestoration(sanVolumeId, snapshotPointId, parts[0]); } else { // snapshot was never exported to SC, no need to disconnect it and un-export it // this could happen if snapshot upload was not necessary LOG.debug("Skipping disconnect and unexport operations for " + sanVolumeId); } } catch (EucalyptusCloudException e) { Exceptions.toException(e); } return null; } }); } catch (EucalyptusCloudException e) { LOG.debug("Deleting " + sanSnapshotId + " on backend"); if (!connectionManager.deleteVolume(sanSnapshotId, iqn)) { LOG.debug("Failed to delete backend resource " + snapshotId); } throw e; } } return null; } @Override public void restoreSnapshotDelta(String currentSnapId, String prevSnapId, String baseId, StorageResource sr) throws EucalyptusCloudException { SANVolumeInfo snapInfo = null; String baseIqn = null; try { snapInfo = lookup(baseId); baseIqn = snapInfo.getIqn(); } catch (NoSuchRecordException e) { LOG.warn("Unable to lookup " + baseId, e); throw new EucalyptusCloudException("Unable to lookup " + baseId, e); } // Apply delta try { LOG.debug("Applying snapshot delta between " + currentSnapId + " and " + prevSnapId + " on base " + baseIqn); connectionManager.restoreSnapshotDelta(baseIqn, sr); } catch (Exception e) { LOG.warn("Failed to apply delta between " + currentSnapId + " and " + prevSnapId + " on base " + baseIqn); throw new EucalyptusCloudException("Failed to apply delta between " + currentSnapId + " and " + prevSnapId + " on base " + baseIqn); } } @Override public void completeSnapshotRestorationFromDeltas(String snapshotId) throws EucalyptusCloudException { SANVolumeInfo snapInfo = null; String sanVolumeId = null; String iqn = null; try { snapInfo = lookup(snapshotId); sanVolumeId = snapInfo.getSanVolumeId(); iqn = snapInfo.getIqn(); } catch (NoSuchRecordException e) { LOG.warn("Unable to lookup " + snapshotId, e); throw new EucalyptusCloudException("Unable to lookup " + snapshotId, e); } // Any remaining tasks/clean up for restoring post delta application try { LOG.debug("Finishing up restoration and configuration of expected snapshot state for " + snapshotId); connectionManager.completeSnapshotDeltaRestoration(sanVolumeId, iqn); } catch (Exception e) { LOG.warn("Failed to finish restoration and configuration of expected snapshot state for " + snapshotId); throw new EucalyptusCloudException("Failed to finish restoration and configuration of expected snapshot state for " + snapshotId, e); } } @Override public <F, T> T executeCallback(Function<F, T> callback, F input) throws EucalyptusCloudException { try { return callback.apply(input); } catch (Throwable t) { throw new EucalyptusCloudException("Unable to execute callback for due to", t); } } private SANVolumeInfo lookup(String resourceId) throws EucalyptusCloudException { SANVolumeInfo resourceInfo = null; try { resourceInfo = Transactions.find(new SANVolumeInfo(resourceId)); if (resourceInfo == null || StringUtils.isBlank(resourceInfo.getSanVolumeId())) { LOG.warn("Backend name/ID not found for " + resourceId); throw new EucalyptusCloudException("Backend name/ID not found for " + resourceId); } else { return resourceInfo; } } catch (NoSuchElementException e) { LOG.warn("Block storage backend database record not found for " + resourceId); throw new NoSuchRecordException("Block storage backend database record not found for " + resourceId); } catch (EucalyptusCloudException e) { throw e; } catch (Exception e) { LOG.warn("Encountered error during block storage backend database lookup for " + resourceId); throw new EucalyptusCloudException("Encountered error during block storage backend database lookup for " + resourceId, e); } } }