Java tutorial
/************************************************************************* * Copyright 2009-2012 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; 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/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, 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. ************************************************************************/ package edu.ucsb.eucalyptus.cloud.ws; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import javax.persistence.EntityTransaction; import org.apache.log4j.Logger; import org.apache.tools.ant.util.DateUtils; import org.hibernate.Criteria; import org.hibernate.criterion.Example; import org.hibernate.criterion.Projections; import org.hibernate.criterion.Restrictions; import org.rhq.helpers.pluginAnnotations.agent.Units; import com.eucalyptus.blockstorage.Volume; 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.EntityWrapper; import com.eucalyptus.entities.TransactionException; import com.eucalyptus.entities.Transactions; import com.eucalyptus.event.ListenerRegistry; import com.eucalyptus.reporting.event.SnapShotEvent; import com.eucalyptus.storage.BlockStorageChecker; import com.eucalyptus.storage.CheckerTask; import com.eucalyptus.storage.LogicalStorageManager; import com.eucalyptus.storage.StorageManagers; import com.eucalyptus.util.EucalyptusCloudException; import com.eucalyptus.util.StorageProperties; import edu.ucsb.eucalyptus.cloud.AccessDeniedException; import edu.ucsb.eucalyptus.cloud.EntityTooLargeException; import edu.ucsb.eucalyptus.cloud.NoSuchEntityException; 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.cloud.entities.SnapshotInfo; import edu.ucsb.eucalyptus.cloud.entities.StorageInfo; import edu.ucsb.eucalyptus.cloud.entities.VolumeInfo; import edu.ucsb.eucalyptus.cloud.entities.WalrusInfo; import edu.ucsb.eucalyptus.msgs.AttachStorageVolumeResponseType; import edu.ucsb.eucalyptus.msgs.AttachStorageVolumeType; import edu.ucsb.eucalyptus.msgs.CloneVolumeResponseType; import edu.ucsb.eucalyptus.msgs.CloneVolumeType; import edu.ucsb.eucalyptus.msgs.ComponentProperty; import edu.ucsb.eucalyptus.msgs.ConvertVolumesResponseType; import edu.ucsb.eucalyptus.msgs.ConvertVolumesType; import edu.ucsb.eucalyptus.msgs.CreateStorageSnapshotResponseType; import edu.ucsb.eucalyptus.msgs.CreateStorageSnapshotType; import edu.ucsb.eucalyptus.msgs.CreateStorageVolumeResponseType; import edu.ucsb.eucalyptus.msgs.CreateStorageVolumeType; import edu.ucsb.eucalyptus.msgs.DeleteStorageSnapshotResponseType; import edu.ucsb.eucalyptus.msgs.DeleteStorageSnapshotType; import edu.ucsb.eucalyptus.msgs.DeleteStorageVolumeResponseType; import edu.ucsb.eucalyptus.msgs.DeleteStorageVolumeType; import edu.ucsb.eucalyptus.msgs.DescribeStorageSnapshotsResponseType; import edu.ucsb.eucalyptus.msgs.DescribeStorageSnapshotsType; import edu.ucsb.eucalyptus.msgs.DescribeStorageVolumesResponseType; import edu.ucsb.eucalyptus.msgs.DescribeStorageVolumesType; import edu.ucsb.eucalyptus.msgs.DetachStorageVolumeResponseType; import edu.ucsb.eucalyptus.msgs.DetachStorageVolumeType; import edu.ucsb.eucalyptus.msgs.GetStorageConfigurationResponseType; import edu.ucsb.eucalyptus.msgs.GetStorageConfigurationType; import edu.ucsb.eucalyptus.msgs.GetStorageVolumeResponseType; import edu.ucsb.eucalyptus.msgs.GetStorageVolumeType; import edu.ucsb.eucalyptus.msgs.StorageSnapshot; import edu.ucsb.eucalyptus.msgs.StorageVolume; import edu.ucsb.eucalyptus.msgs.UpdateStorageConfigurationResponseType; import edu.ucsb.eucalyptus.msgs.UpdateStorageConfigurationType; import edu.ucsb.eucalyptus.storage.StorageCheckerService; import edu.ucsb.eucalyptus.util.EucaSemaphore; import edu.ucsb.eucalyptus.util.EucaSemaphoreDirectory; import edu.ucsb.eucalyptus.util.SystemUtil; import edu.ucsb.eucalyptus.util.SystemUtil.CommandOutput; public class BlockStorage { private static Logger LOG = Logger.getLogger(BlockStorage.class); static LogicalStorageManager blockManager; static BlockStorageChecker checker; static VolumeService volumeService; static SnapshotService snapshotService; static StorageCheckerService checkerService; //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 public static Random randomGenerator = new Random(); public static void configure() throws EucalyptusCloudException { 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); } checker = new BlockStorageChecker(blockManager); volumeService = new VolumeService(); snapshotService = new SnapshotService(); checkerService = new StorageCheckerService(); //httpTransferMap = new ConcurrentHashMap<String, HttpTransfer>(); } public BlockStorage() { } private static void startupChecks() throws EucalyptusCloudException { if (checker != null) { checker.startupChecks(); } } public static void checkPending() { if (checker != null) { StorageProperties.updateWalrusUrl(); try { checker.transferPendingSnapshots(); } catch (Exception ex) { LOG.error("unable to transfer pending snapshots", ex); } } } public static void check() throws EucalyptusCloudException { blockManager.checkReady(); } public static void stop() throws EucalyptusCloudException { if (blockManager != null) { LOG.info("Stopping blockmanager"); blockManager.stop(); } //clean all state. blockManager = null; checker = null; if (volumeService != null) { volumeService.shutdown(); } if (snapshotService != null) { snapshotService.shutdown(); } if (checkerService != null) { checkerService.shutdown(); } StorageProperties.enableSnapshots = StorageProperties.enableStorage = false; } public static void enable() throws EucalyptusCloudException { blockManager.configure(); //blockManager.initialize(); try { startupChecks(); } catch (EucalyptusCloudException ex) { LOG.error("Startup checks failed ", ex); } blockManager.enable(); checkerService.add(new VolumeStateChecker(blockManager)); //add any block manager checkers for (CheckerTask checker : blockManager.getCheckers()) { checkerService.add(checker); } StorageProperties.enableSnapshots = StorageProperties.enableStorage = true; } public static void disable() throws EucalyptusCloudException { blockManager.disable(); } public static void addChecker(CheckerTask checkerTask) { if (checkerService != null) { checkerService.add(checkerTask); } } 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 Walrus 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; } 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; } 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(); EntityWrapper<VolumeInfo> db = StorageProperties.getEntityWrapper(); VolumeInfo volumeInfo = new VolumeInfo(); volumeInfo.setVolumeId(volumeId); List<VolumeInfo> volumeInfos = db.query(volumeInfo); if (volumeInfos.size() > 0) { VolumeInfo foundVolumeInfo = volumeInfos.get(0); String deviceName = blockManager.getVolumeProperty(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 { db.rollback(); throw new NoSuchVolumeException(volumeId); } db.commit(); return reply; } public DeleteStorageVolumeResponseType DeleteStorageVolume(DeleteStorageVolumeType request) throws EucalyptusCloudException { DeleteStorageVolumeResponseType reply = (DeleteStorageVolumeResponseType) request.getReply(); if (!StorageProperties.enableStorage) { LOG.error("BlockStorage has been disabled. Please check your setup"); return reply; } String volumeId = request.getVolumeId(); EntityWrapper<VolumeInfo> db = StorageProperties.getEntityWrapper(); VolumeInfo volumeInfo = new VolumeInfo(); volumeInfo.setVolumeId(volumeId); List<VolumeInfo> volumeList = db.query(volumeInfo); //always return true. reply.set_return(Boolean.TRUE); if (volumeList.size() > 0) { VolumeInfo foundVolume = volumeList.get(0); //check its status String status = foundVolume.getStatus(); if (status.equals(StorageProperties.Status.available.toString()) || status.equals(StorageProperties.Status.failed.toString())) { VolumeDeleter volumeDeleter = new VolumeDeleter(volumeId); volumeService.add(volumeDeleter); } } db.commit(); return reply; } /** * Checks to see if a new snapshot of size volSize will exceed the quota * @param volSize * @return */ private boolean totalSnapshotSizeLimitExceeded(String snapshotId, int volSize) throws EucalyptusCloudException { int totalSnapshotSize = 0; EntityTransaction dbTrans = Entities.get(SnapshotInfo.class); Criteria query = Entities.createCriteria(SnapshotInfo.class); query.setReadOnly(true); //TODO: zhill, fix this logic by adding a size value to the snapshot record, should do the calculation on the DB // this will be very poor for performance as the number of snapshots increases. //Only look for snaps that are not failed and not error query.add(Restrictions .not(Restrictions.and(Restrictions.eq("status", StorageProperties.Status.failed.toString()), Restrictions.eq("status", StorageProperties.Status.error.toString())))); try { List<SnapshotInfo> snapInfos = (List<SnapshotInfo>) query.list(); for (SnapshotInfo sInfo : snapInfos) { try { totalSnapshotSize += blockManager.getSnapshotSize(sInfo.getSnapshotId()); } catch (EucalyptusCloudException e) { LOG.error(e); } } int sizeLimitGB = WalrusInfo.getWalrusInfo().getStorageMaxTotalSnapshotSizeInGb(); 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); } finally { if (dbTrans.isActive()) { dbTrans.rollback(); } } } public CreateStorageSnapshotResponseType CreateStorageSnapshot(CreateStorageSnapshotType request) throws EucalyptusCloudException { CreateStorageSnapshotResponseType reply = (CreateStorageSnapshotResponseType) request.getReply(); StorageProperties.updateWalrusUrl(); if (!StorageProperties.enableSnapshots) { LOG.error("Snapshots have been disabled. Please check connection to Walrus."); return reply; } String volumeId = request.getVolumeId(); String snapshotId = request.getSnapshotId(); EntityTransaction dbTrans = Entities.get(VolumeInfo.class); VolumeInfo sourceVolumeInfo = null; try { VolumeInfo volumeInfo = new VolumeInfo(volumeId); sourceVolumeInfo = Entities.uniqueResult(volumeInfo); dbTrans.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); } finally { if (dbTrans.isActive()) { dbTrans.rollback(); } dbTrans = null; } 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 { //create snapshot if (StorageProperties.shouldEnforceUsageLimits && totalSnapshotSizeLimitExceeded(snapshotId, sourceVolumeInfo.getSize())) { LOG.info("Snapshot " + snapshotId + " exceeds total snapshot size limit of " + WalrusInfo.getWalrusInfo().getStorageMaxTotalSnapshotSizeInGb()); throw new EntityTooLargeException(snapshotId); } SnapshotInfo snapshotInfo = new SnapshotInfo(snapshotId); EntityTransaction snapTrans = Entities.get(SnapshotInfo.class); Date startTime = new Date(); try { snapshotInfo.setUserName(sourceVolumeInfo.getUserName()); snapshotInfo.setVolumeId(volumeId); snapshotInfo.setStartTime(startTime); snapshotInfo.setProgress("0"); snapshotInfo.setStatus(StorageProperties.Status.creating.toString()); /* 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 { //This will be a no-op if the backend doesn't support it. Will return null. snapPointId = blockManager.createSnapshotPoint(volumeId, snapshotId); 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()); 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; } finally { Entities.persist(snapshotInfo); } /* Resume old code path and finish the snapshot process if already started */ //snapshot asynchronously String snapshotSet = "snapset-" + UUID.randomUUID(); Snapshotter snapshotter = new Snapshotter(snapshotSet, volumeId, snapshotId, snapPointId); snapshotService.add(snapshotter); reply.setSnapshotId(snapshotId); reply.setVolumeId(volumeId); reply.setStartTime( DateUtils.format(startTime.getTime(), DateUtils.ISO8601_DATETIME_PATTERN) + ".000Z"); reply.setProgress(snapshotInfo.getProgress()); } catch (EucalyptusCloudException cloudEx) { snapshotInfo.setStatus(StorageProperties.Status.failed.toString()); LOG.error("Snapshot " + snapshotId + " creation failed with exception ", cloudEx); throw cloudEx; } catch (final Throwable e) { snapshotInfo.setStatus(StorageProperties.Status.failed.toString()); LOG.error("Snapshot " + snapshotId + " Error committing state update to failed", e); throw new EucalyptusCloudException( "Snapshot " + snapshotId + " unexpected throwable exception caught", e); } finally { if (snapTrans.isActive()) { snapTrans.commit(); } snapTrans = null; } reply.setStatus(snapshotInfo.getStatus()); } } return reply; } //returns snapshots in progress or at the SC 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>(); EntityWrapper<SnapshotInfo> db = StorageProperties.getEntityWrapper(); if ((snapshotSet != null) && !snapshotSet.isEmpty()) { for (String snapshotSetEntry : snapshotSet) { SnapshotInfo snapshotInfo = new SnapshotInfo(snapshotSetEntry); List<SnapshotInfo> foundSnapshotInfos = db.query(snapshotInfo); if (foundSnapshotInfos.size() > 0) { snapshotInfos.add(foundSnapshotInfos.get(0)); } } } else { SnapshotInfo snapshotInfo = new SnapshotInfo(); List<SnapshotInfo> foundSnapshotInfos = db.query(snapshotInfo); for (SnapshotInfo snapInfo : foundSnapshotInfos) { snapshotInfos.add(snapInfo); } } ArrayList<StorageSnapshot> snapshots = reply.getSnapshotSet(); for (SnapshotInfo snapshotInfo : snapshotInfos) { snapshots.add(convertSnapshotInfo(snapshotInfo)); if (snapshotInfo.getStatus().equals(StorageProperties.Status.failed.toString())) checker.cleanFailedSnapshot(snapshotInfo.getSnapshotId()); } db.commit(); return reply; } public DeleteStorageSnapshotResponseType DeleteStorageSnapshot(DeleteStorageSnapshotType request) throws EucalyptusCloudException { DeleteStorageSnapshotResponseType reply = (DeleteStorageSnapshotResponseType) request.getReply(); StorageProperties.updateWalrusUrl(); if (!StorageProperties.enableSnapshots) { LOG.error("Snapshots have been disabled. Please check connection to Walrus."); return reply; } String snapshotId = request.getSnapshotId(); EntityWrapper<SnapshotInfo> db = StorageProperties.getEntityWrapper(); SnapshotInfo snapshotInfo = new SnapshotInfo(snapshotId); List<SnapshotInfo> snapshotInfos = db.query(snapshotInfo); reply.set_return(true); if (snapshotInfos.size() > 0) { SnapshotInfo foundSnapshotInfo = snapshotInfos.get(0); String status = foundSnapshotInfo.getStatus(); db.commit(); if (status.equals(StorageProperties.Status.available.toString()) || status.equals(StorageProperties.Status.failed.toString())) { SnapshotDeleter snapshotDeleter = new SnapshotDeleter(snapshotId); snapshotService.add(snapshotDeleter); } else { //snapshot is still in progress. reply.set_return(false); throw new SnapshotInUseException(snapshotId); } } else { //the SC knows nothing about this snapshot. db.rollback(); } 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(); EntityWrapper<SnapshotInfo> db = StorageProperties.getEntityWrapper(); SnapshotInfo snapshotInfo = new SnapshotInfo(snapshotId); List<SnapshotInfo> snapshotInfos = db.query(snapshotInfo); reply.set_return(true); if(snapshotInfos.size() > 0) { SnapshotInfo foundSnapshotInfo = snapshotInfos.get(0); String status = foundSnapshotInfo.getStatus(); if(status.equals(StorageProperties.Status.available.toString()) || status.equals(StorageProperties.Status.failed.toString())) { SnapshotDeleter snapshotDeleter = new SnapshotDeleter(snapshotId); snapshotService.add(snapshotDeleter); } else { //snapshot is still in progress. foundSnapshotInfo.setStatus(StorageProperties.Status.failed.toString()); db.commit(); if(httpTransferMap.contains(snapshotId)) { LOG.info("Aborting http transfer for " + snapshotId); httpTransferMap.get(snapshotId).abortTransfer(); } } } else { //the SC knows nothing about this snapshot. db.rollback(); } return reply; }*/ public void DeleteWalrusSnapshot(String snapshotId) { HttpWriter httpWriter = new HttpWriter("DELETE", "snapset", snapshotId, "DeleteWalrusSnapshot", null); try { httpWriter.run(); } catch (Exception ex) { LOG.error(ex); } } public CreateStorageVolumeResponseType CreateStorageVolume(CreateStorageVolumeType request) throws EucalyptusCloudException { 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(); //in GB String size = request.getSize(); int sizeAsInt = 0; if (StorageProperties.shouldEnforceUsageLimits) { if (size != null) { int totalVolumeSize = 0; VolumeInfo volInfo = new VolumeInfo(); volInfo.setStatus(StorageProperties.Status.available.toString()); EntityWrapper<VolumeInfo> db = StorageProperties.getEntityWrapper(); List<VolumeInfo> volInfos = db.query(volInfo); for (VolumeInfo vInfo : volInfos) { totalVolumeSize += vInfo.getSize(); } db.rollback(); sizeAsInt = Integer.parseInt(size); 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"); } } } EntityWrapper<VolumeInfo> db = StorageProperties.getEntityWrapper(); VolumeInfo volumeInfo = new VolumeInfo(volumeId); List<VolumeInfo> volumeInfos = db.query(volumeInfo); if (volumeInfos.size() > 0) { db.rollback(); throw new VolumeAlreadyExistsException(volumeId); } if (snapshotId != null) { SnapshotInfo snapInfo = new SnapshotInfo(snapshotId); snapInfo.setScName(null); snapInfo.setStatus(StorageProperties.Status.available.toString()); EntityWrapper<SnapshotInfo> dbSnap = db.recast(SnapshotInfo.class); List<SnapshotInfo> snapInfos = dbSnap.query(snapInfo); if (snapInfos.size() == 0) { db.rollback(); throw new NoSuchEntityException("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()); Date creationDate = new Date(); volumeInfo.setCreateTime(creationDate); db.add(volumeInfo); reply.setVolumeId(volumeId); reply.setCreateTime(DateUtils.format(creationDate.getTime(), DateUtils.ISO8601_DATETIME_PATTERN) + ".000Z"); reply.setSize(size); reply.setStatus(volumeInfo.getStatus()); db.commit(); //create volume asynchronously VolumeCreator volumeCreator = new VolumeCreator(volumeId, "snapset", snapshotId, parentVolumeId, sizeAsInt); volumeService.add(volumeCreator); return reply; } /* Make sure snapDestination is NOT a bare block device */ private String getSnapshot(String snapshotId) throws EucalyptusCloudException { if (!StorageProperties.enableSnapshots) { LOG.error("Snapshot functionality disabled. Please check connection to Walrus"); throw new EucalyptusCloudException("could not connect to Walrus."); } /*if(snapDestination.startsWith("/dev/")) { throw new EucalyptusCloudException("Cannot get snapshot directly to block device: " + snapDestination); }*/ String snapshotLocation = "snapshots" + "/" + snapshotId; String tmpUncompressedFileName = null; String tmpCompressedFileName = null; File tmpCompressedFile = null; int retry = 0; int maxRetry = 5; do { tmpUncompressedFileName = StorageProperties.storageRootDirectory + File.separator + snapshotId + "-" + String.valueOf(randomGenerator.nextInt()); tmpCompressedFileName = tmpUncompressedFileName + ".gz"; tmpCompressedFile = new File(tmpCompressedFileName); } while (tmpCompressedFile.exists() && retry++ < maxRetry); // This should be *very* rare if (retry >= maxRetry) { // Nothing to clean up at this point throw new EucalyptusCloudException( "Could not get a temporary file for snapshot download after " + maxRetry + " attempts"); } // Download the snapshot from walrus try { HttpReader snapshotGetter = new HttpReader(snapshotLocation, null, tmpCompressedFile, "GetWalrusSnapshot", ""); snapshotGetter.run(); } catch (Exception ex) { // Cleanup the compressed snapshot cleanupFile(tmpCompressedFile); throw new EucalyptusCloudException("Failed to download snapshot " + snapshotId + " from Walrus", ex); } // Uncompress the snapshot and move it to the right location try { CommandOutput output = SystemUtil .runWithRawOutput(new String[] { "/bin/gunzip", tmpCompressedFile.getAbsolutePath() }); if (output.returnValue != 0) { throw new EucalyptusCloudException( "Failed to uncompress snapshot " + snapshotId + " due to: " + output.error); } } catch (Exception ex) { if (ex instanceof EucalyptusCloudException) { throw (EucalyptusCloudException) ex; } else { throw new EucalyptusCloudException("Failed to uncompress and/or move snapshot " + snapshotId, ex); } } finally { // Cleanup the compressed snapshot cleanupFile(tmpCompressedFile); } return tmpUncompressedFileName; // LOG.info("Downloading snapshot " + snapshotId + " from Walrus to " + snapDestination); // String snapshotLocation = "snapshots" + "/" + snapshotId; // String absoluteSnapshotPath = snapDestination; // File file = new File(absoluteSnapshotPath); // HttpReader snapshotGetter = new HttpReader(snapshotLocation, null, file, "GetWalrusSnapshot", "", true, blockManager.getStorageRootDirectory()); // snapshotGetter.run(); } private void cleanupFile(String fileName) { try { cleanupFile(new File(fileName)); } catch (Exception e) { LOG.error("Failed to delete file", e); } } private void cleanupFile(File file) { if (file != null && file.exists()) { try { file.delete(); } catch (Exception e) { LOG.error("Failed to delete file", e); } } } /* * Does a check of the snapshot's status as reflected in the DB. */ private static boolean isSnapshotMarkedFailed(String snapshotId) { EntityTransaction db = Entities.get(SnapshotInfo.class); db.setRollbackOnly(); try { SnapshotInfo snap = Entities.uniqueResult(new SnapshotInfo(snapshotId)); if (snap != null && StorageProperties.Status.failed.toString().equals(snap.getStatus())) { return true; } } catch (Exception e) { LOG.error("Error determining status of snapshot " + snapshotId); } finally { db.rollback(); } return false; } private int getSnapshotSize(String snapshotId) throws EucalyptusCloudException { StorageProperties.updateWalrusUrl(); if (!StorageProperties.enableSnapshots) { LOG.error("Snapshot functionality disabled. Please check connection to Walrus"); throw new EucalyptusCloudException("could not connect to Walrus."); } String snapshotLocation = "snapshots" + "/" + snapshotId; HttpReader snapshotGetter = new HttpReader(snapshotLocation, null, null, "GetWalrusSnapshotSize", ""); int size = Integer.parseInt(snapshotGetter.getResponseHeader("SnapshotSize")); return size; } public DescribeStorageVolumesResponseType DescribeStorageVolumes(DescribeStorageVolumesType request) throws EucalyptusCloudException { DescribeStorageVolumesResponseType reply = (DescribeStorageVolumesResponseType) request.getReply(); List<String> volumeSet = request.getVolumeSet(); ArrayList<VolumeInfo> volumeInfos = new ArrayList<VolumeInfo>(); EntityWrapper<VolumeInfo> db = StorageProperties.getEntityWrapper(); if ((volumeSet != null) && !volumeSet.isEmpty()) { for (String volumeSetEntry : volumeSet) { VolumeInfo volumeInfo = new VolumeInfo(volumeSetEntry); List<VolumeInfo> foundVolumeInfos = db.query(volumeInfo); if (foundVolumeInfos.size() > 0) { volumeInfos.add(foundVolumeInfos.get(0)); } } } else { VolumeInfo volumeInfo = new VolumeInfo(); List<VolumeInfo> foundVolumeInfos = db.query(volumeInfo); for (VolumeInfo volInfo : foundVolumeInfos) { volumeInfos.add(volInfo); } } ArrayList<StorageVolume> volumes = reply.getVolumeSet(); for (VolumeInfo volumeInfo : volumeInfos) { volumes.add(convertVolumeInfo(volumeInfo)); if (volumeInfo.getStatus().equals(StorageProperties.Status.failed.toString())) { LOG.warn("Volume looks like it has failed removing it: " + volumeInfo.getVolumeId()); checker.cleanFailedVolume(volumeInfo.getVolumeId()); } } db.commit(); return reply; } 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).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; } public AttachStorageVolumeResponseType attachVolume(AttachStorageVolumeType request) throws EucalyptusCloudException { AttachStorageVolumeResponseType reply = request.getReply(); String volumeId = request.getVolumeId(); ArrayList<String> nodeIqns = request.getNodeIqns(); EntityWrapper<VolumeInfo> db = StorageProperties.getEntityWrapper(); try { VolumeInfo volumeInfo = db.getUnique(new VolumeInfo(volumeId)); } catch (EucalyptusCloudException ex) { LOG.error("Unable to find volume: " + volumeId + ex); throw new NoSuchEntityException("Unable to find volume: " + volumeId + ex); } finally { db.commit(); } try { String deviceName = blockManager.attachVolume(volumeId, nodeIqns); reply.setRemoteDeviceString(deviceName); } catch (EucalyptusCloudException ex) { LOG.error(ex, ex); throw ex; } return reply; } public DetachStorageVolumeResponseType detachVolume(DetachStorageVolumeType request) throws EucalyptusCloudException { DetachStorageVolumeResponseType reply = request.getReply(); String volumeId = request.getVolumeId(); String nodeIqn = request.getNodeIqn(); EntityWrapper<VolumeInfo> db = StorageProperties.getEntityWrapper(); try { VolumeInfo volumeInfo = db.getUnique(new VolumeInfo(volumeId)); } catch (EucalyptusCloudException ex) { LOG.error("Unable to find volume: " + volumeId + ex); throw new NoSuchEntityException("Unable to find volume: " + volumeId + ex); } finally { db.commit(); } try { blockManager.detachVolume(volumeId, nodeIqn); } catch (EucalyptusCloudException ex) { throw ex; } return reply; } 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()); String deviceName = blockManager.getVolumeProperty(volumeId); if (deviceName != null) volume.setActualDeviceName(deviceName); else volume.setActualDeviceName("invalid"); return volume; } private StorageSnapshot convertSnapshotInfo(SnapshotInfo snapInfo) { StorageSnapshot snapshot = new StorageSnapshot(); snapshot.setVolumeId(snapInfo.getVolumeId()); snapshot.setStatus(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; } public abstract class SnapshotTask implements Runnable { } public abstract class VolumeTask implements Runnable { } public class Snapshotter extends SnapshotTask { private String volumeId; private String snapshotId; private String volumeBucket; private String snapshotFileName; private String snapPointId; /** * Initializes the Snapshotter task. snapPointId should be null if no snap point has been created yet. * @param volumeBucket * @param volumeId * @param snapshotId * @param snapPointId */ public Snapshotter(String volumeBucket, String volumeId, String snapshotId, String snapPointId) { this.volumeBucket = volumeBucket; this.volumeId = volumeId; this.snapshotId = snapshotId; this.snapPointId = snapPointId; } @Override public void run() { EucaSemaphore semaphore = EucaSemaphoreDirectory.getSolitarySemaphore(volumeId); try { try { semaphore.acquire(); } catch (InterruptedException ex) { throw new EucalyptusCloudException("semaphore could not be acquired"); } Boolean shouldTransferSnapshots = true; List<String> returnValues = null; try { //Check to ensure that a failed/cancellation has not be set if (!isSnapshotMarkedFailed(snapshotId)) { shouldTransferSnapshots = StorageInfo.getStorageInfo().getShouldTransferSnapshots(); returnValues = blockManager.createSnapshot(this.volumeId, this.snapshotId, this.snapPointId, shouldTransferSnapshots); } else { throw new EucalyptusCloudException( "Snapshot " + this.snapshotId + " marked as failed by another thread"); } } finally { semaphore.release(); } if (shouldTransferSnapshots) { if (returnValues.size() < 2) { throw new EucalyptusCloudException("Unable to transfer snapshot"); } snapshotFileName = returnValues.get(0); String snapshotSize = returnValues.get(1); int retry = StorageInfo.getStorageInfo().getMaxSnapTransferRetries(); boolean transferSuccess = false; //Use exponential backoff for retries int backoffTime = 1; while (!transferSuccess) { if (!isSnapshotMarkedFailed(snapshotId)) { try { //Keep checking to see if failed state has been set by another thread transferSnapshot(snapshotSize); transferSuccess = true; } catch (Exception e) { LOG.warn("Transfer failed. Retrying"); if (retry > 0) { retry--; Thread.sleep(backoffTime * 1000); backoffTime = backoffTime * 2; } else { //Use retry counter so that we can include this exception info throw new EucalyptusCloudException("Snapshot transfer failed", e); } } } else { throw new EucalyptusCloudException( "Snapshot " + this.snapshotId + " marked as failed by another thread"); } } try { blockManager.finishVolume(snapshotId); } catch (EucalyptusCloudException ex) { blockManager.cleanSnapshot(snapshotId); LOG.error(ex); } } SnapshotInfo snapInfo = new SnapshotInfo(snapshotId); SnapshotInfo snapshotInfo = null; EntityWrapper<SnapshotInfo> db = StorageProperties.getEntityWrapper(); try { snapshotInfo = db.getUnique(snapInfo); snapshotInfo.setStatus(StorageProperties.Status.available.toString()); snapshotInfo.setProgress("100"); snapshotInfo.setSnapPointId(null); //remove snap point Id since it is no longer valid } catch (EucalyptusCloudException e) { LOG.error(e); } finally { db.commit(); } if (snapshotInfo != null) { //Fire the event to indicate the usage for reporting try { final int snapshotSize = blockManager.getSnapshotSize(snapshotInfo.getSnapshotId()); final String volumeUuid = Transactions.find(Volume.named(null, volumeId)).getNaturalId(); ListenerRegistry.getInstance() .fireEvent(SnapShotEvent.with( SnapShotEvent.forSnapShotCreate(snapshotSize, volumeUuid, volumeId), snapshotInfo.getNaturalId(), snapshotInfo.getSnapshotId(), snapshotInfo.getUserName())); // snapshot info user name is user id } catch (final Throwable e) { LOG.error(e, e); } } } catch (Exception ex) { try { LOG.error("Disconnecting snapshot " + snapshotId + " on failed snapshot attempt"); blockManager.finishVolume(snapshotId); } catch (EucalyptusCloudException e1) { LOG.error("Disconnecting snapshot " + snapshotId + " on failed snapshot attempt", e1); blockManager.cleanSnapshot(snapshotId); } SnapshotInfo snapInfo = new SnapshotInfo(snapshotId); EntityWrapper<SnapshotInfo> db = StorageProperties.getEntityWrapper(); try { SnapshotInfo snapshotInfo = db.getUnique(snapInfo); snapshotInfo.setStatus(StorageProperties.Status.failed.toString()); } catch (EucalyptusCloudException e) { LOG.error(e); } finally { db.commit(); } LOG.error(ex); } } private void transferSnapshot(String sizeAsString) throws EucalyptusCloudException { long size = Long.parseLong(sizeAsString); File snapshotFile = new File(snapshotFileName); assert (snapshotFile.exists()); //do a little test to check if we can read from it FileInputStream snapInStream = null; try { snapInStream = new FileInputStream(snapshotFile); byte[] bytes = new byte[1024]; if (snapInStream.read(bytes) <= 0) { throw new EucalyptusCloudException("Unable to read snapshot file: " + snapshotFileName); } } catch (FileNotFoundException e) { throw new EucalyptusCloudException(e); } catch (IOException e) { throw new EucalyptusCloudException(e); } finally { if (snapInStream != null) { try { snapInStream.close(); } catch (IOException e) { throw new EucalyptusCloudException(e); } } } SnapshotProgressCallback callback = new SnapshotProgressCallback(snapshotId, size, StorageProperties.TRANSFER_CHUNK_SIZE); Map<String, String> httpParamaters = new HashMap<String, String>(); HttpWriter httpWriter; httpWriter = new HttpWriter("PUT", snapshotFile, sizeAsString, callback, volumeBucket, snapshotId, "StoreSnapshot", null, httpParamaters); //TODO: zhill, leaving this in for future use if we add snapshot abort capability //Add to the map to make available for cancellation asynchronously //httpTransferMap.put(snapshotId, httpWriter); try { httpWriter.run(); } catch (Exception ex) { LOG.error(ex, ex); checker.cleanFailedSnapshot(snapshotId); } /*Part of snap abort if added * finally { //httpTransferMap.remove(snapshotId); }*/ } } private class SnapshotDeleter extends SnapshotTask { private String snapshotId; public SnapshotDeleter(String snapshotId) { this.snapshotId = snapshotId; } @Override public void run() { try { blockManager.deleteSnapshot(snapshotId); } catch (EucalyptusCloudException e1) { LOG.error(e1); return; } SnapshotInfo snapInfo = new SnapshotInfo(snapshotId); EntityWrapper<SnapshotInfo> db = StorageProperties.getEntityWrapper(); SnapshotInfo foundSnapshotInfo; try { foundSnapshotInfo = db.getUnique(snapInfo); db.delete(foundSnapshotInfo); db.commit(); } catch (EucalyptusCloudException e) { db.rollback(); LOG.error(e); return; } HttpWriter httpWriter = new HttpWriter("DELETE", "snapset", snapshotId, "DeleteWalrusSnapshot", null); try { httpWriter.run(); } catch (EucalyptusCloudException ex) { LOG.error(ex); } } } public class VolumeCreator extends VolumeTask { private String volumeId; private String snapshotId; private String parentVolumeId; private int size; public VolumeCreator(String volumeId, String snapshotSetName, String snapshotId, String parentVolumeId, int size) { this.volumeId = volumeId; this.snapshotId = snapshotId; this.parentVolumeId = parentVolumeId; this.size = size; } // DO NOT throw any exceptions from cleaning routines. Log the errors and move on private void cleanFailedSnapshot(String snapshotId) { if (snapshotId == null) return; LOG.debug("Disconnecting and cleaning local snapshot after failed snapshot transfer: " + snapshotId); try { blockManager.finishVolume(snapshotId); } catch (Exception e) { LOG.error("Error finishing failed snapshot " + snapshotId, e); } finally { try { blockManager.cleanSnapshot(snapshotId); } catch (Exception e) { LOG.error("Error deleting failed snapshot " + snapshotId, e); } } } @Override public void run() { boolean success = true; if (snapshotId != null) { EntityWrapper<SnapshotInfo> db = StorageProperties.getEntityWrapper(); try { SnapshotInfo snapshotInfo = new SnapshotInfo(snapshotId); List<SnapshotInfo> foundSnapshotInfos = db.query(snapshotInfo); if (foundSnapshotInfos.size() == 0) { // Close DB connection. Dont want concurrent threads that could be waiting around for a semaphore below to keep the DB connection open db.commit(); // This SC may not have the snapshot record in its DB, synchronize the snapshot setup EucaSemaphore semaphore = EucaSemaphoreDirectory.getSolitarySemaphore(snapshotId); try { semaphore.acquire(); // Reopen the db connection, it was closed previously. Check if a concurrent thread already setup the snapshot for us db = StorageProperties.getEntityWrapper(); foundSnapshotInfos = db.query(snapshotInfo); if (foundSnapshotInfos.size() == 0) { db.commit(); // This SC definitely does not have a record of the snapshot in its DB. Check for the snpahsot on the storage backend. // Clusters/zones/partitions may be connected to the same storage backend in which case snapshot does not have to be downloaded // from Walrus. int walrusSnapSize = getSnapshotSize(snapshotId); if (!blockManager.getFromBackend(snapshotId, walrusSnapSize)) { // Snapshot does not exist on the backend. Needs to be downloaded from Walrus. /* START: Snapshot preparation on storage back end */ String tmpSnapshotFileName = null; try { // Download the snapshot from walrus and find out the size tmpSnapshotFileName = getSnapshot(snapshotId); File snapFile = new File(tmpSnapshotFileName); if (!snapFile.exists()) { throw new EucalyptusCloudException( "Unable to find snapshot " + snapshotId + "on SC"); } long actualSnapSizeInMB = (long) Math .ceil((double) snapFile.length() / StorageProperties.MB); try { // Allocates the necessary resources on the backend String snapDestination = blockManager.prepareSnapshot(snapshotId, walrusSnapSize, actualSnapSizeInMB); if (snapDestination != null) { // Check if the destination is a block device if (snapDestination.startsWith("/dev/")) { CommandOutput output = SystemUtil.runWithRawOutput( new String[] { StorageProperties.EUCA_ROOT_WRAPPER, "dd", "if=" + tmpSnapshotFileName, "of=" + snapDestination, "bs=" + StorageProperties.blockSize }); LOG.debug("Output of dd command: " + output.error); if (output.returnValue != 0) { throw new EucalyptusCloudException( "Failed to copy the snapshot to the right location due to: " + output.error); } cleanupFile(tmpSnapshotFileName); } else { // Rename file if (!snapFile.renameTo(new File(snapDestination))) { throw new EucalyptusCloudException( "Failed to rename the snapshot"); } } // Finish the snapshot blockManager.finishVolume(snapshotId); /* * if(snapDestination.startsWith("/dev/")) { //Destination is a block device, so use a temp file first String * tmpFileName = StorageProperties.storageRootDirectory + File.pathSeparator + snapshotId + "-" + * Hashes.getRandom(8); File tmpFile = null; try { tmpFile = new File(tmpFileName); int retry = 0; int maxRetry * = 5; while(tmpFile.exists() && retry++ < maxRetry) { LOG.debug("Temporary file " + tmpFileName + * " for snapshot download already exists, trying another. Retry " + retry + " of " + maxRetry); tmpFileName = * StorageProperties.storageRootDirectory + File.pathSeparator + snapshotId + "-" + Hashes.getRandom(8); tmpFile * = new File(tmpFileName); } * * //This should be *very* rare if(retry >= maxRetry) { throw new * EucalyptusCloudException("Could not get a temporary file for snapshot download after " + maxRetry + * " attempts"); } * * getSnapshot(snapshotId, tmpFileName); * * * //Copy file to block device if(SystemUtil.runAndGetCode(new * String[]{"dd","if=",tmpFileName,"of=",snapDestination,"bs=",StorageProperties.blockSize}) < 0) { throw new * EucalyptusCloudException("Failed to copy the downloaded snapshot in " + tmpFileName + " to block device " + * snapDestination); } } catch(Exception e) { LOG.error("Failure downloading snapshot " + snapshotId); * cleanFailedSnapshotDownload(snapshotId); throw new EucalyptusCloudException("Snapshot " + snapshotId + * " download and transfer to block device " + snapDestination + " failed.", e); } finally { //Delete the temp * file no matter what try { if(tmpFile != null && tmpFile.exists()) { tmpFile.delete(); } } catch(Exception e) * { LOG.error("Could not delete temporary file " + tmpFileName + * " during cleanup of failed volume creation from snapshot"); } } } else { */ // Download the snapshot from walrus and set it up on the storage device // try { // getSnapshot(snapshotId, snapDestination, sizeExpected); // } catch (EucalyptusCloudException e) { // LOG.error("Failed to get snapshot " + snapshotId + " from Walrus. Now cleaning up."); // cleanFailedSnapshotDownload(snapshotId); // throw e; // } // } } else { LOG.warn("Block Manager replied that " + snapshotId + " not on backend, but snapshot preparation indicated that the snapshot is already present"); } } catch (Exception ex) { LOG.error("Failed to prepare the snapshot " + snapshotId + " on SAN. Now cleaning up (snapshot on SAN)", ex); cleanFailedSnapshot(snapshotId); throw ex; } } catch (Exception ex) { LOG.error("Failed to prepare the snapshot " + snapshotId + " on the storage backend. Now cleaning up (snapshot on SC)", ex); cleanupFile(tmpSnapshotFileName); throw ex; } /* END: Snapshot preparation on storage back end */ } else { // Snapshot does exist on the backend, no prepping required! Just create a record of it for this partition in the DB and get // going! } db = StorageProperties.getEntityWrapper(); snapshotInfo = new SnapshotInfo(snapshotId); //snapshotInfo.setVolumeId(volumeId); snapshotInfo.setProgress("100"); snapshotInfo.setStartTime(new Date()); snapshotInfo.setStatus(StorageProperties.Status.available.toString()); db.add(snapshotInfo); db.commit(); // This should not be synchronized. Concurrent threads should only wait for snapshot setup to complete // size = blockManager.createVolume(volumeId, snapshotId, size); // leave the snapshot even on failure here } else { // This condition is hit when concurrent threads try to create a volume from a snapshot that did not exist on this SC // according to the first DB query. However, one of the concurrent threads might have finished the setup and hence a snapshot is // available to this thread SnapshotInfo foundSnapshotInfo = foundSnapshotInfos.get(0); if (!StorageProperties.Status.available.toString() .equals(foundSnapshotInfo.getStatus())) { success = false; db.rollback(); LOG.warn("snapshot " + foundSnapshotInfo.getSnapshotId() + " not available."); } else { db.commit(); // This should not be synchronized // size = blockManager.createVolume(volumeId, snapshotId, size); } } } catch (InterruptedException ex) { throw new EucalyptusCloudException("semaphore could not be acquired"); } finally { try { semaphore.release(); } finally { EucaSemaphoreDirectory.removeSemaphore(snapshotId); } } // Create the volume from the snapshot, this can happen in parallel. size = blockManager.createVolume(volumeId, snapshotId, size); } else { // Snapshot does exist on this SC. Repeated logic, fix it! SnapshotInfo foundSnapshotInfo = foundSnapshotInfos.get(0); if (!StorageProperties.Status.available.toString().equals(foundSnapshotInfo.getStatus())) { success = false; db.rollback(); LOG.warn("snapshot " + foundSnapshotInfo.getSnapshotId() + " not available."); } else { db.commit(); size = blockManager.createVolume(volumeId, snapshotId, size); } } } catch (Exception ex) { success = false; LOG.error(ex); } } else { //Not a snapshot-based volume create. try { if (parentVolumeId != null) { //Clone the parent volume. blockManager.cloneVolume(volumeId, parentVolumeId); } else { //Create a regular empty volume blockManager.createVolume(volumeId, size); } } catch (Exception ex) { success = false; LOG.error(ex, ex); } } //Create the necessary database entries for the newly created volume. EntityWrapper<VolumeInfo> db = StorageProperties.getEntityWrapper(); VolumeInfo volumeInfo = new VolumeInfo(volumeId); try { VolumeInfo foundVolumeInfo = db.getUnique(volumeInfo); if (foundVolumeInfo != null) { if (success) { //Check resource constraints, if thresholds are exceeded, fail the operation if (StorageProperties.shouldEnforceUsageLimits) { int totalVolumeSize = 0; VolumeInfo volInfo = new VolumeInfo(); volInfo.setStatus(StorageProperties.Status.available.toString()); List<VolumeInfo> volInfos = db.query(volInfo); for (VolumeInfo vInfo : volInfos) { totalVolumeSize += vInfo.getSize(); } if ((totalVolumeSize + size) > StorageInfo.getStorageInfo().getMaxTotalVolumeSizeInGb() || (size > StorageInfo.getStorageInfo().getMaxVolumeSizeInGB())) { LOG.error("Max Total Volume size limit exceeded creating " + volumeId + ". Removing volume and cancelling operation"); db.commit(); checker.cleanFailedVolume(volumeId); return; } } foundVolumeInfo.setStatus(StorageProperties.Status.available.toString()); } else { foundVolumeInfo.setStatus(StorageProperties.Status.failed.toString()); } if (snapshotId != null) { foundVolumeInfo.setSize(size); } } else { throw new EucalyptusCloudException(); } db.commit(); } catch (EucalyptusCloudException ex) { db.rollback(); LOG.error(ex); } } } public class VolumeDeleter extends VolumeTask { private String volumeId; public VolumeDeleter(String volumeId) { this.volumeId = volumeId; } @Override public void run() { try { blockManager.deleteVolume(volumeId); } catch (EucalyptusCloudException e1) { LOG.error(e1); } EntityWrapper<VolumeInfo> db = StorageProperties.getEntityWrapper(); VolumeInfo foundVolume; try { foundVolume = db.getUnique(new VolumeInfo(volumeId)); db.delete(foundVolume); db.commit(); EucaSemaphoreDirectory.removeSemaphore(volumeId); } catch (EucalyptusCloudException e) { db.rollback(); } } } public class VolumesConvertor extends Thread { private LogicalStorageManager fromBlockManager; public VolumesConvertor(LogicalStorageManager fromBlockManager) { this.fromBlockManager = fromBlockManager; } @Override public void run() { //This is a heavy weight operation. It must execute atomically. //All other volume operations are forbidden when a conversion is in progress. synchronized (blockManager) { StorageProperties.enableStorage = StorageProperties.enableSnapshots = false; EntityWrapper<VolumeInfo> db = StorageProperties.getEntityWrapper(); VolumeInfo volumeInfo = new VolumeInfo(); volumeInfo.setStatus(StorageProperties.Status.available.toString()); List<VolumeInfo> volumeInfos = db.query(volumeInfo); List<VolumeInfo> volumes = new ArrayList<VolumeInfo>(); volumes.addAll(volumeInfos); SnapshotInfo snapInfo = new SnapshotInfo(); snapInfo.setStatus(StorageProperties.Status.available.toString()); EntityWrapper<SnapshotInfo> dbSnap = db.recast(SnapshotInfo.class); List<SnapshotInfo> snapshotInfos = dbSnap.query(snapInfo); List<SnapshotInfo> snapshots = new ArrayList<SnapshotInfo>(); snapshots.addAll(snapshotInfos); db.commit(); for (VolumeInfo volume : volumes) { String volumeId = volume.getVolumeId(); try { LOG.info("Converting volume: " + volumeId + " please wait..."); String volumePath = fromBlockManager.getVolumePath(volumeId); blockManager.importVolume(volumeId, volumePath, volume.getSize()); fromBlockManager.finishVolume(volumeId); LOG.info("Done converting volume: " + volumeId); } catch (Exception ex) { LOG.error(ex); try { blockManager.deleteVolume(volumeId); } catch (EucalyptusCloudException e1) { LOG.error(e1); } //this one failed, continue processing the rest } } for (SnapshotInfo snap : snapshots) { String snapshotId = snap.getSnapshotId(); try { LOG.info("Converting snapshot: " + snapshotId + " please wait..."); String snapPath = fromBlockManager.getSnapshotPath(snapshotId); int size = fromBlockManager.getSnapshotSize(snapshotId); blockManager.importSnapshot(snapshotId, snap.getVolumeId(), snapPath, size); fromBlockManager.finishVolume(snapshotId); LOG.info("Done converting snapshot: " + snapshotId); } catch (Exception ex) { LOG.error(ex); try { blockManager.deleteSnapshot(snapshotId); } catch (EucalyptusCloudException e1) { LOG.error(e1); } //this one failed, continue processing the rest } } LOG.info("Conversion complete"); StorageProperties.enableStorage = StorageProperties.enableSnapshots = true; } } } public CloneVolumeResponseType CloneVolume(CloneVolumeType request) throws EucalyptusCloudException { CloneVolumeResponseType reply = request.getReply(); CreateStorageVolumeType createStorageVolume = new CreateStorageVolumeType(); createStorageVolume.setParentVolumeId(request.getVolumeId()); CreateStorageVolumeResponseType createStorageVolumeResponse = CreateStorageVolume(createStorageVolume); return reply; } }