/*************************************************************************
* 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 static com.eucalyptus.reporting.event.VolumeEvent.VolumeAction;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.ExecutionException;
import java.util.Date;
import java.util.function.Consumer;
import javax.persistence.EntityTransaction;
import org.apache.log4j.Logger;
import org.hibernate.criterion.Example;
import com.eucalyptus.auth.principal.UserFullName;
import com.eucalyptus.blockstorage.msgs.CreateStorageVolumeResponseType;
import com.eucalyptus.blockstorage.msgs.CreateStorageVolumeType;
import com.eucalyptus.blockstorage.msgs.DescribeStorageVolumesResponseType;
import com.eucalyptus.blockstorage.msgs.DescribeStorageVolumesType;
import com.eucalyptus.compute.common.CloudMetadata.VolumeMetadata;
import com.eucalyptus.component.Partitions;
import com.eucalyptus.component.ServiceConfiguration;
import com.eucalyptus.component.Topology;
import com.eucalyptus.compute.common.CloudMetadatas;
import com.eucalyptus.compute.common.VolumeStatusItemType;
import com.eucalyptus.compute.common.internal.account.IdentityIdFormats;
import com.eucalyptus.compute.common.internal.blockstorage.State;
import com.eucalyptus.compute.common.internal.blockstorage.Volume;
import com.eucalyptus.compute.common.internal.blockstorage.VolumeTag;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.entities.TransactionException;
import com.eucalyptus.entities.TransactionResource;
import com.eucalyptus.entities.Transactions;
import com.eucalyptus.event.ListenerRegistry;
import com.eucalyptus.records.Logs;
import com.eucalyptus.reporting.event.EventActionInfo;
import com.eucalyptus.reporting.event.VolumeEvent;
import com.eucalyptus.util.Callback;
import com.eucalyptus.util.EucalyptusCloudException;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.auth.principal.OwnerFullName;
import com.eucalyptus.util.RestrictedTypes.QuantityMetricFunction;
import com.eucalyptus.util.RestrictedTypes.UsageMetricFunction;
import com.eucalyptus.util.TypeMapper;
import com.eucalyptus.util.async.AsyncRequests;
import com.eucalyptus.vm.VmInstances;
import com.eucalyptus.compute.common.internal.vm.VmVolumeAttachment;
import com.eucalyptus.compute.common.internal.tags.FilterSupport;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import edu.ucsb.eucalyptus.cloud.VolumeSizeExceededException;
import edu.ucsb.eucalyptus.msgs.BaseMessage;
public class Volumes {
private static Logger LOG = Logger.getLogger( Volumes.class );
@QuantityMetricFunction( VolumeMetadata.class )
public enum CountVolumes implements Function<OwnerFullName, Long> {
INSTANCE;
@Override
public Long apply( final OwnerFullName input ) {
final EntityTransaction db = Entities.get( Volume.class );
try {
return Entities.count( Volume.named( input, null ) );
} finally {
db.rollback( );
}
}
}
@UsageMetricFunction( VolumeMetadata.class )
public enum MeasureVolumes implements Function<OwnerFullName, Long> {
INSTANCE;
@SuppressWarnings( "unchecked" )
@Override
public Long apply( final OwnerFullName input ) {
Long size = 0l;
final EntityTransaction db = Entities.get( Volume.class );
try {
final List<Volume> vols = Entities.createCriteria( Volume.class )
.add( Example.create( Volume.named( input, null ) ) )
.setReadOnly( true )
.setCacheable( false )
.list( );
for ( final Volume v : vols ) {
size += v.getSize( );
}
} finally {
db.rollback( );
}
return size;
}
}
public static Volume checkVolumeReady( final Volume vol ) throws EucalyptusCloudException {
if ( vol.isReady( ) ) {
return vol;
} else {
//TODO:GRZE:REMOVE temporary workaround to update the volume state.
final ServiceConfiguration sc = Topology.lookup( Storage.class, Partitions.lookupByName( vol.getPartition( ) ) );
final DescribeStorageVolumesType descVols = new DescribeStorageVolumesType( Lists.newArrayList( vol.getDisplayName( ) ) );
try {
Transactions.one( Volume.named( null, vol.getDisplayName( ) ), new Callback<Volume>( ) {
@Override
public void fire( final Volume t ) {
try {
final DescribeStorageVolumesResponseType volState = AsyncRequests.sendSync( sc, descVols );
if ( !volState.getVolumeSet( ).isEmpty( ) ) {
final State newVolumeState = Volumes.transformStorageState( vol.getState( ), volState.getVolumeSet( ).get( 0 ).getStatus( ) );
vol.setState( newVolumeState );
}
} catch ( final Exception ex ) {
LOG.error( ex );
Logs.extreme( ).error( ex, ex );
throw Exceptions.toUndeclared( "Failed to update the volume state " + vol.getDisplayName( ) + " not yet ready", ex );
}
}
} );
} catch ( final ExecutionException ex ) {
throw new EucalyptusCloudException( ex.getCause( ) );
}
if ( !vol.isReady( ) ) {
throw new EucalyptusCloudException( "Volume " + vol.getDisplayName( ) + " not yet ready" );
}
return vol;
}
}
public static Volume lookup( final OwnerFullName ownerFullName, final String volumeId ) {
try ( final TransactionResource db =
Entities.transactionFor( Volume.class ) ){
try{
Volume volume = Entities.uniqueResult( Volume.named( ownerFullName, volumeId ) );
db.commit( );
return volume;
} catch ( final NoSuchElementException e ) {
throw e;
} catch ( final Exception ex ) {
LOG.debug( ex, ex );
throw Exceptions.toUndeclared( ex );
}
}
}
public static void setSystemManagedFlag (final OwnerFullName ownerFullName,
final String volumeId, boolean systemManaged) throws NoSuchElementException {
try ( final TransactionResource db = Entities.transactionFor( Volume.class ) ) {
Volume volume = Entities.uniqueResult( Volume.named( ownerFullName, volumeId ) );
if (volume == null)
throw new NoSuchElementException( "Can not find volume with id " + volumeId );
volume.setSystemManaged(systemManaged);
db.commit();
} catch ( final TransactionException ex ) {
LOG.debug( ex, ex );
throw Exceptions.toUndeclared( ex );
}
}
public static Volume createStorageVolume(
final ServiceConfiguration sc,
final String arn,
final UserFullName owner,
final String snapId,
final Integer newSize,
final Consumer<Volume> consumeInTx
) throws ExecutionException {
final String newId = IdentityIdFormats.generate( arn, Volume.ID_PREFIX );
LOG.debug("Creating volume");
final Volume newVol = Transactions.save( Volume.create( sc, owner, snapId, newSize, newId ), new Callback<Volume>( ) {
@Override
public void fire( final Volume t ) {
t.setState( State.GENERATING );
try {
final CreateStorageVolumeType req = new CreateStorageVolumeType( t.getDisplayName( ), t.getSize( ), snapId, null );
final CreateStorageVolumeResponseType ret = AsyncRequests.sendSync( sc, req );
LOG.debug("Volume created");
if ( t.getSize() != null && t.getSize() > 0 ) {
fireUsageEvent( t, VolumeEvent.forVolumeCreate());
}
} catch ( final Exception ex ) {
final VolumeSizeExceededException volumeSizeException =
Exceptions.findCause( ex, VolumeSizeExceededException.class );
if ( volumeSizeException != null ) {
LOG.debug( "Failed to create volume: " + t.getDisplayName() + " due to " + volumeSizeException.getLocalizedMessage() );
} else {
LOG.error( "Failed to create volume: " + t.getDisplayName(), ex );
}
t.setState( State.FAIL );
throw Exceptions.toUndeclared( ex );
}
if ( consumeInTx != null ) {
consumeInTx.accept( t );
}
}
} );
return newVol;
}
public static void annihilateStorageVolume( Volume volume) {
volume.setState( State.ANNIHILATING );
fireUsageEvent( volume, VolumeEvent.forVolumeDelete());
}
static State transformStorageState( final State volumeState, final String storageState ) {
if ( State.GENERATING.equals( volumeState ) ) {
if ( "failed".equals( storageState ) ) {
return State.FAIL;
} else if ( "error".equals( storageState ) ) {
return State.ERROR;
} else if ("available".equals(storageState) ) {
return State.EXTANT;
} else {
return State.GENERATING;
}
} else if ( State.ANNIHILATING.equals( volumeState ) ) {
if ("deleted".equals(storageState) ) {
return State.ANNIHILATED;
} else {
return State.ANNIHILATING;
}
} else if ( !State.ANNIHILATING.equals( volumeState ) && !State.BUSY.equals( volumeState ) ) {
if ( "failed".equals(storageState) ) {
return State.FAIL;
} else if ( "creating".equals(storageState) ) {
return State.GENERATING;
} else if ( "available".equals(storageState) ) {
return State.EXTANT;
} else if ( "in-use".equals( storageState ) ) {
return State.BUSY;
} else if ( "error".equals( storageState ) ) {
return State.ERROR;
} else {
return State.ANNIHILATED;
}
} else if ( State.BUSY.equals( volumeState ) ) {
return State.BUSY;
} else if ( State.ERROR.equals( volumeState ) ) {
if ( "available".equals(storageState) ) {
return State.EXTANT;
} else if ( "deleted".equals(storageState) ) {
return State.ANNIHILATED;
} else {
return State.ERROR;
}
} else {
if ("failed".equals(storageState) ) {
return State.FAIL;
} else if( "error".equals(storageState) ) {
return State.ERROR;
} else {
return State.ANNIHILATED;
}
}
}
static void fireUsageEvent( final Volume volume,
final EventActionInfo<VolumeAction> actionInfo ) {
try {
ListenerRegistry.getInstance().fireEvent(
VolumeEvent.with(
actionInfo,
volume.getNaturalId(),
volume.getDisplayName(),
volume.getSize(),
volume.getOwner(),
volume.getPartition())
);
} catch (final Throwable e) {
LOG.error("Error creating/inserting reporting event " + (actionInfo == null ? "null" : actionInfo.getAction().toString()) + " for volume " + (volume == null ? "null" : volume.getDisplayName()), e);
}
}
@TypeMapper
public enum VolumeToVolumeStatusItemTypeTransform implements Function<Volume, VolumeStatusItemType> {
INSTANCE;
@Override
public VolumeStatusItemType apply( final Volume volume ) {
final VolumeStatusItemType status = new VolumeStatusItemType( );
status.setAvailabilityZone( volume.getPartition( ) );
status.setVolumeId( volume.getDisplayName( ) );
status.setStatus( "ok" );
status.setIoEnabledStatus( "passed" );
return status;
}
}
public static class VolumeFilterSupport extends FilterSupport<Volume>{
public VolumeFilterSupport(){
super( builderFor( Volume.class )
.withTagFiltering( VolumeTag.class, "volume" )
.withDateProperty( "attachment.attach-time", FilterDateFunctions.ATTACHMENT_ATTACH_TIME )
.withBooleanProperty( "attachment.delete-on-termination", FilterBooleanFunctions.ATTACHMENT_DELETE_ON_TERMINATION )
.withStringProperty( "attachment.device", FilterStringFunctions.ATTACHMENT_DEVICE )
.withStringProperty( "attachment.instance-id", FilterStringFunctions.ATTACHMENT_INSTANCE_ID )
.withStringProperty( "attachment.status", FilterStringFunctions.ATTACHMENT_STATUS )
.withStringProperty( "availability-zone", FilterStringFunctions.AVAILABILITY_ZONE )
.withDateProperty( "create-time", FilterDateFunctions.CREATE_TIME )
.withStringProperty( "size", FilterStringFunctions.SIZE )
.withStringProperty( "snapshot-id", FilterStringFunctions.SNAPSHOT_ID )
.withStringProperty( "status", FilterStringFunctions.STATUS )
.withInternalBooleanProperty( "system-managed", FilterBooleanFunctions.SYSTEM_MANAGED )
.withStringProperty( "volume-id", FilterStringFunctions.VOLUME_ID )
.withConstantProperty( "volume-type", "standard" )
.withPersistenceFilter( "availability-zone", "partition" )
.withPersistenceFilter( "create-time", "creationTimestamp", PersistenceFilter.Type.Date )
.withPersistenceFilter( "size", "size", PersistenceFilter.Type.Integer )
.withPersistenceFilter( "snapshot-id", "parentSnapshot" )
.withPersistenceFilter( "volume-id", "displayName" ) );
}
}
public static class VolumeStatusFilterSupport extends FilterSupport<Volume>{
public VolumeStatusFilterSupport( ){
super( qualifierBuilderFor( Volume.class, "status" )
.withUnsupportedProperty( "action.code" )
.withUnsupportedProperty( "action.description" )
.withUnsupportedProperty( "action.event-id" )
.withStringProperty( "availability-zone", FilterStringFunctions.AVAILABILITY_ZONE )
.withUnsupportedProperty( "event.description" )
.withUnsupportedProperty( "event.event-id" )
.withUnsupportedProperty( "event.event-type" )
.withUnsupportedProperty( "event.not-after" )
.withUnsupportedProperty( "event.not-before" )
.withInternalBooleanProperty( "system-managed", FilterBooleanFunctions.SYSTEM_MANAGED )
.withInternalStringProperty( "volume-id", CloudMetadatas.toDisplayName( ) )
.withConstantProperty( "volume-status.details-name", "io-enabled" )
.withConstantProperty( "volume-status.details-status", "passed" )
.withConstantProperty( "volume-status.status", "ok" )
.withPersistenceFilter( "availability-zone", "partition" )
.withPersistenceFilter( "volume-id", "displayName" ) );
}
}
private enum FilterStringFunctions implements Function<Volume,String> {
ATTACHMENT_DEVICE {
@Override
public String apply(final Volume vol){
try{
VmVolumeAttachment attachment = VmInstances.lookupVolumeAttachment( vol.getDisplayName( ) );
return attachment.getDevice( );
}catch (final Throwable e) {
return null;
}
}
},
ATTACHMENT_INSTANCE_ID {
@Override
public String apply(final Volume vol){
try{
VmVolumeAttachment attachment = VmInstances.lookupVolumeAttachment( vol.getDisplayName() );
return attachment.getVmInstance().getInstanceId();
}catch (final Throwable e) {
return null;
}
}
},
ATTACHMENT_STATUS {
@Override
public String apply(final Volume vol){
try{
VmVolumeAttachment attachment = VmInstances.lookupVolumeAttachment(vol.getDisplayName());
return attachment.getStatus();
}catch (final Throwable e){
return null;
}
}
},
AVAILABILITY_ZONE {
@Override
public String apply(final Volume vol){
return vol.getPartition();
}
},
SIZE {
@Override
public String apply(final Volume vol){
return vol.getSize().toString();
}
},
SNAPSHOT_ID {
@Override
public String apply(final Volume vol){
return vol.getParentSnapshot();
}
},
STATUS {
@Override
public String apply(final Volume vol){
return vol.mapState();
}
},
VOLUME_ID {
@Override
public String apply (final Volume vol){
return vol.getDisplayName();
}
},
}
private enum FilterDateFunctions implements Function<Volume, Date>{
ATTACHMENT_ATTACH_TIME {
@Override
public Date apply(final Volume vol){
try{
VmVolumeAttachment attachment = VmInstances.lookupVolumeAttachment(vol.getDisplayName());
return attachment.getAttachTime();
}catch(final Throwable e){
return null;
}
}
},
CREATE_TIME {
@Override
public Date apply(final Volume vol){
return vol.getCreationTimestamp();
}
}
}
private enum FilterBooleanFunctions implements Function<Volume, Boolean> {
ATTACHMENT_DELETE_ON_TERMINATION {
@Override
public Boolean apply(final Volume vol){
try{
VmVolumeAttachment attachment = VmInstances.lookupVolumeAttachment(vol.getDisplayName());
return attachment.getDeleteOnTerminate();
}catch (final Throwable e){
return false;
}
}
},
SYSTEM_MANAGED {
@Override
public Boolean apply(final Volume vol){
return Objects.firstNonNull( vol.getSystemManaged( ), Boolean.FALSE );
}
},
}
}