/******************************************************************************* *Copyright (c) 2009 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, only version 3 of the License. * * * This file is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see <http://www.gnu.org/licenses/>. * * Please contact Eucalyptus Systems, Inc., 130 Castilian * Dr., Goleta, CA 93101 USA or visit <http://www.eucalyptus.com/licenses/> * if you need additional information or have any questions. * * This file may incorporate work covered under the following copyright and * permission notice: * * Software License Agreement (BSD License) * * Copyright (c) 2008, Regents of the University of California * All rights reserved. * * Redistribution and use of this software in source and binary forms, with * or without modification, are permitted provided that the following * conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. USERS OF * THIS SOFTWARE ACKNOWLEDGE THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE * LICENSED MATERIAL, COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS * SOFTWARE, AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING * IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, SANTA * BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, WHICH IN * THE REGENTS’ DISCRETION MAY INCLUDE, WITHOUT LIMITATION, REPLACEMENT * OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO IDENTIFIED, OR * WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT NEEDED TO COMPLY WITH * ANY SUCH LICENSES OR RIGHTS. *******************************************************************************/ /* * * Author: chris grzegorczyk <grze@eucalyptus.com> */ package com.eucalyptus.blockstorage; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import org.apache.log4j.Logger; import com.eucalyptus.auth.crypto.Crypto; import com.eucalyptus.cluster.Cluster; import com.eucalyptus.cluster.Clusters; import com.eucalyptus.cluster.VmInstance; import com.eucalyptus.cluster.VmInstances; import com.eucalyptus.cluster.callback.VolumeAttachCallback; import com.eucalyptus.cluster.callback.VolumeDetachCallback; import com.eucalyptus.config.Configuration; import com.eucalyptus.config.StorageControllerConfiguration; import com.eucalyptus.entities.EntityWrapper; import com.eucalyptus.records.EventClass; import com.eucalyptus.records.EventRecord; import com.eucalyptus.records.EventType; import com.eucalyptus.util.EucalyptusCloudException; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import edu.ucsb.eucalyptus.cloud.state.State; import edu.ucsb.eucalyptus.msgs.AttachVolumeResponseType; import edu.ucsb.eucalyptus.msgs.AttachVolumeType; import edu.ucsb.eucalyptus.msgs.AttachedVolume; import edu.ucsb.eucalyptus.msgs.CreateStorageVolumeResponseType; import edu.ucsb.eucalyptus.msgs.CreateStorageVolumeType; import edu.ucsb.eucalyptus.msgs.CreateVolumeResponseType; import edu.ucsb.eucalyptus.msgs.CreateVolumeType; import edu.ucsb.eucalyptus.msgs.DeleteStorageVolumeResponseType; import edu.ucsb.eucalyptus.msgs.DeleteStorageVolumeType; import edu.ucsb.eucalyptus.msgs.DeleteVolumeResponseType; import edu.ucsb.eucalyptus.msgs.DeleteVolumeType; import edu.ucsb.eucalyptus.msgs.DescribeVolumesResponseType; import edu.ucsb.eucalyptus.msgs.DescribeVolumesType; import edu.ucsb.eucalyptus.msgs.DetachVolumeResponseType; import edu.ucsb.eucalyptus.msgs.DetachVolumeType; public class VolumeManager { static String PERSISTENCE_CONTEXT = "eucalyptus_images"; private static String ID_PREFIX = "vol"; private static Logger LOG = Logger.getLogger( VolumeManager.class ); public static EntityWrapper<Volume> getEntityWrapper( ) { return new EntityWrapper<Volume>( PERSISTENCE_CONTEXT ); } public CreateVolumeResponseType CreateVolume( CreateVolumeType request ) throws EucalyptusCloudException { if ( ( request.getSnapshotId( ) == null && request.getSize( ) == null ) ) { throw new EucalyptusCloudException( "One of size or snapshotId is required as a parameter." ); } try { Configuration.getClusterConfiguration( request.getAvailabilityZone( ) ); } catch ( Exception e ) { throw new EucalyptusCloudException( "Zone does not exist: " + request.getAvailabilityZone( ), e ); } StorageControllerConfiguration sc; try { sc = Configuration.getStorageControllerConfiguration( request.getAvailabilityZone( ) ); } catch ( Exception e ) { throw new EucalyptusCloudException( "Storage services are not available for the requested availability zone.", e ); } EntityWrapper<Volume> db = VolumeManager.getEntityWrapper( ); if ( request.getSnapshotId( ) != null ) { String userName = request.isAdministrator( ) ? null : request.getUserId( ); try { db.recast( Snapshot.class ).getUnique( Snapshot.named( userName, request.getSnapshotId( ) ) ); } catch ( EucalyptusCloudException e ) { LOG.debug( e, e ); db.rollback( ); throw new EucalyptusCloudException( "Snapshot does not exist: " + request.getSnapshotId( ) ); } } String newId = null; Volume newVol = null; while ( true ) { newId = Crypto.generateId( request.getUserId( ), ID_PREFIX ); try { db.getUnique( Volume.named( null, newId ) ); } catch ( EucalyptusCloudException e ) { try { newVol = new Volume( request.getUserId( ), newId, new Integer( request.getSize( ) != null ? request.getSize( ) : "-1" ), request.getAvailabilityZone( ), request.getSnapshotId( ) ); db.add( newVol ); db.commit( ); break; } catch ( Throwable e1 ) { db.rollback( ); db = VolumeManager.getEntityWrapper( ); } } } newVol.setState( State.GENERATING ); try { CreateStorageVolumeType req = new CreateStorageVolumeType( newId, request.getSize( ), request.getSnapshotId( ) ); req.setUserId( request.getUserId( ) ); req.setEffectiveUserId( request.getEffectiveUserId( ) ); StorageUtil.send( sc.getName( ), req ); EventRecord.here( VolumeManager.class, EventClass.VOLUME, EventType.VOLUME_CREATE, "user=" + newVol.getUserName( ), "volume=" + newVol.getDisplayName( ), "size=" + newVol.getSize( ), "cluster=" + newVol.getCluster( ), "snapshot=" + newVol.getParentSnapshot( ) ).info( ); } catch ( EucalyptusCloudException e ) { LOG.debug( e, e ); try { db = VolumeManager.getEntityWrapper( ); Volume d = db.getUnique( Volume.named( newVol.getUserName( ), newVol.getDisplayName( ) ) ); db.delete( d ); db.commit( ); } catch ( Throwable e1 ) { db.rollback( ); LOG.debug( e1, e1 ); } throw new EucalyptusCloudException( "Error while communicating with Storage Controller: CreateStorageVolume:" + e.getMessage( ) ); } CreateVolumeResponseType reply = ( CreateVolumeResponseType ) request.getReply( ); reply.setVolume( newVol.morph( new edu.ucsb.eucalyptus.msgs.Volume( ) ) ); return reply; } public DeleteVolumeResponseType DeleteVolume( DeleteVolumeType request ) throws EucalyptusCloudException { DeleteVolumeResponseType reply = ( DeleteVolumeResponseType ) request.getReply( ); reply.set_return( false ); EntityWrapper<Volume> db = VolumeManager.getEntityWrapper( ); String userName = request.isAdministrator( ) ? null : request.getUserId( ); boolean reallyFailed = false; try { Volume vol = db.getUnique( Volume.named( userName, request.getVolumeId( ) ) ); for ( VmInstance vm : VmInstances.getInstance( ).listValues( ) ) { for ( AttachedVolume attachedVol : vm.getVolumes( ) ) { if ( request.getVolumeId( ).equals( attachedVol.getVolumeId( ) ) ) { db.rollback( ); return reply; } } } if ( State.FAIL.equals( vol.getState( ) ) ) { db.delete( vol ); db.commit( ); return reply; } DeleteStorageVolumeResponseType scReply = StorageUtil.send( vol.getCluster( ), new DeleteStorageVolumeType( vol.getDisplayName( ) ) ); if ( scReply.get_return( ) ) { vol.setState( State.ANNIHILATING ); db.commit( ); EventRecord.here( VolumeManager.class, EventClass.VOLUME, EventType.VOLUME_DELETE, "user=" + vol.getUserName( ), "volume=" + vol.getDisplayName( ), "size=" + vol.getSize( ), "cluster=" + vol.getCluster( ), "snapshot=" + vol.getParentSnapshot( ) ).info( ); } else { reallyFailed = true; throw new EucalyptusCloudException( "Storage Controller returned false: Contact the administrator to report the problem." ); } } catch ( EucalyptusCloudException e ) { LOG.debug( e, e ); db.rollback( ); if ( reallyFailed ) { throw e; } else { return reply; } } reply.set_return( true ); return reply; } public DescribeVolumesResponseType DescribeVolumes( DescribeVolumesType request ) throws EucalyptusCloudException { DescribeVolumesResponseType reply = ( DescribeVolumesResponseType ) request.getReply( ); EntityWrapper<Volume> db = getEntityWrapper( ); try { String userName = request.isAdministrator( ) ? null : request.getUserId( ); Map<String, AttachedVolume> attachedVolumes = new HashMap<String, AttachedVolume>( ); for ( VmInstance vm : VmInstances.getInstance( ).listValues( ) ) { for ( AttachedVolume av : vm.getVolumes( ) ) { attachedVolumes.put( av.getVolumeId( ), av ); } } List<Volume> volumes = db.query( Volume.ownedBy( userName ) ); List<Volume> describeVolumes = Lists.newArrayList( ); for ( Volume v : volumes ) { if ( !State.ANNIHILATED.equals( v.getState( ) ) ) { describeVolumes.add( v ); } else { db.delete( v ); } } try { ArrayList<edu.ucsb.eucalyptus.msgs.Volume> volumeReplyList = StorageUtil.getVolumeReply( attachedVolumes, describeVolumes ); reply.getVolumeSet( ).addAll( volumeReplyList ); } catch ( Exception e ) { LOG.warn( "Error getting volume information from the Storage Controller: " + e ); LOG.debug( e, e ); } db.commit( ); } catch ( Throwable t ) { db.commit( ); } return reply; } public AttachVolumeResponseType AttachVolume( AttachVolumeType request ) throws EucalyptusCloudException { AttachVolumeResponseType reply = ( AttachVolumeResponseType ) request.getReply( ); if ( request.getDevice( ) == null || request.getDevice( ).endsWith( "sda" ) ) { throw new EucalyptusCloudException( "Invalid device name specified: " + request.getDevice( ) ); } VmInstance vm = null; try { vm = VmInstances.getInstance( ).lookup( request.getInstanceId( ) ); } catch ( NoSuchElementException e ) { LOG.debug( e, e ); throw new EucalyptusCloudException( "Instance does not exist: " + request.getInstanceId( ) ); } for ( AttachedVolume attachedVol : vm.getVolumes( ) ) { if ( attachedVol.getDevice( ).replaceAll( "unknown,requested:", "" ).equals( request.getDevice( ) ) ) { throw new EucalyptusCloudException( "Already have a device attached to: " + request.getDevice( ) ); } } Cluster cluster = null; try { cluster = Clusters.getInstance( ).lookup( vm.getPlacement( ) ); } catch ( NoSuchElementException e ) { LOG.debug( e, e ); throw new EucalyptusCloudException( "Cluster does not exist: " + vm.getPlacement( ) ); } for ( VmInstance v : VmInstances.getInstance( ).listValues( ) ) { for ( AttachedVolume vol : v.getVolumes( ) ) { if ( vol.getVolumeId( ).equals( request.getVolumeId( ) ) ) { throw new EucalyptusCloudException( "Volume already attached: " + request.getVolumeId( ) ); } } } EntityWrapper<Volume> db = VolumeManager.getEntityWrapper( ); String userName = request.isAdministrator( ) ? null : request.getUserId( ); Volume volume = null; try { volume = db.getUnique( Volume.named( userName, request.getVolumeId( ) ) ); if ( volume.getRemoteDevice( ) == null ) { StorageUtil.getVolumeReply( new HashMap<String,AttachedVolume>(), Lists.newArrayList( volume ) ); } db.commit( ); } catch ( EucalyptusCloudException e ) { LOG.debug( e, e ); db.rollback( ); throw new EucalyptusCloudException( "Volume does not exist: " + request.getVolumeId( ) ); } if ( !volume.getCluster( ).equals( cluster.getName( ) ) ) { throw new EucalyptusCloudException( "Can only attach volumes in the same cluster: " + request.getVolumeId( ) ); } else if ( "invalid".equals( volume.getRemoteDevice( ) ) ) { throw new EucalyptusCloudException( "Volume is not yet available: " + request.getVolumeId( ) ); } request.setRemoteDevice( volume.getRemoteDevice( ) ); new VolumeAttachCallback( request ).dispatch( cluster ); AttachedVolume attachVol = new AttachedVolume( volume.getDisplayName( ), vm.getInstanceId( ), request.getDevice( ), volume.getRemoteDevice( ) ); attachVol.setStatus( "attaching" ); vm.getVolumes( ).add( attachVol ); EventRecord.here( VolumeManager.class, EventClass.VOLUME, EventType.VOLUME_ATTACH, "user=" + volume.getUserName( ), "volume=" + volume.getDisplayName( ), "instance=" + vm.getInstanceId( ), "cluster=" + vm.getPlacement( ) ).info( ); volume.setState( State.BUSY ); reply.setAttachedVolume( attachVol ); return reply; } public DetachVolumeResponseType detach( DetachVolumeType request ) throws EucalyptusCloudException { DetachVolumeResponseType reply = ( DetachVolumeResponseType ) request.getReply( ); EntityWrapper<Volume> db = VolumeManager.getEntityWrapper( ); String userName = request.isAdministrator( ) ? null : request.getUserId( ); try { db.getUnique( Volume.named( userName, request.getVolumeId( ) ) ); } catch ( EucalyptusCloudException e ) { LOG.debug( e, e ); db.rollback( ); throw new EucalyptusCloudException( "Volume does not exist: " + request.getVolumeId( ) ); } db.commit( ); VmInstance vm = null; AttachedVolume volume = null; for ( VmInstance v : VmInstances.getInstance( ).listValues( ) ) { for ( AttachedVolume vol : v.getVolumes( ) ) { if ( vol.getVolumeId( ).equals( request.getVolumeId( ) ) ) { volume = vol; vm = v; } } } if ( volume == null ) { throw new EucalyptusCloudException( "Volume is not attached: " + request.getVolumeId( ) ); } if ( !vm.getInstanceId( ).equals( request.getInstanceId( ) ) && request.getInstanceId( ) != null && !request.getInstanceId( ).equals( "" ) ) { throw new EucalyptusCloudException( "Volume is not attached to instance: " + request.getInstanceId( ) ); } if ( request.getDevice( ) != null && !request.getDevice( ).equals( "" ) && !volume.getDevice( ).equals( request.getDevice( ) ) ) { throw new EucalyptusCloudException( "Volume is not attached to device: " + request.getDevice( ) ); } Cluster cluster = null; try { cluster = Clusters.getInstance( ).lookup( vm.getPlacement( ) ); } catch ( NoSuchElementException e ) { LOG.debug( e, e ); throw new EucalyptusCloudException( "Cluster does not exist: " + vm.getPlacement( ) ); } request.setVolumeId( volume.getVolumeId( ) ); request.setRemoteDevice( volume.getRemoteDevice( ) ); request.setDevice( volume.getDevice( ).replaceAll( "unknown,requested:", "" ) ); request.setInstanceId( vm.getInstanceId( ) ); new VolumeDetachCallback( request ).dispatch( cluster ); EventRecord.here( VolumeManager.class, EventClass.VOLUME, EventType.VOLUME_DETACH, "user=" + vm.getOwnerId( ), "volume=" + volume.getVolumeId( ), "instance=" + vm.getInstanceId( ), "cluster=" + vm.getPlacement( ) ).info( ); volume.setStatus( "detaching" ); reply.setDetachedVolume( volume ); return reply; } }