/************************************************************************* * (c) Copyright 2017 Hewlett Packard Enterprise Development Company LP * * 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; version 3 of the License. * * This program 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/. * * 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. ************************************************************************/ package com.eucalyptus.blockstorage; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.Semaphore; import org.apache.log4j.Logger; import com.eucalyptus.component.annotation.ComponentNamed; import com.eucalyptus.org.apache.tools.ant.util.DateUtils; import org.hibernate.Criteria; import org.hibernate.criterion.Restrictions; import com.eucalyptus.blockstorage.async.ExpiredSnapshotCleaner; import com.eucalyptus.blockstorage.async.ExpiredVolumeCleaner; import com.eucalyptus.blockstorage.async.FailedSnapshotCleaner; import com.eucalyptus.blockstorage.async.FailedVolumeCleaner; import com.eucalyptus.blockstorage.async.SnapshotCreator; import com.eucalyptus.blockstorage.async.SnapshotDeleter; import com.eucalyptus.blockstorage.async.SnapshotTransferCleaner; import com.eucalyptus.blockstorage.async.ThreadPoolSizeUpdater; import com.eucalyptus.blockstorage.async.VolumeCreator; import com.eucalyptus.blockstorage.async.VolumeDeleter; import com.eucalyptus.blockstorage.async.VolumeStateChecker; import com.eucalyptus.blockstorage.async.VolumesConvertor; import com.eucalyptus.blockstorage.entities.BlockStorageGlobalConfiguration; import com.eucalyptus.blockstorage.entities.SnapshotInfo; import com.eucalyptus.blockstorage.entities.StorageInfo; import com.eucalyptus.blockstorage.entities.VolumeExportRecord; import com.eucalyptus.blockstorage.entities.VolumeInfo; import com.eucalyptus.blockstorage.entities.VolumeToken; import com.eucalyptus.blockstorage.exceptions.AccessDeniedException; import com.eucalyptus.blockstorage.exceptions.SnapshotNotFoundException; import com.eucalyptus.blockstorage.exceptions.SnapshotTooLargeException; import com.eucalyptus.blockstorage.msgs.AttachStorageVolumeResponseType; import com.eucalyptus.blockstorage.msgs.AttachStorageVolumeType; import com.eucalyptus.blockstorage.msgs.CloneVolumeResponseType; import com.eucalyptus.blockstorage.msgs.CloneVolumeType; import com.eucalyptus.blockstorage.msgs.ConvertVolumesResponseType; import com.eucalyptus.blockstorage.msgs.ConvertVolumesType; import com.eucalyptus.blockstorage.msgs.CreateStorageSnapshotResponseType; import com.eucalyptus.blockstorage.msgs.CreateStorageSnapshotType; import com.eucalyptus.blockstorage.msgs.CreateStorageVolumeResponseType; import com.eucalyptus.blockstorage.msgs.CreateStorageVolumeType; import com.eucalyptus.blockstorage.msgs.DeleteStorageSnapshotResponseType; import com.eucalyptus.blockstorage.msgs.DeleteStorageSnapshotType; import com.eucalyptus.blockstorage.msgs.DeleteStorageVolumeResponseType; import com.eucalyptus.blockstorage.msgs.DeleteStorageVolumeType; import com.eucalyptus.blockstorage.msgs.DescribeStorageSnapshotsResponseType; import com.eucalyptus.blockstorage.msgs.DescribeStorageSnapshotsType; import com.eucalyptus.blockstorage.msgs.DescribeStorageVolumesResponseType; import com.eucalyptus.blockstorage.msgs.DescribeStorageVolumesType; import com.eucalyptus.blockstorage.msgs.DetachStorageVolumeResponseType; import com.eucalyptus.blockstorage.msgs.DetachStorageVolumeType; import com.eucalyptus.blockstorage.msgs.ExportVolumeResponseType; import com.eucalyptus.blockstorage.msgs.ExportVolumeType; import com.eucalyptus.blockstorage.msgs.GetStorageConfigurationResponseType; import com.eucalyptus.blockstorage.msgs.GetStorageConfigurationType; import com.eucalyptus.blockstorage.msgs.GetStorageVolumeResponseType; import com.eucalyptus.blockstorage.msgs.GetStorageVolumeType; import com.eucalyptus.blockstorage.msgs.GetVolumeTokenResponseType; import com.eucalyptus.blockstorage.msgs.GetVolumeTokenType; import com.eucalyptus.blockstorage.msgs.StorageSnapshot; import com.eucalyptus.blockstorage.msgs.StorageVolume; import com.eucalyptus.blockstorage.msgs.UnexportVolumeResponseType; import com.eucalyptus.blockstorage.msgs.UnexportVolumeType; import com.eucalyptus.blockstorage.msgs.UpdateStorageConfigurationResponseType; import com.eucalyptus.blockstorage.msgs.UpdateStorageConfigurationType; import com.eucalyptus.blockstorage.threadpool.CheckerThreadPool; import com.eucalyptus.blockstorage.threadpool.SnapshotThreadPool; import com.eucalyptus.blockstorage.threadpool.SnapshotTransferThreadPool; import com.eucalyptus.blockstorage.threadpool.VolumeThreadPool; import com.eucalyptus.blockstorage.util.BlockStorageUtil; import com.eucalyptus.blockstorage.util.StorageProperties; import com.eucalyptus.component.ComponentIds; import com.eucalyptus.component.id.Eucalyptus; import com.eucalyptus.context.Context; import com.eucalyptus.context.Contexts; import com.eucalyptus.context.NoSuchContextException; import com.eucalyptus.entities.Entities; import com.eucalyptus.entities.TransactionException; import com.eucalyptus.entities.TransactionResource; import com.eucalyptus.storage.common.CheckerTask; import com.eucalyptus.util.EucalyptusCloudException; import com.eucalyptus.util.metrics.MonitoredAction; import com.eucalyptus.util.metrics.ThruputMetrics; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import edu.ucsb.eucalyptus.cloud.InvalidParameterValueException; import edu.ucsb.eucalyptus.cloud.NoSuchVolumeException; import edu.ucsb.eucalyptus.cloud.SnapshotInUseException; import edu.ucsb.eucalyptus.cloud.VolumeAlreadyExistsException; import edu.ucsb.eucalyptus.cloud.VolumeNotReadyException; import edu.ucsb.eucalyptus.cloud.VolumeSizeExceededException; import edu.ucsb.eucalyptus.msgs.ComponentProperty; @ComponentNamed public class BlockStorageController implements BlockStorageService { private static Logger LOG = Logger.getLogger(BlockStorageController.class); static LogicalStorageManager blockManager; static final int SNAPSHOT_POINT_DEFAULT_CONCURRENCY = 3; static Supplier<Semaphore> snapshotPointSemaphoreAllocator = new Supplier<Semaphore>() { @Override public Semaphore get( ) { Integer maxConcurrentSnapshots = StorageInfo.getStorageInfo().getMaxConcurrentSnapshots(); int snapshotPointSemaphorePermits = (maxConcurrentSnapshots == null ? SNAPSHOT_POINT_DEFAULT_CONCURRENCY : maxConcurrentSnapshots); LOG.debug("Snapshot point semaphore created with " + snapshotPointSemaphorePermits + " permits."); return new Semaphore(snapshotPointSemaphorePermits); } }; static Supplier<Semaphore> snapshotPointSemaphoreSupplier = Suppliers.memoize(snapshotPointSemaphoreAllocator); private static Function<String, SnapshotInfo> SNAPSHOT_FAILED = new Function<String, SnapshotInfo>() { @Override public SnapshotInfo apply(String arg0) { SnapshotInfo snap; try { snap = Entities.uniqueResult(new SnapshotInfo(arg0)); snap.setStatus(StorageProperties.Status.failed.toString()); snap.setProgress("0"); LOG.debug("Snapshot " + arg0 + " set to 'failed' state"); return snap; } catch (TransactionException | NoSuchElementException e) { LOG.warn("Failed to retrieve snapshot entity from DB for " + arg0, e); } return null; } }; // TODO: zhill, this can be added later for snapshot abort capabilities // static ConcurrentHashMap<String,HttpTransfer> httpTransferMap; //To keep track of current transfers to support aborting // Introduced for testing EUCA-9297 fix: allows artificial capacity changes of backend static boolean setUseTestingDelegateManager(boolean enableDelegate) { if (enableDelegate && !(blockManager instanceof StorageManagerTestingProxy)) { LOG.info("Switching to use delegating storage manager for testing"); blockManager = new StorageManagerTestingProxy(blockManager); } else if (!enableDelegate && (blockManager instanceof StorageManagerTestingProxy)) { LOG.info("Switching to NOT use delegating storage manager anymore"); blockManager = ((StorageManagerTestingProxy) blockManager).getDelegateStorageManager(); } return enableDelegate; } public static void configure() throws EucalyptusCloudException { BlockStorageGlobalConfiguration.getInstance(); StorageProperties.updateWalrusUrl(); StorageProperties.updateName(); StorageProperties.updateStorageHost(); try { blockManager = StorageManagers.getInstance(); if (blockManager != null) { blockManager.initialize(); } else { throw new EucalyptusCloudException("Got null block manager. Cannot configure."); } } catch (Exception e) { throw new EucalyptusCloudException(e); } } public BlockStorageController() {} // for unit testing with a mock implementation public BlockStorageController(LogicalStorageManager blockManager) { this.blockManager = blockManager; } public static void check() throws EucalyptusCloudException { blockManager.checkReady(); } public static void stop() throws EucalyptusCloudException { // Shutdown volume, snapshot, snapshot transfer and checker thread pools VolumeThreadPool.shutdown(); SnapshotThreadPool.shutdown(); SnapshotTransferThreadPool.shutdown(); CheckerThreadPool.shutdown(); if (blockManager != null) { LOG.info("Stopping blockmanager"); blockManager.stop(); } // clean all state. blockManager = null; StorageProperties.enableSnapshots = false; // swathi: what does this mean? } public static void enable() throws EucalyptusCloudException { blockManager.configure(); // blockManager.initialize(); blockManager.enable(); // run startup checks // changing this from async to sync execution so it does not accidentally pick up any new volumes/snapshots runStartUpChecks(); // Initialize volume, snapshot, snapshot transfer and checker thread pools StorageInfo info = StorageInfo.getStorageInfo(); VolumeThreadPool.initialize(info.getMaxConcurrentVolumes()); SnapshotThreadPool.initialize(info.getMaxConcurrentSnapshots()); SnapshotTransferThreadPool.initialize(info.getMaxConcurrentSnapshotTransfers()); CheckerThreadPool.initialize(); // Add checkers for volume and snapshot maintenance CheckerThreadPool.add(new VolumeDeleter(blockManager)); CheckerThreadPool.add(new FailedVolumeCleaner(blockManager)); CheckerThreadPool.add(new ExpiredVolumeCleaner()); CheckerThreadPool.add(new VolumeStateChecker(blockManager)); // TODO What is the point of this checker? CheckerThreadPool.add(new SnapshotDeleter(blockManager)); CheckerThreadPool.add(new FailedSnapshotCleaner(blockManager)); CheckerThreadPool.add(new ExpiredSnapshotCleaner()); CheckerThreadPool.add(new SnapshotTransferCleaner()); CheckerThreadPool.add(new ThreadPoolSizeUpdater()); // add any block manager checkers List<CheckerTask> backendCheckers = null; if ((backendCheckers = blockManager.getCheckers()) != null && !backendCheckers.isEmpty()) { for (CheckerTask checker : backendCheckers) { CheckerThreadPool.add(checker); } } // TODO ask neil what this means StorageProperties.enableSnapshots = StorageProperties.enableStorage = true; } public static void disable() throws EucalyptusCloudException { // Shutdown volume, snapshot, snapshot transfer and checker thread pools VolumeThreadPool.shutdown(); SnapshotThreadPool.shutdown(); SnapshotTransferThreadPool.shutdown(); CheckerThreadPool.shutdown(); blockManager.disable(); } private static void runStartUpChecks() { try { LOG.info("Initiating startup checks for block storage"); updateStuckVolumes(); updateStuckSnapshots(); } catch (Exception e) { LOG.error("Startup cleanup failed", e); } try { blockManager.startupChecks(); } catch (EucalyptusCloudException e) { LOG.error("Startup checks failed"); } } public static void updateStuckVolumes() { LOG.info("Initiating clean up of stuck EBS volumes"); try (TransactionResource tran = Entities.transactionFor(VolumeInfo.class)) { VolumeInfo volumeInfo = new VolumeInfo(); volumeInfo.setStatus(StorageProperties.Status.creating.toString()); List<VolumeInfo> volumesInCreating = Entities.query(volumeInfo, false); if (volumesInCreating != null && !volumesInCreating.isEmpty()) { for (VolumeInfo volInfo : volumesInCreating) { // Mark them as failed so that it gets reflected in the CLC // and the clean up routine picks them up later volInfo.setStatus(StorageProperties.Status.failed.toString()); } } else { LOG.info("No stuck EBS volumes found. No clean up needed"); } tran.commit(); } catch (Throwable e) { LOG.warn("Failed to clean up stuck EBS volumes", e); } } public static void updateStuckSnapshots() { LOG.info("Initiating clean up of stuck EBS snapshots"); try (TransactionResource tran = Entities.transactionFor(SnapshotInfo.class)) { SnapshotInfo snapshotInfo = new SnapshotInfo(); snapshotInfo.setStatus(StorageProperties.Status.creating.toString()); List<SnapshotInfo> snapshotsInCreating = Entities.query(snapshotInfo, false); if (snapshotsInCreating != null && !snapshotsInCreating.isEmpty()) { for (SnapshotInfo snapInfo : snapshotsInCreating) { // Mark them as failed so that it gets reflected in the CLC // and the clean up routine picks them up later snapInfo.setStatus(StorageProperties.Status.failed.toString()); snapInfo.setProgress("0"); } } else { LOG.info("No stuck EBS snapshots found. No clean up needed"); } tran.commit(); } catch (Throwable e) { LOG.warn("Failed to clean up stuck EBS snapshots", e); } } @Override public UpdateStorageConfigurationResponseType UpdateStorageConfiguration(UpdateStorageConfigurationType request) throws EucalyptusCloudException { UpdateStorageConfigurationResponseType reply = (UpdateStorageConfigurationResponseType) request.getReply(); if (ComponentIds.lookup(Eucalyptus.class).name().equals(request.getEffectiveUserId())) throw new AccessDeniedException("Only admin can change walrus properties."); // test connection to ObjectStorage StorageProperties.updateWalrusUrl(); try { blockManager.checkPreconditions(); StorageProperties.enableStorage = true; } catch (Exception ex) { StorageProperties.enableStorage = false; LOG.error(ex); } if (request.getStorageParams() != null) { for (ComponentProperty param : request.getStorageParams()) { LOG.debug("Storage Param: " + param.getDisplayName() + " Qname: " + param.getQualifiedName() + " Value: " + param.getValue()); } blockManager.setStorageProps(request.getStorageParams()); } return reply; } @Override public GetStorageConfigurationResponseType GetStorageConfiguration(GetStorageConfigurationType request) throws EucalyptusCloudException { GetStorageConfigurationResponseType reply = (GetStorageConfigurationResponseType) request.getReply(); StorageProperties.updateName(); if (ComponentIds.lookup(Eucalyptus.class).name().equals(request.getEffectiveUserId())) throw new AccessDeniedException("Only admin can change walrus properties."); if (StorageProperties.NAME.equals(request.getName())) { reply.setName(StorageProperties.NAME); ArrayList<ComponentProperty> storageParams = blockManager.getStorageProps(); reply.setStorageParams(storageParams); } return reply; } @Override public GetVolumeTokenResponseType GetVolumeToken(GetVolumeTokenType request) throws EucalyptusCloudException { GetVolumeTokenResponseType reply = (GetVolumeTokenResponseType) request.getReply(); String volumeId = request.getVolumeId(); LOG.info("Processing GetVolumeToken request for volume " + volumeId); if (null == volumeId) { LOG.error("Cannot get token for a null-valued volumeId"); throw new EucalyptusCloudException("No volumeId specified in token request"); } try (TransactionResource tran = Entities.transactionFor(VolumeInfo.class)) { VolumeInfo vol = Entities.uniqueResult(new VolumeInfo(volumeId)); VolumeToken token = vol.getOrCreateAttachmentToken(); // Encrypt the token with the NC's private key String encryptedToken = BlockStorageUtil.encryptForNode(token.getToken(), BlockStorageUtil.getPartitionForLocalService(Storage.class)); reply.setToken(encryptedToken); reply.setVolumeId(volumeId); tran.commit(); LOG.debug(reply.toSimpleString()); return reply; } catch (NoSuchElementException e) { throw new EucalyptusCloudException("Volume " + request.getVolumeId() + " not found", e); } catch (Exception e) { LOG.error("Failed to get volume token: " + e.getMessage()); throw new EucalyptusCloudException("Could not get volume token for volume " + request.getVolumeId(), e); } } /** * Removes connection authorization for the specified iqn/ip pair in the request using the specified token. Only performs the operation if the token * is valid for the specified volume. * * Invalidates the token upon successful de-authorization. * * @param request * @return * @throws EucalyptusCloudException */ @Override public UnexportVolumeResponseType UnexportVolume(UnexportVolumeType request) throws EucalyptusCloudException { final long startTime = System.currentTimeMillis(); UnexportVolumeResponseType reply = request.getReply(); final String token = request.getToken(); final String volumeId = request.getVolumeId(); final String nodeIqn = request.getIqn(); final String nodeIp = request.getIp(); LOG.info("Processing UnexportVolume request for volume " + volumeId + " from node " + nodeIp + " with iqn " + nodeIqn); VolumeInfo volumeEntity = null; VolumeToken validToken = null; try (TransactionResource tran = Entities.transactionFor(VolumeInfo.class)) { VolumeInfo foundVolume = Entities.uniqueResult(new VolumeInfo(volumeId)); volumeEntity = Entities.merge(foundVolume); try { validToken = volumeEntity.getAttachmentTokenIfValid(token); // volumeEntity.invalidateExport(tokenValue, nodeIp, nodeIqn); // Entities.flush(volumeEntity); //Sync state -- not needed.... same transaction } catch (Exception e) { LOG.error("Invalid token in request for volume " + volumeId + ". Encrypted token: " + token); throw new EucalyptusCloudException(e); } try { blockManager.unexportVolume(volumeEntity.getVolumeId(), nodeIqn); } catch (UnsupportedOperationException e) { // The backend doesn't support unexport to just one host // Check to see if the volume is exported to any other hosts if (validToken.hasOnlyExport(nodeIp, nodeIqn)) { // Either volume is exported to this host only or has no active exports , so unexport all. blockManager.unexportVolumeFromAll(volumeId); } else { // Volume may be exported to other hosts... this is a noop. LOG.info("Volume " + volumeId + ": UnexportVolume for single host not supported by backend. Treating as no-op and continuing normally."); } } catch (Exception e) { LOG.error("Could not detach volume: " + volumeId, e); throw e; } // Do the actual invalidation. Handle retries, but only on the DB part. if (!Entities.asTransaction(VolumeInfo.class, new Function<VolumeInfo, Boolean>() { @Override public Boolean apply(VolumeInfo vol) { VolumeInfo entity = Entities.merge(vol); try { entity.invalidateExport(token, nodeIp, nodeIqn); return true; } catch (Exception e) { LOG.error("Error invalidating export: " + e.getMessage()); return false; } } }).apply(volumeEntity)) { // Transaction failed after retries... LOG.error("Error invalidating the export record in the DB for volume " + volumeId); } tran.commit(); reply.set_return(true); } catch (NoSuchElementException e) { LOG.error("Volume " + volumeId + " not found in DB", e); throw new EucalyptusCloudException("Volume " + volumeId + " not found"); } catch (Exception e) { LOG.error("Failed UnexportVolume due to: " + e.getMessage(), e); throw new EucalyptusCloudException(e); } ThruputMetrics.addDataPoint(MonitoredAction.UNEXPORT_VOLUME, System.currentTimeMillis() - startTime); return reply; } /** * Perform a volume export validated by the token presented in the request. Upon completion of the Export operation, the identified host (by ip and * iqn) will have access to connect to the requested volume. No connection is made, just the authorization. * * If a valid export record exists for the given token and host information, then the connectionString for that record is returned rather than * creating a new record. * * @param request * @return * @throws EucalyptusCloudException */ @Override public ExportVolumeResponseType ExportVolume(ExportVolumeType request) throws EucalyptusCloudException { final long startTime = System.currentTimeMillis(); final ExportVolumeResponseType reply = (ExportVolumeResponseType) request.getReply(); final String volumeId = request.getVolumeId(); final String token = request.getToken(); final String ip = request.getIp(); final String iqn = request.getIqn(); reply.setVolumeId(volumeId); LOG.info("Processing ExportVolume request for volume " + volumeId); final Function<VolumeInfo, String> exportAndAttach = new Function<VolumeInfo, String>() { @Override public String apply(VolumeInfo volume) { int tokenRetry = 3; VolumeToken tokenInfo = null; VolumeInfo volEntity = Entities.merge(volume); for (int i = 0; i < tokenRetry; i++) { try { tokenInfo = volEntity.getAttachmentTokenIfValid(token); if (tokenInfo != null) { break; } } catch (Exception e) { LOG.warn("Could not check for valid token. Will retry. ", e); tokenInfo = null; } try { Thread.sleep(100); // sleep 100ms to make retry useful. } catch (InterruptedException e) { throw new RuntimeException("Token check backoff sleep interrupted", e); } } if (tokenInfo == null) { throw new RuntimeException("Cannot export, due to invalid token"); } VolumeExportRecord export = null; try { export = tokenInfo.getValidExport(ip, iqn); } catch (EucalyptusCloudException e2) { LOG.error("Failed when checking/getting valid export for " + ip + " - " + iqn); return null; } if (export == null) { String connectionString = null; try { // attachVolume must be idempotent. connectionString = blockManager.exportVolume(volumeId, iqn); } catch (Exception e) { LOG.error("Could not attach volume: " + e.getMessage()); LOG.trace("Failed volume attach", e); return null; } try { // addExport must be idempotent, if one exists a new is not added with same data tokenInfo.addExport(ip, iqn, connectionString); return connectionString; } catch (Exception e) { LOG.error("Could not export volume " + volumeId + " failed to add export record"); try { LOG.info("Unexporting volume " + volumeId + " to " + iqn + " for export failure cleanup"); blockManager.unexportVolume(volumeId, iqn); } catch (EucalyptusCloudException e1) { LOG.error("Failed to detach volume during invalidation failure", e); } return null; } } else { LOG.debug("Found extant valid export for " + ip + " and " + iqn + " returning connection information for that export"); return export.getConnectionString(); } } }; VolumeInfo searchVol = new VolumeInfo(volumeId); VolumeInfo vol = null; try (TransactionResource tran = Entities.transactionFor(VolumeInfo.class)) { vol = Entities.uniqueResult(searchVol); tran.commit(); } catch (NoSuchElementException e) { LOG.error("No volume db record found for " + volumeId, e); throw new EucalyptusCloudException("Volume not found " + volumeId); } catch (TransactionException e) { LOG.error("Failed to Export due to db error", e); throw new EucalyptusCloudException("Could not export volume", e); } // Do the export try { String connectionString = Entities.asTransaction(VolumeInfo.class, exportAndAttach).apply(vol); if (connectionString != null) { reply.setConnectionString(connectionString); } else { throw new Exception("Got null record result. Cannot set connection string"); } } catch (Exception e) { LOG.error("Failed ExportVolume transaction due to: " + e.getMessage(), e); throw new EucalyptusCloudException("Failed to add export", e); } ThruputMetrics.addDataPoint(MonitoredAction.EXPORT_VOLUME, System.currentTimeMillis() - startTime); return reply; } @Override public GetStorageVolumeResponseType GetStorageVolume(GetStorageVolumeType request) throws EucalyptusCloudException { GetStorageVolumeResponseType reply = (GetStorageVolumeResponseType) request.getReply(); if (!StorageProperties.enableStorage) { LOG.error("BlockStorage has been disabled. Please check your setup"); return reply; } String volumeId = request.getVolumeId(); LOG.info("Processing GetStorageVolume request for volume " + volumeId); try (TransactionResource tran = Entities.transactionFor(VolumeInfo.class)) { VolumeInfo volumeInfo = new VolumeInfo(); volumeInfo.setVolumeId(volumeId); List<VolumeInfo> volumeInfos = Entities.query(volumeInfo); if (volumeInfos.size() > 0) { VolumeInfo foundVolumeInfo = volumeInfos.get(0); String deviceName = blockManager.getVolumeConnectionString(volumeId); reply.setVolumeId(foundVolumeInfo.getVolumeId()); reply.setSize(foundVolumeInfo.getSize().toString()); reply.setStatus(foundVolumeInfo.getStatus()); reply.setSnapshotId(foundVolumeInfo.getSnapshotId()); if (deviceName != null) reply.setActualDeviceName(deviceName); else reply.setActualDeviceName("invalid"); } else { throw new NoSuchVolumeException(volumeId); } tran.commit(); } return reply; } @Override public DeleteStorageVolumeResponseType DeleteStorageVolume(DeleteStorageVolumeType request) throws EucalyptusCloudException { final long startTime = System.currentTimeMillis(); DeleteStorageVolumeResponseType reply = (DeleteStorageVolumeResponseType) request.getReply(); if (!StorageProperties.enableStorage) { LOG.error("BlockStorage has been disabled. Please check your setup"); return reply; } String volumeId = request.getVolumeId(); LOG.info("Processing DeleteStorageVolume request for volume " + volumeId); VolumeInfo volumeInfo = new VolumeInfo(); volumeInfo.setVolumeId(volumeId); try (TransactionResource tran = Entities.transactionFor(VolumeInfo.class)) { VolumeInfo foundVolume = Entities.uniqueResult(volumeInfo); // check its status String status = foundVolume.getStatus(); if (status == null) { throw new EucalyptusCloudException("Invalid volume status: null"); } else if (status.equals(StorageProperties.Status.available.toString())) { // Set status, for cleanup thread to find. LOG.trace("Marking volume " + volumeId + " for deletion"); ThruputMetrics.startOperation(MonitoredAction.DELETE_VOLUME, volumeId, startTime); foundVolume.setStatus(StorageProperties.Status.deleting.toString()); } else if (status.equals(StorageProperties.Status.deleting.toString()) || status.equals(StorageProperties.Status.deleted.toString()) || status.equals(StorageProperties.Status.failed.toString())) { LOG.debug("Volume " + volumeId + " already in deleting/deleted/failed. No-op for delete request."); } else { throw new EucalyptusCloudException("Cannot delete volume in state: " + status + ". Please retry later"); } // Delete operation should be idempotent as multiple attempts can be made to delete the same volume // Set the response element to true if the volume entity is found. EUCA-6093 reply.set_return(Boolean.TRUE); tran.commit(); } catch (NoSuchElementException e) { // Set the response element to false if the volume entity does not exist in the SC database // if record is not found, delete is idempotent LOG.warn("Got delete request, but unable to find volume in SC database: " + volumeId); reply.set_return(Boolean.TRUE); } catch (EucalyptusCloudException e) { LOG.error("Error marking volume " + volumeId + " for deletion: " + e.getMessage()); throw e; } catch (final Throwable e) { LOG.error("Exception looking up volume: " + volumeId, e); throw new EucalyptusCloudException(e); } return reply; } /** * Checks to see if a new snapshot of size volSize will exceed the quota * * @param volSize * @param maxSize * @return */ private boolean totalSnapshotSizeLimitExceeded(String snapshotId, int volSize, int sizeLimitGB) throws EucalyptusCloudException { int totalSnapshotSize = 0; try (TransactionResource tran = Entities.transactionFor(SnapshotInfo.class)) { Criteria query = Entities.createCriteria(SnapshotInfo.class); query.setReadOnly(true); // Only look for snaps that are not failed nor deleted ImmutableSet<String> excludedStates = ImmutableSet.of(StorageProperties.Status.failed.toString(), StorageProperties.Status.deleted.toString(), StorageProperties.Status.deletedfromebs.toString()); query.add(Restrictions.not(Restrictions.in("status", excludedStates))); // The listing may include duplicates (for snapshots cached on multiple clusters), this set ensures each unique snap id is counted only once. HashSet<String> idSet = new HashSet<String>(); List<SnapshotInfo> snapshots = (List<SnapshotInfo>) query.list(); tran.commit(); for (SnapshotInfo snap : snapshots) { totalSnapshotSize += (snap.getSizeGb() != null && idSet.add(snap.getSnapshotId()) ? snap.getSizeGb() : 0); } LOG.debug("Snapshot " + snapshotId + " checking snapshot total size of " + totalSnapshotSize + " against limit of " + sizeLimitGB); return (totalSnapshotSize + volSize) > sizeLimitGB; } catch (final Throwable e) { LOG.error("Error finding total snapshot used size " + e.getMessage()); throw new EucalyptusCloudException("Failed to check snapshot total size limit", e); } } @Override public CreateStorageSnapshotResponseType CreateStorageSnapshot(CreateStorageSnapshotType request) throws EucalyptusCloudException { final long actionStart = System.currentTimeMillis(); CreateStorageSnapshotResponseType reply = (CreateStorageSnapshotResponseType) request.getReply(); StorageProperties.updateWalrusUrl(); if (!StorageProperties.enableSnapshots) { LOG.error("Snapshots have been disabled. Please check connection to ObjectStorage."); return reply; } String volumeId = request.getVolumeId(); LOG.info("Processing CreateStorageSnapshot request for volume " + volumeId); String snapshotId = request.getSnapshotId(); VolumeInfo sourceVolumeInfo = null; try (TransactionResource tran = Entities.transactionFor(VolumeInfo.class)) { VolumeInfo volumeInfo = new VolumeInfo(volumeId); sourceVolumeInfo = Entities.uniqueResult(volumeInfo); tran.commit(); } catch (NoSuchElementException e) { LOG.debug("Volume " + volumeId + " not found in db"); throw new NoSuchVolumeException(volumeId); } catch (final Throwable e) { LOG.warn("Volume " + volumeId + " error getting info from db. May not exist. " + e.getMessage()); throw new EucalyptusCloudException("Could not get volume information for volume " + volumeId, e); } if (sourceVolumeInfo == null) { // Another check to be sure that we have the source volume throw new NoSuchVolumeException(volumeId); } else { // check status if (!sourceVolumeInfo.getStatus().equals(StorageProperties.Status.available.toString())) { throw new VolumeNotReadyException(volumeId); } else { ThruputMetrics.startOperation(MonitoredAction.CREATE_SNAPSHOT, snapshotId, actionStart); // create snapshot if (StorageProperties.shouldEnforceUsageLimits) { int maxSize = -1; try { maxSize = BlockStorageGlobalConfiguration.getInstance().getGlobal_total_snapshot_size_limit_gb(); } catch (Exception e) { LOG.error("Could not determine max global snapshot limit. Aborting snapshot creation", e); throw new EucalyptusCloudException("Total size limit not found.", e); } if (maxSize <= 0) { LOG.warn("Total global snapshot size limit is less than or equal to 0"); throw new EucalyptusCloudException("Total snapshot size limit is less than or equal to 0"); } if (totalSnapshotSizeLimitExceeded(snapshotId, sourceVolumeInfo.getSize(), maxSize)) { LOG.info("Snapshot " + snapshotId + " exceeds global total snapshot size limit of " + maxSize + "GB (see storage.global_total_snapshot_size_limit_gb configurable property)"); throw new SnapshotTooLargeException(maxSize); } } SnapshotCreator snapshotter = null; SnapshotInfo snapshotInfo = new SnapshotInfo(snapshotId); try { snapshotInfo.setUserName(sourceVolumeInfo.getUserName()); snapshotInfo.setVolumeId(volumeId); snapshotInfo.setProgress("0"); snapshotInfo.setSizeGb(sourceVolumeInfo.getSize()); snapshotInfo.setStatus(StorageProperties.Status.creating.toString()); LOG.debug("Snapshot " + snapshotId + " set to 'creating' state"); snapshotInfo.setIsOrigin(Boolean.TRUE); /* Change to support sync snap consistency point set on CLC round-trip */ /* * Always do this operation. On backends that don't support it they will return null. In that case it is effectively a no-op and we continue * normal async snapshot. * * If the snap point is set, then we update the DB properly. */ String snapPointId = null; try { // Only allow 'n' snapshot point creation operations concurrently, where 'n' is the // eucalyptus property [ZONE].storage.maxconcurrentsnapshots. If 'n' are already running, // block until one frees. // If this property is changed during runtime, then eucalyptus-cloud must be restarted // for the change to take effect. // Note this is not the entire snapshot process, only taking the snapshot on the back end, // which should normally be fast, so OK to block in this synchronous data path. try { snapshotPointSemaphoreSupplier.get().acquire(); LOG.trace("Acquired semaphore for BlockStorageController createSnapshotPoint. Remaining permits = " + snapshotPointSemaphoreSupplier.get().availablePermits()); } catch (InterruptedException ex) { throw new EucalyptusCloudException("Failed to create snapshot point " + snapshotId + " on volume " + volumeId + " as the semaphore could not be acquired"); } try { // This will be a no-op if the backend doesn't support it. Will return null. snapPointId = blockManager.createSnapshotPoint(volumeId, snapshotId); } finally { LOG.trace("Releasing semaphore for BlockStorageController createSnapshotPoint"); snapshotPointSemaphoreSupplier.get().release(); } // Start time is the time of snapshot point creation snapshotInfo.setStartTime(new Date()); if (snapPointId == null) { LOG.debug("Synchronous snap point not supported for this backend. Cleanly skipped."); } else { snapshotInfo.setSnapPointId(snapPointId); } // Do a commit here because the snapshotter expects to find db entry. snapshotInfo.setStatus(StorageProperties.Status.creating.toString()); // Persist the snapshot metadata to db try (TransactionResource tran = Entities.transactionFor(SnapshotInfo.class)) { Entities.persist(snapshotInfo); tran.commit(); } catch (Exception e) { LOG.warn("Unable to persist metadata for snapshot " + snapshotId, e); throw e; } Context ctx = null; try { ctx = Contexts.lookup(request.getCorrelationId()); if (!ctx.getChannel().isOpen()) { throw new NoSuchContextException("Channel is closed"); } } catch (NoSuchContextException e) { if (snapPointId != null) { // Other end hung up, mark this as failed since this is a sync operation throw new EucalyptusCloudException("Channel closed, aborting snapshot."); } } } catch (EucalyptusCloudException e) { // If the snapshot was done but took too long then delete the snap and fail the op. try { blockManager.deleteSnapshotPoint(volumeId, snapshotId, snapPointId); } catch (Exception ex) { LOG.error("Snapshot " + snapshotId + " exception on snap point cleanup after failure: " + e.getMessage()); } LOG.error("Snapshot " + snapshotId + " failed to create snap point successfully: " + e.getMessage()); throw e; } /* Resume old code path and finish the snapshot process if already started */ // snapshot asynchronously snapshotter = new SnapshotCreator(volumeId, snapshotId, snapPointId, blockManager); reply.setSnapshotId(snapshotId); reply.setVolumeId(volumeId); reply.setStartTime(DateUtils.format(snapshotInfo.getStartTime().getTime(), DateUtils.ISO8601_DATETIME_PATTERN) + ".000Z"); reply.setProgress(snapshotInfo.getProgress()); } catch (EucalyptusCloudException cloudEx) { snapshotInfo.setStatus(StorageProperties.Status.failed.toString()); markSnapshotFailed(snapshotId); LOG.error("Snapshot " + snapshotId + " creation failed with exception ", cloudEx); throw cloudEx; } catch (final Throwable e) { snapshotInfo.setStatus(StorageProperties.Status.failed.toString()); markSnapshotFailed(snapshotId); LOG.error("Snapshot " + snapshotId + " Error committing state update to failed", e); throw new EucalyptusCloudException("Snapshot " + snapshotId + " unexpected throwable exception caught", e); } reply.setStatus(snapshotInfo.getStatus()); if (snapshotter != null) { // Kick off the snapshotter task after persisting snapshot to database try { SnapshotThreadPool.add(snapshotter); } catch (Exception e) { LOG.warn("Failed to add creation task for " + snapshotId + " to asynchronous thread pool", e); // Mark the snapshot as failed markSnapshotFailed(snapshotId); throw new EucalyptusCloudException("Failed to add creation task for " + snapshotId + " to asynchronous thread pool", e); } } } } return reply; } private void markSnapshotFailed(String snapshotId) { try { Entities.asTransaction(SnapshotInfo.class, SNAPSHOT_FAILED).apply(snapshotId); } catch (Throwable t) { LOG.warn("Unable to update status to failed for " + snapshotId, t); } } // returns snapshots in progress or at the SC @Override public DescribeStorageSnapshotsResponseType DescribeStorageSnapshots(DescribeStorageSnapshotsType request) throws EucalyptusCloudException { DescribeStorageSnapshotsResponseType reply = (DescribeStorageSnapshotsResponseType) request.getReply(); // checker.transferPendingSnapshots(); List<String> snapshotSet = request.getSnapshotSet(); ArrayList<SnapshotInfo> snapshotInfos = new ArrayList<SnapshotInfo>(); try (TransactionResource tran = Entities.transactionFor(SnapshotInfo.class)) { if ((snapshotSet != null) && !snapshotSet.isEmpty()) { for (String snapshotSetEntry : snapshotSet) { SnapshotInfo snapshotInfo = new SnapshotInfo(snapshotSetEntry); List<SnapshotInfo> foundSnapshotInfos = Entities.query(snapshotInfo); if (foundSnapshotInfos.size() > 0) { snapshotInfos.add(foundSnapshotInfos.get(0)); } } } else { SnapshotInfo snapshotInfo = new SnapshotInfo(); List<SnapshotInfo> foundSnapshotInfos = Entities.query(snapshotInfo); for (SnapshotInfo snapInfo : foundSnapshotInfos) { snapshotInfos.add(snapInfo); } } ArrayList<StorageSnapshot> snapshots = reply.getSnapshotSet(); for (SnapshotInfo snapshotInfo : snapshotInfos) { snapshots.add(convertSnapshotInfo(snapshotInfo)); // Getting rid of failed snapshot cleanup - EUCA-10803 // if (snapshotInfo.getStatus().equals(StorageProperties.Status.failed.toString())) // checker.cleanFailedSnapshot(snapshotInfo.getSnapshotId()); } tran.commit(); } return reply; } /** * Delete snapshot in idempotent way. Multiple requests for same snapshotId should return true. Only return false if the snapsnot *cannot* be * deleted but does exist * * @param request * @return * @throws EucalyptusCloudException */ @Override public DeleteStorageSnapshotResponseType DeleteStorageSnapshot(DeleteStorageSnapshotType request) throws EucalyptusCloudException { final long startTime = System.currentTimeMillis(); DeleteStorageSnapshotResponseType reply = (DeleteStorageSnapshotResponseType) request.getReply(); StorageProperties.updateWalrusUrl(); if (!StorageProperties.enableSnapshots) { LOG.error("Snapshots have been disabled. Please check connection to ObjectStorage."); return reply; } String snapshotId = request.getSnapshotId(); LOG.info("Processing DeleteStorageSnapshot request for snapshot " + snapshotId); try (TransactionResource tran = Entities.transactionFor(SnapshotInfo.class)) { SnapshotInfo snapshotInfo = Entities.uniqueResult(new SnapshotInfo(snapshotId)); String status = snapshotInfo.getStatus(); if (status.equals(StorageProperties.Status.available.toString())) { snapshotInfo.setStatus(StorageProperties.Status.deleting.toString()); LOG.debug("Snapshot " + snapshotId + " set to 'deleting' state"); ThruputMetrics.startOperation(MonitoredAction.DELETE_SNAPSHOT, snapshotId, startTime); } else if (status.equals(StorageProperties.Status.deleting.toString()) || status.equals(StorageProperties.Status.deleted.toString()) || status.equals(StorageProperties.Status.deletedfromebs.toString()) || status.equals(StorageProperties.Status.failed.toString())) { LOG.debug("Snapshot " + snapshotId + " already in deleting/deleted/failed. No-op for delete request."); } else { // snapshot is still in progress. throw new SnapshotInUseException("Cannot delete snapshot in state: " + status + ". Please retry later"); } reply.set_return(Boolean.TRUE); tran.commit(); } catch (NoSuchElementException e) { // the SC knows nothing about this snapshot, either never existed or was deleted // For idempotent behavior, tell backend to delete and return true LOG.info("Got delete request, but unable to find snapshot in database: " + snapshotId + ". May have already been deleted in another zone."); reply.set_return(Boolean.TRUE); } catch (TransactionException e) { LOG.error("Exception looking up snapshot: " + snapshotId, e); throw new EucalyptusCloudException(e); } return reply; } /* * TODO: zhill, removed this since it isn't necessary, but can be added-back later when we have time for full dev and testing public * AbortStorageSnapshotResponseType AbortSnapshotPoint( AbortStorageSnapshotType request ) throws EucalyptusCloudException { * AbortStorageSnapshotResponseType reply = ( AbortStorageSnapshotResponseType ) request.getReply(); String snapshotId = request.getSnapshotId(); * reply.set_return(true); * * try (TransactionResource tr = Entities.transactionFor(SnapshotInfo.class)) { SnapshotInfo foundSnapshotInfo = Entities.uniqueResult(new * SnapshotInfo(snapshotId)); String status = foundSnapshotInfo.getStatus(); if(status.equals(StorageProperties.Status.available.toString()) || * status.equals(StorageProperties.Status.failed.toString())) { foundSnapshotInfo.setStatus(StorageProperties.Status.deleting.toString()); * tr.commit(); } else { //snapshot is still in progress. foundSnapshotInfo.setStatus(StorageProperties.Status.failed.toString()); tr.commit(); * checker.cleanFailedSnapshot(snapshotId); } } catch (NoSuchElementException e) { //the SC knows nothing about this snapshot. LOG.debug("Snapshot " * + snapshotId + " not found"); } catch (Exception e) { LOG.error("Failed to abort snapshot " + snapshotId, e); throw new * EucalyptusCloudException("Failed to abort snapshot " + snapshotId, e) } * * return reply; } */ @Override public CreateStorageVolumeResponseType CreateStorageVolume(CreateStorageVolumeType request) throws EucalyptusCloudException { final long actionStart = System.currentTimeMillis(); CreateStorageVolumeResponseType reply = (CreateStorageVolumeResponseType) request.getReply(); if (!StorageProperties.enableStorage) { LOG.error("BlockStorage has been disabled. Please check your setup"); return reply; } String snapshotId = request.getSnapshotId(); String parentVolumeId = request.getParentVolumeId(); String userId = request.getUserId(); String volumeId = request.getVolumeId(); LOG.info("Processing CreateStorageVolume request for volume " + volumeId); // in GB String size = request.getSize(); int sizeAsInt = (size != null) ? Integer.parseInt(size) : 0; if (size != null && sizeAsInt <= 0) { throw new InvalidParameterValueException("The parameter size (" + sizeAsInt + ") must be greater than zero."); } if (StorageProperties.shouldEnforceUsageLimits) { if (size != null) { int totalVolumeSize = 0; VolumeInfo volInfo = new VolumeInfo(); try (TransactionResource tran = Entities.transactionFor(VolumeInfo.class)) { List<VolumeInfo> volInfos = Entities.query(volInfo); for (VolumeInfo vInfo : volInfos) { if (!vInfo.getStatus().equals(StorageProperties.Status.failed.toString()) && !vInfo.getStatus().equals(StorageProperties.Status.deleted.toString())) { totalVolumeSize += vInfo.getSize(); } } tran.commit(); } if (((totalVolumeSize + sizeAsInt) > StorageInfo.getStorageInfo().getMaxTotalVolumeSizeInGb())) { throw new VolumeSizeExceededException(volumeId, "Total Volume Size Limit Exceeded"); } if (sizeAsInt > StorageInfo.getStorageInfo().getMaxVolumeSizeInGB()) { throw new VolumeSizeExceededException(volumeId, "Max Volume Size Limit Exceeded"); } } } try (TransactionResource tran = Entities.transactionFor(VolumeInfo.class)) { VolumeInfo volumeInfo = new VolumeInfo(volumeId); List<VolumeInfo> volumeInfos = Entities.query(volumeInfo); if (volumeInfos.size() > 0) { throw new VolumeAlreadyExistsException(volumeId); } if (snapshotId != null) { SnapshotInfo snapInfo = new SnapshotInfo(snapshotId); snapInfo.setScName(null); snapInfo.setStatus(StorageProperties.Status.available.toString()); List<SnapshotInfo> snapInfos = Entities.query(snapInfo); if (snapInfos.size() == 0) { throw new SnapshotNotFoundException("Snapshot " + snapshotId + " does not exist or is unavailable"); } volumeInfo.setSnapshotId(snapshotId); reply.setSnapshotId(snapshotId); } volumeInfo.setUserName(userId); volumeInfo.setSize(sizeAsInt); volumeInfo.setStatus(StorageProperties.Status.creating.toString()); LOG.debug("Volume " + volumeId + " set to 'creating' state"); Date creationDate = new Date(); volumeInfo.setCreateTime(creationDate); Entities.persist(volumeInfo); reply.setVolumeId(volumeId); reply.setCreateTime(DateUtils.format(creationDate.getTime(), DateUtils.ISO8601_DATETIME_PATTERN) + ".000Z"); reply.setSize(size); reply.setStatus(volumeInfo.getStatus()); tran.commit(); } try { // create volume asynchronously VolumeCreator volumeCreator = new VolumeCreator(volumeId, "snapset", snapshotId, parentVolumeId, sizeAsInt, blockManager); ThruputMetrics.startOperation(snapshotId != null ? MonitoredAction.CREATE_VOLUME_FROM_SNAPSHOT : MonitoredAction.CREATE_VOLUME, volumeId, actionStart); VolumeThreadPool.add(volumeCreator); } catch (Exception e) { LOG.warn("Failed to add creation task for " + volumeId + " to asynchronous thread pool", e); // Mark the volume as failed try { Function<String, VolumeInfo> updateFunction = new Function<String, VolumeInfo>() { @Override public VolumeInfo apply(String arg0) { VolumeInfo vol; try { vol = Entities.uniqueResult(new VolumeInfo(arg0)); vol.setStatus(StorageProperties.Status.failed.toString()); LOG.debug("Volume " + arg0 + " set to 'failed' state"); return vol; } catch (TransactionException | NoSuchElementException e) { LOG.warn("Failed to retrieve DB entity for " + arg0, e); } return null; } }; Entities.asTransaction(VolumeInfo.class, updateFunction).apply(volumeId); } catch (Exception e1) { LOG.warn("Unable to update status for " + volumeId + " after failure to add creation task to asynchronous thread pool", e); } throw new EucalyptusCloudException("Failed to add creation task for " + volumeId + " to asynchronous thread pool", e); } return reply; } @Override public DescribeStorageVolumesResponseType DescribeStorageVolumes(DescribeStorageVolumesType request) throws EucalyptusCloudException { DescribeStorageVolumesResponseType reply = (DescribeStorageVolumesResponseType) request.getReply(); List<String> volumeSet = request.getVolumeSet(); ArrayList<VolumeInfo> volumeInfos = new ArrayList<VolumeInfo>(); try (TransactionResource tran = Entities.transactionFor(VolumeInfo.class)) { if ((volumeSet != null) && !volumeSet.isEmpty()) { for (String volumeSetEntry : volumeSet) { VolumeInfo volumeInfo = new VolumeInfo(volumeSetEntry); List<VolumeInfo> foundVolumeInfos = Entities.query(volumeInfo); if (foundVolumeInfos.size() > 0) { volumeInfos.add(foundVolumeInfos.get(0)); } } } else { VolumeInfo volumeInfo = new VolumeInfo(); List<VolumeInfo> foundVolumeInfos = Entities.query(volumeInfo); for (VolumeInfo volInfo : foundVolumeInfos) { volumeInfos.add(volInfo); } } ArrayList<StorageVolume> volumes = reply.getVolumeSet(); for (VolumeInfo volumeInfo : volumeInfos) { volumes.add(convertVolumeInfo(volumeInfo)); // Getting rid of failed volume cleanup - EUCA-10803 // if (volumeInfo.getStatus().equals(StorageProperties.Status.failed.toString()) // && (System.currentTimeMillis() - volumeInfo.getLastUpdateTimestamp().getTime() > StorageProperties.FAILED_STATE_CLEANUP_THRESHOLD_MS)) { // LOG.warn("Failed volume, cleaning it: " + volumeInfo.getVolumeId()); // checker.cleanFailedVolume(volumeInfo.getVolumeId()); // } } tran.commit(); } return reply; } @Override public ConvertVolumesResponseType ConvertVolumes(ConvertVolumesType request) throws EucalyptusCloudException { ConvertVolumesResponseType reply = (ConvertVolumesResponseType) request.getReply(); String provider = request.getOriginalProvider(); provider = "com.eucalyptus.storage." + provider; if (!blockManager.getClass().getName().equals(provider)) { // different backend provider. Try upgrade try { LogicalStorageManager fromBlockManager = (LogicalStorageManager) ClassLoader.getSystemClassLoader().loadClass(provider).newInstance(); fromBlockManager.checkPreconditions(); // initialize fromBlockManager new VolumesConvertor(fromBlockManager, blockManager).start(); } catch (InstantiationException e) { LOG.error(e); throw new EucalyptusCloudException(e); } catch (ClassNotFoundException e) { LOG.error(e); throw new EucalyptusCloudException(e); } catch (IllegalAccessException e) { LOG.error(e); throw new EucalyptusCloudException(e); } } return reply; } /** * This should no longer be called/invoked directly... * * @param request * @return * @throws EucalyptusCloudException */ @Override public AttachStorageVolumeResponseType attachVolume(AttachStorageVolumeType request) throws EucalyptusCloudException { throw new EucalyptusCloudException("Operation not supported"); } @Override public DetachStorageVolumeResponseType detachVolume(DetachStorageVolumeType request) throws EucalyptusCloudException { DetachStorageVolumeResponseType reply = request.getReply(); String volumeId = request.getVolumeId(); LOG.info("Processing DetachVolume request for volume " + volumeId); // Do the work. try { LOG.info("Detaching volume " + volumeId + " from all hosts"); Entities.asTransaction(VolumeInfo.class, invalidateAndDetachAll()).apply(volumeId); } catch (final Exception e) { LOG.error("Failed to fully detach volume " + volumeId); reply.set_return(false); } return reply; } private static Function<String, VolumeInfo> invalidateAndDetachAll() { final Predicate<VolumeToken> invalidateExports = new Predicate<VolumeToken>() { @Override public boolean apply(VolumeToken volToken) { VolumeToken tokenEntity = Entities.merge(volToken); try { tokenEntity.invalidateAllExportsAndToken(); return true; } catch (Exception e) { LOG.error("Failed invalidating exports for token " + tokenEntity.getToken()); return false; } } }; // Could save cycles by statically setting all of these functions that don't require closures so they are not // constructed for each request. return new Function<String, VolumeInfo>() { @Override public VolumeInfo apply(String volumeId) { try { VolumeInfo volumeEntity = Entities.uniqueResult(new VolumeInfo(volumeId)); try { LOG.debug("Invalidating all tokens and all exports for " + volumeId); // Invalidate all tokens and exports and forcibly detach. if (!Iterables.all(volumeEntity.getAttachmentTokens(), invalidateExports)) { // At least one failed. LOG.error("Failed to invalidate all tokens and exports"); } } catch (Exception e) { LOG.error("Error invalidating tokens", e); } try { LOG.debug("Unexporting volume " + volumeId + " from all hosts"); blockManager.unexportVolumeFromAll(volumeId); } catch (EucalyptusCloudException ex) { LOG.error("Detaching volume " + volumeId + " from all hosts failed", ex); } } catch (NoSuchElementException e) { LOG.error("Cannot force detach of volume " + volumeId + " because it is not found in database"); return null; } catch (TransactionException e) { LOG.error("Failed to lookup volume " + volumeId); } return null; } }; } private StorageVolume convertVolumeInfo(VolumeInfo volInfo) throws EucalyptusCloudException { StorageVolume volume = new StorageVolume(); String volumeId = volInfo.getVolumeId(); volume.setVolumeId(volumeId); volume.setStatus(volInfo.getStatus()); volume.setCreateTime(DateUtils.format(volInfo.getCreateTime().getTime(), DateUtils.ISO8601_DATETIME_PATTERN) + ".000Z"); volume.setSize(String.valueOf(volInfo.getSize())); volume.setSnapshotId(volInfo.getSnapshotId()); VolumeToken tok = volInfo.getCurrentValidToken(); if (tok != null) { volume.setActualDeviceName(BlockStorageUtil.encryptForNode(tok.getToken(), BlockStorageUtil.getPartitionForLocalService(Storage.class))); } else { // use 'invalid' to indicate no export? invalid seems okay since there is no valid device unless a token is valid volume.setActualDeviceName("invalid"); } return volume; } private StorageSnapshot convertSnapshotInfo(SnapshotInfo snapInfo) { StorageSnapshot snapshot = new StorageSnapshot(); snapshot.setVolumeId(snapInfo.getVolumeId()); snapshot.setStatus(StorageProperties.Status.deletedfromebs.toString().equals(snapInfo.getStatus()) ? StorageProperties.Status.deleted.toString() : snapInfo.getStatus()); snapshot.setSnapshotId(snapInfo.getSnapshotId()); String progress = snapInfo.getProgress(); progress = progress != null ? progress + "%" : progress; snapshot.setProgress(progress); snapshot.setStartTime(DateUtils.format(snapInfo.getStartTime().getTime(), DateUtils.ISO8601_DATETIME_PATTERN) + ".000Z"); return snapshot; } @Override public CloneVolumeResponseType CloneVolume(CloneVolumeType request) throws EucalyptusCloudException { CloneVolumeResponseType reply = request.getReply(); CreateStorageVolumeType createStorageVolume = new CreateStorageVolumeType(); createStorageVolume.setParentVolumeId(request.getVolumeId()); CreateStorageVolume(createStorageVolume); return reply; } }