/*************************************************************************
* Copyright 2009-2016 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 com.eucalyptus.blockstorage;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ExecutionException;
import javax.annotation.Nullable;
import com.eucalyptus.auth.Accounts;
import com.eucalyptus.auth.AuthQuotaException;
import com.eucalyptus.auth.policy.PolicySpec;
import com.eucalyptus.component.annotation.ComponentNamed;
import com.eucalyptus.compute.ClientUnauthorizedComputeException;
import com.eucalyptus.compute.ComputeException;
import com.eucalyptus.compute.common.AttachedVolume;
import com.eucalyptus.compute.common.ResourceTag;
import com.eucalyptus.compute.common.backend.AttachVolumeResponseType;
import com.eucalyptus.compute.common.backend.AttachVolumeType;
import com.eucalyptus.compute.common.backend.CreateVolumeResponseType;
import com.eucalyptus.compute.common.backend.CreateVolumeType;
import com.eucalyptus.compute.common.backend.DeleteVolumeResponseType;
import com.eucalyptus.compute.common.backend.DeleteVolumeType;
import com.eucalyptus.compute.common.backend.DetachVolumeResponseType;
import com.eucalyptus.compute.common.backend.DetachVolumeType;
import com.eucalyptus.compute.common.backend.EnableVolumeIOResponseType;
import com.eucalyptus.compute.common.backend.EnableVolumeIOType;
import com.eucalyptus.compute.common.backend.ModifyVolumeAttributeResponseType;
import com.eucalyptus.compute.common.backend.ModifyVolumeAttributeType;
import com.eucalyptus.compute.common.internal.blockstorage.Snapshot;
import com.eucalyptus.compute.common.internal.blockstorage.Snapshots;
import com.eucalyptus.compute.common.internal.blockstorage.State;
import com.eucalyptus.compute.common.internal.blockstorage.Volume;
import com.eucalyptus.compute.common.internal.identifier.InvalidResourceIdentifier;
import com.eucalyptus.compute.ClientComputeException;
import com.eucalyptus.compute.common.internal.tags.Tag;
import com.eucalyptus.compute.common.internal.tags.TagSupport;
import com.eucalyptus.compute.common.internal.tags.Tags;
import com.eucalyptus.compute.common.internal.util.MetadataException;
import com.eucalyptus.tags.TagHelper;
import com.google.common.base.Predicates;
import org.apache.log4j.Logger;
import com.eucalyptus.auth.AuthException;
import com.eucalyptus.auth.principal.AccountFullName;
import com.eucalyptus.auth.principal.UserFullName;
import com.eucalyptus.blockstorage.msgs.DeleteStorageVolumeResponseType;
import com.eucalyptus.blockstorage.msgs.DeleteStorageVolumeType;
import com.eucalyptus.blockstorage.msgs.DetachStorageVolumeType;
import com.eucalyptus.blockstorage.msgs.GetVolumeTokenResponseType;
import com.eucalyptus.blockstorage.msgs.GetVolumeTokenType;
import com.eucalyptus.blockstorage.util.StorageProperties;
import com.eucalyptus.cluster.callback.VolumeAttachCallback;
import com.eucalyptus.cluster.callback.VolumeDetachCallback;
import com.eucalyptus.component.Partition;
import com.eucalyptus.component.Partitions;
import com.eucalyptus.component.ServiceConfiguration;
import com.eucalyptus.component.Topology;
import com.eucalyptus.cluster.common.ClusterController;
import com.eucalyptus.compute.common.internal.identifier.ResourceIdentifiers;
import com.eucalyptus.context.Context;
import com.eucalyptus.context.Contexts;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.entities.TransactionException;
import com.eucalyptus.entities.Transactions;
import com.eucalyptus.records.EventClass;
import com.eucalyptus.records.EventRecord;
import com.eucalyptus.records.EventType;
import com.eucalyptus.records.Logs;
import com.eucalyptus.reporting.event.VolumeEvent;
import com.eucalyptus.util.EucalyptusCloudException;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.util.RestrictedTypes;
import com.eucalyptus.util.async.AsyncRequests;
import com.eucalyptus.compute.common.internal.vm.MigrationState;
import com.eucalyptus.compute.common.internal.vm.VmEphemeralAttachment;
import com.eucalyptus.compute.common.internal.vm.VmInstance;
import com.eucalyptus.compute.common.internal.vm.VmInstance.VmState;
import com.eucalyptus.vm.VmInstances;
import com.eucalyptus.compute.common.internal.vm.VmVolumeAttachment;
import com.eucalyptus.compute.common.internal.vm.VmVolumeAttachment.AttachmentState;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.primitives.Ints;
import edu.ucsb.eucalyptus.cloud.VolumeSizeExceededException;
import com.eucalyptus.cluster.common.msgs.ClusterAttachVolumeType;
import com.eucalyptus.cluster.common.msgs.ClusterDetachVolumeType;
@ComponentNamed("computeVolumeManager")
public class VolumeManager {
private static final int VOL_CREATE_RETRIES = 10;
private static Logger LOG = Logger.getLogger( VolumeManager.class );
public CreateVolumeResponseType CreateVolume( final CreateVolumeType request ) throws EucalyptusCloudException {
Context ctx = Contexts.lookup( );
Long volSize = request.getSize( ) != null
? Long.parseLong( request.getSize( ) )
: null;
final String snapId = normalizeOptionalSnapshotIdentifier( request.getSnapshotId() );
Integer snapSize = 0;
String partition = request.getAvailabilityZone( );
if ( ( request.getSnapshotId( ) == null && request.getSize( ) == null ) ) {
throw new EucalyptusCloudException( "One of size or snapshotId is required as a parameter." );
}
try {
TagHelper.validateTagSpecifications( request.getTagSpecification( ) );
} catch ( MetadataException e ) {
throw new ClientComputeException( "InvalidParameterValue", e.getMessage( ) );
}
final List<ResourceTag> volumeTags = TagHelper.tagsForResource( request.getTagSpecification( ), PolicySpec.EC2_RESOURCE_VOLUME );
if ( !volumeTags.isEmpty( ) ) {
if ( !TagHelper.createTagsAuthorized( ctx, PolicySpec.EC2_RESOURCE_VOLUME ) ) {
throw new ClientUnauthorizedComputeException( "Not authorized to create tags by " + ctx.getUser( ).getName( ) );
}
}
if ( snapId != null ) {
try {
Snapshot snap = Transactions.find( Snapshot.named( null, normalizeOptionalSnapshotIdentifier( snapId ) ) );
snapSize = snap.getVolumeSize( );
if ( !Predicates.and( Snapshots.FilterPermissions.INSTANCE, RestrictedTypes.filterPrivilegedWithoutOwner()).apply(snap)) {
throw new ClientUnauthorizedComputeException( "Not authorized to use snapshot " + snapId + " by " + ctx.getUser( ).getName( ) );
}
// Volume created from a snapshot cannot be smaller than the size of the snapshot
if (volSize != null && snap != null && snap.getVolumeSize() != null && volSize < snap.getVolumeSize()) {
throw new EucalyptusCloudException( "Volume size cannot be less than snapshot size" );
}
} catch ( NoSuchElementException ex ) {
throw new ClientComputeException( "InvalidSnapshot.NotFound", "The snapshot " + snapId + " does not exist.");
} catch ( TransactionException ex ) {
throw Exceptions.toUndeclared( "Creating volume failed: Contact the administrator to report the problem.", ex );
}
}
final Integer sizeFromRequest = request.getSize() != null
? new Integer(request.getSize())
: null;
if ( sizeFromRequest != null && sizeFromRequest <= 0) {
throw new EucalyptusCloudException( "Failed to create volume because the parameter size (" + sizeFromRequest + ") must be greater than zero.");
}
final Integer newSize = sizeFromRequest != null
? sizeFromRequest
: (snapId != null
? snapSize : new Integer(-1));
Exception lastEx = null;
for ( int i = 0; i < VOL_CREATE_RETRIES; i++ ) {
try {
final ServiceConfiguration sc = Topology.lookup( Storage.class, Partitions.lookupByName( partition ) );
final String arn = Accounts.getAuthenticatedArn( ctx.getUser( ) );
final UserFullName owner = ctx.getUserFullName( );
Function<Long, Volume> allocator = new Function<Long, Volume>( ) {
@Override
public Volume apply( Long size ) {
try {
return Volumes.createStorageVolume( sc, arn, owner, snapId, Ints.checkedCast( size ),
volume -> TagHelper.createOrUpdateTags( owner, volume, volumeTags ) );
} catch ( ExecutionException ex ) {
throw Exceptions.toUndeclared( ex );
}
}
};
Volume newVol = RestrictedTypes.allocateMeasurableResource(
newSize.longValue( ),
allocator,
Volume.exampleResource( owner, snapId, partition, newSize ) );
CreateVolumeResponseType reply = request.getReply( );
reply.setVolume( newVol.morph( new com.eucalyptus.compute.common.Volume( ) ) );
Map<String,List<Tag>> tagsMap = TagSupport.forResourceClass( Volume.class ).getResourceTagMap(
AccountFullName.getInstance( ctx.getAccountNumber( ) ),
Collections.singleton( newVol.getDisplayName( ) ) );
Tags.addFromTags( reply.getVolume().getTagSet(), ResourceTag.class, tagsMap.get( newVol.getDisplayName( ) ) );
return reply;
} catch ( RuntimeException ex ) {
if ( Exceptions.isCausedBy( ex, NoSuchElementException.class ) ) {
throw new ClientComputeException( "InvalidZone.NotFound", "The zone '"+partition+"' does not exist." );
}
if ( !( ex.getCause( ) instanceof ExecutionException ) ) {
throw handleException( ex );
} else {
LOG.error( ex, ex );
lastEx = ex;
}
} catch ( AuthException e ) {
throw handleException( e );
}
}
throw new EucalyptusCloudException( "Failed to create volume after " + VOL_CREATE_RETRIES + " because of: " + lastEx, lastEx );
}
public DeleteVolumeResponseType DeleteVolume( final DeleteVolumeType request ) throws EucalyptusCloudException {
DeleteVolumeResponseType reply = ( DeleteVolumeResponseType ) request.getReply( );
final Context ctx = Contexts.lookup( );
reply.set_return( false );
final Function<String, Volume> deleteVolume = new Function<String, Volume>( ) {
@Override
public Volume apply( final String input ) {
try {
Volume vol = null;
try {
vol = Entities.uniqueResult( Volume.named( ctx.getUserFullName( ).asAccountFullName( ), input ) );
} catch ( NoSuchElementException e ) {
// Slow path: try searching globally
try {
vol = Entities.uniqueResult( Volume.named( null, input ) );
} catch ( NoSuchElementException e2 ) {
throw Exceptions.toUndeclared( new ClientComputeException( "InvalidVolume.NotFound", "The volume '"+input+"' does not exist" ) );
}
}
if ( !RestrictedTypes.filterPrivileged( ).apply( vol ) ) {
throw Exceptions.toUndeclared( new ClientUnauthorizedComputeException( "Not authorized to delete volume by " + ctx.getUser( ).getName( ) ) );
}
try {
VmVolumeAttachment attachment = VmInstances.lookupVolumeAttachment( input );
throw Exceptions.toUndeclared( new ClientComputeException( "VolumeInUse", "Volume is currently attached to: " + attachment.getVmInstance( ).getDisplayName( ) ) );
} catch ( NoSuchElementException ex ) {
/** no such volume attached, move along... **/
}
if ( State.FAIL.equals( vol.getState( ) ) || State.ANNIHILATED.equals( vol.getState( ) ) ) {
Entities.delete( vol );
return vol;
} else {
try {
ServiceConfiguration sc = Topology.lookup( Storage.class, Partitions.lookupByName( vol.getPartition( ) ) );
DeleteStorageVolumeResponseType scReply = AsyncRequests.sendSync( sc, new DeleteStorageVolumeType( vol.getDisplayName( ) ) );
if ( scReply.get_return( ) ) {
Volumes.annihilateStorageVolume( vol );
return vol;
} else {
throw Exceptions.toUndeclared( "Storage Controller returned false: Contact the administrator to report the problem." );
}
} catch ( Exception ex ) {
throw Exceptions.toUndeclared( "Delete volume request failed because of: " + ex.getMessage(), ex);
}
}
} catch ( NoSuchElementException ex ) {
throw ex;
} catch ( TransactionException ex ) {
throw Exceptions.toUndeclared( "Deleting volume failed: Contact the administrator to report the problem.", ex );
}
}
};
try {
Entities.asTransaction( Volume.class, deleteVolume ).apply( normalizeVolumeIdentifier( request.getVolumeId() ) );
reply.set_return( true );
return reply;
} catch ( NoSuchElementException ex ) {
return reply;
} catch ( RuntimeException ex ) {
Exceptions.rethrow( ex, ComputeException.class );
throw ex;
}
}
public EnableVolumeIOResponseType EnableVolumeIO( EnableVolumeIOType request ) {
return request.getReply( );
}
public AttachVolumeResponseType AttachVolume( AttachVolumeType request ) throws EucalyptusCloudException {
AttachVolumeResponseType reply = ( AttachVolumeResponseType ) request.getReply( );
final String deviceName = request.getDevice( );
final String volumeId = normalizeVolumeIdentifier( request.getVolumeId() );
final String instanceId = normalizeInstanceIdentifier( request.getInstanceId() );
final Context ctx = Contexts.lookup( );
if ( deviceName == null || !validateDeviceName( deviceName ) ) {
throw new ClientComputeException( "InvalidParameterValue", "Value (" + deviceName + ") for parameter device is invalid." );
}
VmInstance vm = null;
try {
vm = RestrictedTypes.doPrivileged( instanceId, VmInstance.class );
} catch ( final NoSuchElementException e ) {
throw new ClientComputeException( "InvalidInstanceID.NotFound", "The instance ID '"+request.getInstanceId()+"' does not exist" );
} catch ( Exception ex ) {
throw handleException( ex );
}
if ( MigrationState.isMigrating( vm ) ) {
throw Exceptions.toUndeclared( "Cannot attach a volume to an instance which is currently migrating: "
+ vm.getInstanceId( )
+ " "
+ vm.getRuntimeState().getMigrationTask( ) );
}
AccountFullName ownerFullName = ctx.getUserFullName( ).asAccountFullName( );
Volume volume = null;
try{
volume = Volumes.lookup( ownerFullName, volumeId );
}catch(final NoSuchElementException ex){
try {
volume = Volumes.lookup( null, volumeId );
} catch ( NoSuchElementException e ) {
throw new ClientComputeException( "InvalidVolume.NotFound", "The volume '"+request.getVolumeId()+"' does not exist" );
}
}
if ( !RestrictedTypes.filterPrivileged( ).apply( volume ) ) {
throw new ClientUnauthorizedComputeException( "Not authorized to attach volume " + volumeId + " by " + ctx.getUser( ).getName( ) );
}
// check volumes
try {
vm.lookupVolumeAttachmentByDevice( deviceName );
throw new ClientComputeException( "InvalidParameterValue", "Already have a device attached to: " + request.getDevice( ) );
} catch ( NoSuchElementException ex1 ) {
/** no attachment **/
}
// check ephemeral devices
if ( Iterables.any(VmInstances.lookupEphemeralDevices(instanceId),
new Predicate<VmEphemeralAttachment>() {
@Override
public boolean apply(VmEphemeralAttachment device) {
return deviceName.endsWith(device.getShortDeviceName());
}
}) )
throw new ClientComputeException( "InvalidParameterValue", "Already have an ephemeral device attached to: " + request.getDevice( ) );
try {
VmInstances.lookupVolumeAttachment( volumeId );
throw new ClientComputeException( "VolumeInUse", "Volume already attached: " + volumeId );
} catch ( NoSuchElementException ex1 ) {
/** no attachment **/
}
Partition volPartition = Partitions.lookupByName( volume.getPartition( ) );
ServiceConfiguration sc = Topology.lookup( Storage.class, volPartition );
ServiceConfiguration scVm = Topology.lookup( Storage.class, vm.lookupPartition( ) );
if ( !sc.equals( scVm ) ) {
throw new EucalyptusCloudException( "Can only attach volumes in the same zone: " + volumeId );
}
// check if instance is stopped
if ( VmState.STOPPED.apply( vm ) ) {
// Volume attachment to an EBS backed instance in stopped state. Don't get attachment token from SC since its a part of instance start up
// process. Just add the persistent attachment record to the VM
String rootDevice = vm.getBootRecord().getMachine().getRootDeviceName();
// swathi - assuming delete on terminate flag to always be false. Its better to err on the safe side and not delete volumes
// add root attachment
VmInstances.addPersistentVolume( vm, deviceName, volume, rootDevice.equals( deviceName ), false );
} else if ( VmState.RUNNING.apply( vm ) ) {
// A normal volume attachment. Get attachment token from SC and fire attachment request to NC/CC
ServiceConfiguration ccConfig = Topology.lookup( ClusterController.class, vm.lookupPartition() );
GetVolumeTokenResponseType scGetTokenResponse;
try {
GetVolumeTokenType req = new GetVolumeTokenType(volume.getDisplayName());
scGetTokenResponse = AsyncRequests.sendSync(sc, req);
} catch (Exception e) {
LOG.warn("Failed to attach volume " + volume.getDisplayName(), e);
throw new EucalyptusCloudException(e.getMessage(), e);
}
// The SC should not know the format, so the CLC must construct the special format
String token = StorageProperties.formatVolumeAttachmentTokenForTransfer(scGetTokenResponse.getToken(), volume.getDisplayName());
final ClusterAttachVolumeType attachVolume = new ClusterAttachVolumeType();
attachVolume.setInstanceId(request.getInstanceId());
attachVolume.setVolumeId(request.getVolumeId());
attachVolume.setDevice(request.getDevice());
attachVolume.setRemoteDevice(token);
// Add volume attachment record to VM
VmInstances.addTransientVolume( vm, deviceName, token, volume );
// Fire attach volume request to NC/CC
final VolumeAttachCallback cb = new VolumeAttachCallback(attachVolume);
AsyncRequests.newRequest(cb).dispatch(ccConfig);
} else {
throw new ClientComputeException( "IncorrectState", "Instance '"+instanceId+"' is not 'running'" );
}
EventRecord.here( VolumeManager.class, EventClass.VOLUME, EventType.VOLUME_ATTACH )
.withDetails( volume.getOwner( ).toString( ), volume.getDisplayName( ), "instance", vm.getInstanceId( ) )
.withDetails( "partition", vm.getPartition( ).toString( ) ).info( );
reply.setAttachedVolume( new AttachedVolume(volume.getDisplayName(), vm.getInstanceId(), request.getDevice()) );
Volumes.fireUsageEvent(volume, VolumeEvent.forVolumeAttach(vm.getInstanceUuid(), volume.getDisplayName()));
return reply;
}
public DetachVolumeResponseType detach( DetachVolumeType request ) throws EucalyptusCloudException {
final DetachVolumeResponseType reply = ( DetachVolumeResponseType ) request.getReply( );
final String volumeId = normalizeVolumeIdentifier( request.getVolumeId( ) );
final String instanceId = normalizeOptionalInstanceIdentifier( request.getInstanceId( ) );
final Context ctx = Contexts.lookup( );
Volume vol;
try {
vol = Volumes.lookup( ctx.getUserFullName( ).asAccountFullName( ), volumeId );
} catch ( NoSuchElementException ex ) {
try {
vol = Volumes.lookup(null, volumeId);
} catch ( NoSuchElementException e ) {
throw new ClientComputeException( "InvalidVolume.NotFound", "The volume '"+request.getVolumeId()+"' does not exist" );
}
}
if ( !RestrictedTypes.filterPrivileged( ).apply( vol ) ) {
throw new ClientUnauthorizedComputeException( "Not authorized to detach volume " + volumeId + " by " + ctx.getUser( ).getName( ) );
}
VmInstance vm = null;
String remoteDevice = null;
AttachedVolume volume = null;
VmVolumeAttachment vmVolAttach = null;
try {
// Lookup both persistent and transient volumes
vmVolAttach = VmInstances.lookupVolumeAttachment( volumeId );
remoteDevice = vmVolAttach.getRemoteDevice( );
volume = VmVolumeAttachment.asAttachedVolume( vmVolAttach.getVmInstance( ) ).apply( vmVolAttach );
vm = vmVolAttach.getVmInstance( );
} catch ( NoSuchElementException ex ) {
throw new ClientComputeException( "IncorrectState", "Volume is not attached: " + volumeId );
}
// Cannot detach root volume of EBS backed instance when the instance is not stopped
if (vmVolAttach.getIsRootDevice() && !VmState.STOPPED.equals(vm.getState())) {
throw new ClientComputeException("IncorrectState", "Cannot detach root volume " + volumeId + " from EBS backed instance " + vm.getInstanceId()
+ " when instance state is " + vm.getState().toString().toLowerCase());
}
// Dropping the validation check for device string retrieved from database - EUCA-8330
if ( vm != null && MigrationState.isMigrating( vm ) ) {
throw Exceptions.toUndeclared( "Cannot detach a volume from an instance which is currently migrating: "
+ vm.getInstanceId( )
+ " "
+ vm.getRuntimeState().getMigrationTask( ) );
}
if ( volume == null ) {
throw new ClientComputeException( "IncorrectState", "Volume is not attached: " + volumeId );
}
if ( !RestrictedTypes.filterPrivileged( ).apply( vm ) ) {
throw new ClientUnauthorizedComputeException( "Not authorized to detach volume from instance " + instanceId + " by " + ctx.getUser( ).getName( ) );
}
if ( instanceId != null && vm != null && !vm.getInstanceId( ).equals( instanceId ) ) {
throw new ClientComputeException( "InvalidAttachment.NotFound", "Volume is not attached to instance: " + instanceId );
}
if ( request.getDevice( ) != null && !request.getDevice( ).equals( "" ) && !volume.getDevice( ).equals( request.getDevice( ) ) ) {
throw new EucalyptusCloudException( "Volume is not attached to device: " + request.getDevice( ) );
}
ServiceConfiguration scVm;
try {
scVm = Topology.lookup( Storage.class, vm.lookupPartition( ) );
} catch ( Exception ex ) {
LOG.error( ex, ex );
throw new EucalyptusCloudException( "Failed to lookup SC for partition: " + vm.getPartition( ), ex );
}
if ( VmState.STOPPED.equals( vm.getState( ) ) ) {
//Ensure that the volume is not attached
try {
final DetachStorageVolumeType detach = new DetachStorageVolumeType( volume.getVolumeId( ));
AsyncRequests.sendSync( scVm, detach);
} catch ( Exception e ) {
LOG.debug( e );
Logs.extreme( ).debug( e, e );
//GRZE: attach is idempotent, failure here is ok, throw new EucalyptusCloudException( e.getMessage( ) );
}
VmInstances.removeVolumeAttachment( vm, volume.getVolumeId( ) );
} else {
ServiceConfiguration ccConfig = null;
try {
ccConfig = Topology.lookup( ClusterController.class, vm.lookupPartition( ) );
} catch ( NoSuchElementException e ) {
LOG.debug( e, e );
throw new EucalyptusCloudException( "Cluster does not exist in partition: " + vm.getPartition( ) );
}
final ClusterDetachVolumeType detachVolume = new ClusterDetachVolumeType( );
detachVolume.setVolumeId( volume.getVolumeId( ) );
detachVolume.setRemoteDevice( remoteDevice );
detachVolume.setDevice( volume.getDevice( ).replaceAll( "unknown,requested:", "" ) );
detachVolume.setInstanceId( vm.getInstanceId( ) );
detachVolume.setForce( request.getForce( ) );
VolumeDetachCallback ncDetach = new VolumeDetachCallback( detachVolume );
/* No SC action required, send directly to NC
* try {
AsyncRequests.sendSync( scVm, new DetachStorageVolumeType( volume.getVolumeId( ) ) );
} catch ( Exception e ) {
LOG.debug( e );
Logs.extreme( ).debug( e, e );
//GRZE: attach is idempotent, failure here is ok, throw new EucalyptusCloudException( e.getMessage( ) );
}*/
AsyncRequests.newRequest( ncDetach ).dispatch( ccConfig );
//Update the state of the attachment to 'detaching'
VmInstances.updateVolumeAttachment( vm, volumeId, AttachmentState.detaching );
volume.setStatus( "detaching" );
}
reply.setDetachedVolume( volume );
Volumes.fireUsageEvent(vol, VolumeEvent.forVolumeDetach(vm.getInstanceUuid(), vm.getInstanceId()));
return reply;
}
public ModifyVolumeAttributeResponseType modifyVolumeAttribute( ModifyVolumeAttributeType request ) {
return request.getReply();
}
private boolean validateDeviceName(String DeviceName){
return java.util.regex.Pattern.matches("^[a-zA-Z\\d/]{3,10}$", DeviceName);
}
private static String normalizeIdentifier( final String identifier,
final String prefix,
final boolean required,
final String message ) throws ClientComputeException {
try {
return Strings.emptyToNull( identifier ) == null && !required ?
null :
ResourceIdentifiers.parse( prefix, identifier ).getIdentifier( );
} catch ( final InvalidResourceIdentifier e ) {
throw new ClientComputeException( "InvalidParameterValue", String.format( message, e.getIdentifier( ) ) );
}
}
@Nullable
private static String normalizeOptionalSnapshotIdentifier( final String identifier ) throws EucalyptusCloudException {
return normalizeIdentifier(
identifier, "snap", false, "Value (%s) for parameter snapshotId is invalid. Expected: 'snap-...'." );
}
@Nullable
private static String normalizeOptionalInstanceIdentifier( final String identifier ) throws EucalyptusCloudException {
return normalizeIdentifier(
identifier, VmInstance.ID_PREFIX, false, "Value (%s) for parameter instanceId is invalid. Expected: 'i-...'." );
}
private static String normalizeInstanceIdentifier( final String identifier ) throws EucalyptusCloudException {
return normalizeIdentifier(
identifier, VmInstance.ID_PREFIX, true, "Value (%s) for parameter instanceId is invalid. Expected: 'i-...'." );
}
private static String normalizeVolumeIdentifier( final String identifier ) throws EucalyptusCloudException {
return normalizeIdentifier(
identifier, Volume.ID_PREFIX, true, "Value (%s) for parameter volume is invalid. Expected: 'vol-...'." );
}
/**
* Method always throws, signature allows use of "throw handleException ..."
*/
private static ComputeException handleException( final Exception e ) throws ComputeException {
final ComputeException cause = Exceptions.findCause( e, ComputeException.class );
if ( cause != null ) {
throw cause;
}
final AuthException authException = Exceptions.findCause( e, AuthException.class );
if ( authException != null ) {
if ( authException instanceof AuthQuotaException ) {
throw new ClientComputeException( "VolumeLimitExceeded", authException.getMessage( ) );
} else {
throw new ClientUnauthorizedComputeException( authException.getMessage( ) );
}
}
final VolumeSizeExceededException volumeSizeException =
Exceptions.findCause( e, VolumeSizeExceededException.class );
if ( volumeSizeException != null )
throw new ClientComputeException(
"VolumeLimitExceeded", volumeSizeException.getMessage( ) );
LOG.error( e, e );
final ComputeException exception = new ComputeException( "InternalError", String.valueOf( e.getMessage( ) ) );
if ( Contexts.lookup( ).hasAdministrativePrivileges() ) {
exception.initCause( e );
}
throw exception;
}
}