/*************************************************************************
* Copyright 2009-2015 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.images;
import static com.eucalyptus.util.Parameters.checkParam;
import static org.hamcrest.Matchers.notNullValue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import javax.persistence.EntityTransaction;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.hibernate.exception.ConstraintViolationException;
import com.eucalyptus.auth.Accounts;
import com.eucalyptus.auth.AuthException;
import com.eucalyptus.auth.principal.AccountFullName;
import com.eucalyptus.auth.principal.UserFullName;
import com.eucalyptus.compute.common.internal.blockstorage.Snapshot;
import com.eucalyptus.bootstrap.Bootstrap;
import com.eucalyptus.bootstrap.Databases;
import com.eucalyptus.compute.common.BlockDeviceMappingItemType;
import com.eucalyptus.compute.common.CloudMetadatas;
import com.eucalyptus.compute.common.EbsDeviceMapping;
import com.eucalyptus.compute.common.ImageDetails;
import com.eucalyptus.compute.common.ImageMetadata;
import com.eucalyptus.compute.common.ImageMetadata.Architecture;
import com.eucalyptus.compute.common.ImageMetadata.State;
import com.eucalyptus.compute.common.internal.images.BlockStorageDeviceMapping;
import com.eucalyptus.compute.common.internal.images.BlockStorageImageInfo;
import com.eucalyptus.compute.common.internal.images.BootableImageInfo;
import com.eucalyptus.compute.common.internal.images.DeviceMapping;
import com.eucalyptus.compute.common.internal.images.EphemeralDeviceMapping;
import com.eucalyptus.compute.common.internal.images.ImageInfo;
import com.eucalyptus.compute.common.internal.images.ImageInfoTag;
import com.eucalyptus.compute.common.internal.images.KernelImageInfo;
import com.eucalyptus.compute.common.internal.images.MachineImageInfo;
import com.eucalyptus.compute.common.internal.images.PutGetImageInfo;
import com.eucalyptus.compute.common.internal.images.RamdiskImageInfo;
import com.eucalyptus.compute.common.internal.images.SuppressDeviceMappping;
import com.eucalyptus.compute.common.internal.util.MetadataException;
import com.eucalyptus.component.Topology;
import com.eucalyptus.component.id.Eucalyptus;
import com.eucalyptus.compute.common.internal.identifier.ResourceIdentifiers;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.entities.TransactionException;
import com.eucalyptus.entities.TransactionExecutionException;
import com.eucalyptus.entities.TransactionResource;
import com.eucalyptus.entities.Transactions;
import com.eucalyptus.event.EventListener;
import com.eucalyptus.event.Hertz;
import com.eucalyptus.event.Listeners;
import com.eucalyptus.images.ImageManifests.ImageManifest;
import com.eucalyptus.records.Logs;
import com.eucalyptus.compute.common.internal.tags.FilterSupport;
import com.eucalyptus.util.EucalyptusCloudException;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.auth.principal.OwnerFullName;
import com.eucalyptus.util.FUtils;
import com.eucalyptus.util.RestrictedTypes;
import com.eucalyptus.util.RestrictedTypes.QuantityMetricFunction;
import com.eucalyptus.util.Strings;
import com.eucalyptus.util.TypeMapper;
import com.eucalyptus.compute.common.internal.vm.VmVolumeAttachment;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
public class Images extends com.eucalyptus.compute.common.internal.images.Images {
private static Logger LOG = Logger.getLogger( Images.class );
@QuantityMetricFunction( ImageMetadata.class )
public enum CountImages implements Function<OwnerFullName, Long> {
INSTANCE;
@Override
public Long apply( final OwnerFullName input ) {
final EntityTransaction db = Entities.get( ImageInfo.class );
try {
return Entities.count( ImageInfo.named( input, null ) );
} finally {
db.rollback( );
}
}
}
@TypeMapper
public enum KernelImageDetails implements Function<KernelImageInfo, ImageDetails> {
INSTANCE;
@Override
public ImageDetails apply( KernelImageInfo arg0 ) {
ImageDetails i = new ImageDetails( );
i.setName( arg0.getImageName( ) );
i.setDescription( arg0.getDescription( ) );
i.setArchitecture( arg0.getArchitecture( ).toString( ) );
i.setImageId( arg0.getDisplayName( ) );
i.setImageLocation( arg0.getManifestLocation( ) );
i.setImageOwnerId( arg0.getOwnerAccountNumber( ).toString( ) );//TODO:GRZE:verify imageOwnerAlias
i.setImageState( arg0.getState( ).getExternalStateName() );
i.setImageType( arg0.getImageType( ).toString( ) );
i.setIsPublic( arg0.getImagePublic( ) );
i.setPlatform( ImageMetadata.Platform.linux.toString( ) );
// i.setStateReason( arg0.getStateReason( ) );//TODO:GRZE:NOW
// i.setVirtualizationType( arg0.getVirtualizationType( ) );//TODO:GRZE:NOW
// i.getProductCodes().addAll( arg0.getProductCodes() );//TODO:GRZE:NOW
// i.getTags().addAll( arg0.getTags() );//TODO:GRZE:NOW
// i.setHypervisor( arg0.getHypervisor( ) );//TODO:GRZE:NOW
i.setCreationDate( arg0.getCreationTimestamp( ) );
return i;
}
}
@TypeMapper
public enum RamdiskImageDetails implements Function<RamdiskImageInfo, ImageDetails> {
INSTANCE;
@Override
public ImageDetails apply( RamdiskImageInfo arg0 ) {
ImageDetails i = new ImageDetails( );
i.setName( arg0.getImageName( ) );
i.setDescription( arg0.getDescription( ) );
i.setArchitecture( arg0.getArchitecture( ).toString( ) );
i.setImageId( arg0.getDisplayName( ) );
i.setImageLocation( arg0.getManifestLocation( ) );
i.setImageOwnerId( arg0.getOwnerAccountNumber( ).toString( ) );//TODO:GRZE:verify imageOwnerAlias
i.setImageState( arg0.getState().getExternalStateName() );
i.setImageType( arg0.getImageType( ).toString( ) );
i.setIsPublic( arg0.getImagePublic( ) );
i.setPlatform( ImageMetadata.Platform.linux.toString( ) );
// i.setStateReason( arg0.getStateReason( ) );//TODO:GRZE:NOW
// i.setVirtualizationType( arg0.getVirtualizationType( ) );//TODO:GRZE:NOW
// i.getProductCodes().addAll( arg0.getProductCodes() );//TODO:GRZE:NOW
// i.getTags().addAll( arg0.getTags() );//TODO:GRZE:NOW
// i.setHypervisor( arg0.getHypervisor( ) );//TODO:GRZE:NOW
i.setCreationDate( arg0.getCreationTimestamp( ) );
return i;
}
}
@TypeMapper
public enum BlockStorageImageDetails implements Function<BlockStorageImageInfo, ImageDetails> {
INSTANCE;
@Override
public ImageDetails apply( BlockStorageImageInfo arg0 ) {
ImageDetails i = new ImageDetails( );
i.setName( arg0.getImageName( ) );
i.setDescription( arg0.getDescription() );
i.setArchitecture( arg0.getArchitecture().toString() );
i.setRootDeviceName( arg0.getRootDeviceName() );
i.setRootDeviceType( arg0.getRootDeviceType() );
i.setImageId( arg0.getDisplayName() );
i.setImageLocation( arg0.getOwnerAccountNumber( ) + "/" + arg0.getImageName( ) );
i.setImageOwnerId( arg0.getOwnerAccountNumber( ).toString() );//TODO:GRZE:verify imageOwnerAlias
i.setImageState( arg0.getState( ).getExternalStateName() );
i.setImageType( arg0.getImageType( ).toString( ) );
i.setIsPublic( arg0.getImagePublic( ) );
i.setImageType( arg0.getImageType( ).toString( ) );
i.setKernelId( arg0.getKernelId( ) );
i.setRamdiskId( arg0.getRamdiskId( ) );
i.setPlatform( arg0.getPlatform( ).toString( ) );
if (arg0.getVirtualizationType() == null)
i.setVirtualizationType(ImageMetadata.VirtualizationType.hvm.toString());
else
i.setVirtualizationType(arg0.getVirtualizationType().toString());
i.getBlockDeviceMappings( ).addAll( Collections2.transform( arg0.getDeviceMappings( ), DeviceMappingDetails.INSTANCE ) );
// i.setStateReason( arg0.getStateReason( ) );//TODO:GRZE:NOW
// i.setVirtualizationType( arg0.getVirtualizationType( ) );//TODO:GRZE:NOW
// i.getProductCodes().addAll( arg0.getProductCodes() );//TODO:GRZE:NOW
// i.getTags().addAll( arg0.getTags() );//TODO:GRZE:NOW
// i.setHypervisor( arg0.getHypervisor( ) );//TODO:GRZE:NOW
i.setCreationDate( arg0.getCreationTimestamp( ) );
return i;
}
}
@TypeMapper
public enum MachineImageDetails implements Function<MachineImageInfo, ImageDetails> {
INSTANCE;
@Override
public ImageDetails apply( MachineImageInfo arg0 ) {
ImageDetails i = new ImageDetails( );
i.setName( arg0.getImageName( ) );
i.setDescription( arg0.getDescription( ) );
i.setArchitecture( arg0.getArchitecture( ).toString( ) );
i.setRootDeviceName( arg0.getRootDeviceName() );
i.setRootDeviceType( arg0.getRootDeviceType() );
i.setImageId( arg0.getDisplayName() );
i.setImageLocation( arg0.getManifestLocation( ) );
i.setImageOwnerId( arg0.getOwnerAccountNumber( ).toString() );//TODO:GRZE:verify imageOwnerAlias
i.setImageState( arg0.getState( ).getExternalStateName() );
i.setImageType( arg0.getImageType( ).toString( ) );
i.setIsPublic( arg0.getImagePublic( ) );
i.setImageType( arg0.getImageType( ).toString( ) );
i.setKernelId( arg0.getKernelId( ) );
i.setRamdiskId( arg0.getRamdiskId( ) );
i.setPlatform( arg0.getPlatform( ).toString( ) );
if (arg0.getVirtualizationType() == null){
if(ImageMetadata.Platform.windows.equals( arg0.getPlatform( )))
i.setVirtualizationType(ImageMetadata.VirtualizationType.hvm.toString());
else
i.setVirtualizationType(ImageMetadata.VirtualizationType.paravirtualized.toString());
}else
i.setVirtualizationType(arg0.getVirtualizationType().toString());
i.getBlockDeviceMappings( ).addAll( Collections2.transform( arg0.getDeviceMappings( ), DeviceMappingDetails.INSTANCE ) );
// i.setStateReason( arg0.getStateReason( ) );//TODO:GRZE:NOW
// i.setVirtualizationType( arg0.getVirtualizationType( ) );//TODO:GRZE:NOW
// i.getProductCodes().addAll( arg0.getProductCodes() );//TODO:GRZE:NOW
// i.setHypervisor( arg0.getHypervisor( ) );//TODO:GRZE:NOW
i.setCreationDate( arg0.getCreationTimestamp( ) );
return i;
}
}
@TypeMapper
public enum DeviceMappingDetails implements Function<DeviceMapping, BlockDeviceMappingItemType> {
INSTANCE;
@Override
public BlockDeviceMappingItemType apply( DeviceMapping input ) {
BlockDeviceMappingItemType ret = new BlockDeviceMappingItemType( );
ret.setDeviceName( input.getDeviceName( ) );
if ( input instanceof BlockStorageDeviceMapping ) {
final BlockStorageDeviceMapping ebsDev = ( BlockStorageDeviceMapping ) input;
ret.setEbs( new EbsDeviceMapping( ) {
{
this.setVirtualName( ebsDev.getVirtualName( ) );
this.setSnapshotId( ebsDev.getSnapshotId( ) );
this.setVolumeSize( ebsDev.getSize( ) );
this.setDeleteOnTermination( ebsDev.getDelete( ) );
}
} );
} else {
ret.setVirtualName( input.getVirtualName( ) );
}
return ret;
}
}
// Changing to method signature to accept a default size for generating ebs mappings.
// The default size will be used when both snapshot ID and volume size are missing. (AWS compliance)
// The default size is usually size of the root device volume in case of boot from ebs images.
public static Function<BlockDeviceMappingItemType, DeviceMapping> deviceMappingGenerator( final ImageInfo parent,
final Integer rootVolSize ) {
return deviceMappingGenerator( parent, rootVolSize, Collections.<String,String>emptyMap() );
}
public static Function<BlockDeviceMappingItemType, DeviceMapping> deviceMappingGenerator(
final ImageInfo parent,
final Integer rootVolSize,
final Map<String,String> deviceNameMap
) {
return new Function<BlockDeviceMappingItemType, DeviceMapping>( ) {
@Override
public DeviceMapping apply( BlockDeviceMappingItemType input ) {
checkParam( input, notNullValue() );
checkParam( input.getDeviceName(), notNullValue() );
if ( isEbsMapping( input ) ) {
final EbsDeviceMapping ebsInfo = input.getEbs( );
Integer size = -1;
final String snapshotId = ResourceIdentifiers.tryNormalize( ).apply( ebsInfo.getSnapshotId( ) );
if ( ebsInfo.getVolumeSize() != null ) {
size = ebsInfo.getVolumeSize();
} else if ( ebsInfo.getSnapshotId() != null ){
try {
Snapshot snap = Transactions.find( Snapshot.named( null, snapshotId ) );
size = snap.getVolumeSize( );
if ( ebsInfo.getVolumeSize( ) != null && ebsInfo.getVolumeSize( ) >= snap.getVolumeSize( ) ) {
size = ebsInfo.getVolumeSize( );
}
} catch ( NoSuchElementException ex ) {
throw Exceptions.toUndeclared( new MetadataException( "Snapshot " + ebsInfo.getSnapshotId() + " does not exist" ) );
} catch ( ExecutionException ex ) {
LOG.error( "Unable to find snapshot " + ebsInfo.getSnapshotId() , ex );
throw Exceptions.toUndeclared( new MetadataException( "Snapshot " + ebsInfo.getSnapshotId() + " does not exist" ) );
}
} else {
size = rootVolSize;
}
final String mappedDeviceName = deviceNameMap.containsKey( input.getDeviceName() ) ?
deviceNameMap.get( input.getDeviceName() ) :
input.getDeviceName();
return new BlockStorageDeviceMapping( parent, mappedDeviceName, input.getEbs( ).getVirtualName( ), snapshotId, size, ebsInfo.getDeleteOnTermination( ) );
} else if ( input.getVirtualName( ) != null ) {
return new EphemeralDeviceMapping( parent, input.getDeviceName( ), input.getVirtualName( ) );
} else {
return new SuppressDeviceMappping( parent, input.getDeviceName( ) );
}
}
};
}
// Utility method (and refactored code) for figuring out if a device mapping is an ebs mapping
public static Boolean isEbsMapping( BlockDeviceMappingItemType input ) {
if( input.getEbs( ) != null && input.getVirtualName() == null && input.getNoDevice() == null ) {
return Boolean.TRUE;
}
return Boolean.FALSE;
}
public static <T extends ImageInfo> Predicate<T> inState( final Set<ImageMetadata.State> states ) {
return new Predicate<T>() {
@Override
public boolean apply( final T imageInfo ) {
return states.contains( imageInfo.getState() );
}
};
}
// Predicate for comparing image state deduced from ebs mapping against a set of input states
public static Predicate<BlockStorageDeviceMapping> imageInState( final Set<ImageMetadata.State> states ) {
return new Predicate<BlockStorageDeviceMapping>() {
@Override
public boolean apply( final BlockStorageDeviceMapping arg0 ) {
return states.contains( arg0.getParent().getState() );
}
};
}
public static enum DeviceMappingValidationOption {
AllowSuppressMapping,
AllowEbsMapping,
AllowDevSda1,
SkipExtraEphemeral,
;
/**
* Is this option present in the given set?
*/
public boolean present( final Set<DeviceMappingValidationOption> options ) {
return options != null && options.contains( this );
}
}
/**
* <p>Validates the correctness of a block device mapping</p>
* <p>Returns block device mapping that has only one ephemeral device if multiple were passed</p>
* <p>Invalid cases:</p>
* <ul>
* <li>Device name is assigned multiple times</li>
* <li>Device name is a partition</li>
* <li>Suppress mapping is not valid if <code>isSuppressMappingValid</code> argument is set to <code>Boolean.False<code></li>
* <li>Ebs mapping is not valid if <code>isEbsMappingValid</code> argument is set to <code>Boolean.False<code></li>
* <li>Volume size is 0</li>
* <li>Volume size is smaller than the snapshot size </li>
* </ul>
*
* @param bdms List of <code>BlockDeviceMappingItemType</code>
* @param options Validation options
* @throws MetadataException for any validation failure
*/
public static ArrayList<BlockDeviceMappingItemType> validateBlockDeviceMappings(
final List<BlockDeviceMappingItemType> bdms,
final Set<DeviceMappingValidationOption> options
) throws MetadataException {
ArrayList<BlockDeviceMappingItemType> resultedBdms = new ArrayList<>();
if ( bdms != null ) {
Set<String> deviceNames = Sets.newHashSet();
boolean ephemeralAlreadyPresent = false;
int ephemeralCount = 0;
for ( final BlockDeviceMappingItemType bdm : bdms ) {
checkParam( bdm, notNullValue( ) );
checkParam( bdm.getDeviceName( ), notNullValue( ) );
if( !deviceNames.add( bdm.getDeviceName( ).replace("/dev/","") ) ) {
throw new MetadataException( bdm.getDeviceName() + " is assigned multiple times" );
}
if ( DeviceMappingValidationOption.SkipExtraEphemeral.present(options) && StringUtils.isNotBlank(bdm.getVirtualName( )) ) {
if( !ephemeralAlreadyPresent ) {
ephemeralAlreadyPresent = true;
} else {
// skip all ephemerals except the first one
continue;
}
}
resultedBdms.add(bdm);
}
final Set<String> fullDeviceNames = Sets.newHashSet();
for(final String name : deviceNames){
fullDeviceNames.add(String.format("/dev/%s", name));
}
deviceNames = fullDeviceNames;
for ( final BlockDeviceMappingItemType bdm : resultedBdms ) {
if ( !bdm.getDeviceName().matches("(/dev/)?([svh]|xv)d[a-z]([1-9])*")){
throw new MetadataException("Device name " + bdm.getDeviceName() + " is invalid");
} else if( bdm.getDeviceName().matches(".*\\d\\Z") &&
!(DeviceMappingValidationOption.AllowDevSda1.present(options) && DEFAULT_PARTITIONED_ROOT_DEVICE.equals( bdm.getDeviceName() ) && !deviceNames.contains(DEFAULT_ROOT_DEVICE)) ) {
throw new MetadataException( bdm.getDeviceName() + " is not supported. Device name cannot be a partition");
} else if ( bdm.getNoDevice() != null && bdm.getNoDevice() ) {
if ( !DeviceMappingValidationOption.AllowSuppressMapping.present( options ) ) {
throw new MetadataException( "Block device mapping for " + bdm.getDeviceName() + " cannot be suppressed" );
}
} else if ( StringUtils.isNotBlank( bdm.getVirtualName( ) ) ) {
if ( !bdm.getVirtualName( ).matches( "ephemeral[0123]" ) ) {
throw new MetadataException( "Virtual device name must be of the form ephemeral[0123]. Fix the mapping for " + bdm.getDeviceName() );
}
if ( !DeviceMappingValidationOption.SkipExtraEphemeral.present(options) ){
ephemeralCount++;
if( ephemeralCount > 1 ) {
throw new MetadataException( "Only one ephemeral device is supported. More than one ephemeral device mappings found" );
}
}
} else if ( null != bdm.getEbs( ) ) {
if ( !DeviceMappingValidationOption.AllowEbsMapping.present( options ) ) {
throw new MetadataException( "Ebs block device mappings are not supported" );
}
final EbsDeviceMapping ebsInfo = bdm.getEbs( );
if ( ebsInfo.getSnapshotId() != null ) {
final Snapshot snap;
try {
snap = Transactions.find( Snapshot.named( null, ResourceIdentifiers.tryNormalize( ).apply( ebsInfo.getSnapshotId( ) ) ) );
} catch ( Exception ex ) {
LOG.error("Failed to find snapshot " + ebsInfo.getSnapshotId(), ex);
throw new MetadataException("Unable to find snapshot " + ebsInfo.getSnapshotId() + " in the block device mapping for " + bdm.getDeviceName());
}
if ( ebsInfo.getVolumeSize( ) != null && ebsInfo.getVolumeSize( ) < snap.getVolumeSize( ) ) {
throw new MetadataException( "Size of the volume cannot be smaller than the source snapshot for " + bdm.getDeviceName() );
}
} else if ( ebsInfo.getVolumeSize() != null && ebsInfo.getVolumeSize() == 0 ) {
throw new MetadataException( "Volume size for " + bdm.getDeviceName() + " cannot be 0");
}
} else {
// It should never get here
throw new MetadataException( "Incorrectly constructed block device mapping for " + bdm.getDeviceName() + " . Refer to documentation" );
}
}
}
return resultedBdms;
}
public static boolean isImageNameValid(final String imgName){
if(imgName==null)
return false;
if (!imgName.matches("[A-Za-z0-9()./_-]+"))
return false;
if (imgName.length() < 3 || imgName.length() > 128)
return false;
return true;
}
public static boolean isImageDescriptionValid(final String imgDescription){
if(imgDescription==null)
return false;
if(imgDescription.length()> 255)
return false;
return true;
}
public static ImageInfo ALL = new ImageInfo( );
public static List<ImageInfo> listAllImages( ) {
final List<ImageInfo> images = Lists.newArrayList( );
final EntityTransaction db = Entities.get(ImageInfo.class);
try {
final List<ImageInfo> found = Entities.query( Images.ALL, true );
images.addAll( found );
db.rollback( );
} catch ( final Exception e ) {
db.rollback( );
LOG.error("failed to query images", e);
} finally{
if(db.isActive())
db.rollback();
}
return images;
}
public static void enableImage( String imageId ) throws NoSuchImageException {
final EntityTransaction db = Entities.get(ImageInfo.class);
try {
final ImageInfo img = Entities.uniqueResult(Images.exampleWithImageId( imageId ));
img.setState( ImageMetadata.State.available );
db.commit();
} catch ( final NoSuchElementException e ) {
db.rollback();
throw new NoSuchImageException("Failed to lookup image: " + imageId, e);
} catch ( final Exception e) {
db.rollback();
throw new NoSuchImageException( "Failed to lookup image: " + imageId, e );
} finally{
if(db.isActive())
db.rollback();
}
}
public static void setImageState( String imageId, ImageMetadata.State state) throws NoSuchImageException {
final EntityTransaction db = Entities.get( ImageInfo.class );
try {
ImageInfo img = Entities.uniqueResult( Images.exampleWithImageId( imageId ) );
img.setState( state );
db.commit( );
} catch ( final Exception e ) {
db.rollback( );
throw new NoSuchImageException( "Failed to update image state: "+imageId);
} finally{
if(db.isActive())
db.rollback();
}
}
public static void deregisterImage( String imageId ) throws NoSuchImageException, InstanceNotTerminatedException {
EntityTransaction tx = Entities.get( ImageInfo.class );
try {
ImageInfo img = Entities.uniqueResult( Images.exampleWithImageId( imageId ) );
if ( ImageMetadata.State.deregistered.equals( img.getState( ) ) ||
ImageMetadata.State.failed.equals( img.getState())) {
Entities.delete( img );
} else {
if( img instanceof MachineImageInfo){
final String runManifestLocation = ((MachineImageInfo)img).getRunManifestLocation();
final String manifestLocation = ((MachineImageInfo)img).getManifestLocation();
// cleanup system generated buckets if exist
if(!manifestLocation.equals(runManifestLocation))
img.setState(ImageMetadata.State.deregistered_cleanup);
else
img.setState( ImageMetadata.State.deregistered );
} else
img.setState( ImageMetadata.State.deregistered );
}
tx.commit( );
} catch ( ConstraintViolationException cve ) {
tx.rollback( );
throw new InstanceNotTerminatedException("To deregister " + imageId + " all associated instances must be in the terminated state.");
} catch ( TransactionException ex ) {
tx.rollback( );
throw new NoSuchImageException( "Failed to lookup image: " + imageId, ex );
} catch ( NoSuchElementException ex ) {
tx.rollback( );
throw new NoSuchImageException( "Failed to lookup image: " + imageId, ex );
} finally {
if ( tx.isActive() ) tx.rollback();
}
}
public static KernelImageInfo lookupKernel( final String kernelId ) {
EntityTransaction tx = Entities.get( KernelImageInfo.class );
KernelImageInfo ret = new KernelImageInfo( );
try {
ret = Entities.uniqueResult( Images.exampleKernelWithImageId( kernelId ) );
tx.commit( );
} catch ( Exception e ) {
LOG.error( "Kernel '" + kernelId + "' does not exist" + e );
throw new NoSuchElementException( "InvalidAMIID.NotFound" );
} finally {
if ( tx.isActive( ) ) tx.rollback( );
}
return ret;
}
public static RamdiskImageInfo exampleRamdiskWithImageId( final String imageId ) {
return new RamdiskImageInfo( imageId );
}
public static RamdiskImageInfo lookupRamdisk( final String ramdiskId ) {
EntityTransaction tx = Entities.get( RamdiskImageInfo.class );
RamdiskImageInfo ret = new RamdiskImageInfo( );
try {
ret = Entities.uniqueResult( Images.exampleRamdiskWithImageId( ramdiskId ) );
tx.commit( );
} catch ( Exception e ) {
LOG.error( "Ramdisk '" + ramdiskId + "' does not exist" + e );
throw new NoSuchElementException( "InvalidAMIID.NotFound" );
} finally {
if ( tx.isActive( ) ) tx.rollback( );
}
return ret;
}
public static ImageInfo lookupImage( String imageId ) {
final EntityTransaction db = Entities.get(ImageInfo.class);
try{
final ImageInfo found = Entities.uniqueResult(Images.exampleWithImageId(imageId));
db.commit();
return found;
}catch(final NoSuchElementException ex){
db.rollback();
throw ex;
}catch(final Exception ex){
db.rollback();
throw Exceptions.toUndeclared(ex);
}finally{
if(db.isActive())
db.rollback();
}
}
public static Predicate<BlockDeviceMappingItemType> findEbsRoot( final String rootDevName ) {
return findEbsRoot( rootDevName, true );
}
public static Predicate<BlockDeviceMappingItemType> findEbsRootOptionalSnapshot( final String rootDevName ) {
return findEbsRoot( rootDevName, false );
}
private static Predicate<BlockDeviceMappingItemType> findEbsRoot( final String rootDevName,
final boolean requireSnapshotId ) {
return new Predicate<BlockDeviceMappingItemType>( ) {
@Override
public boolean apply( BlockDeviceMappingItemType input ) {
return
rootDevName.equals( input.getDeviceName( ) ) &&
input.getEbs( ) != null &&
(!requireSnapshotId || input.getEbs( ).getSnapshotId( ) != null);
}
};
}
public static Predicate<BlockDeviceMappingItemType> findCreateImageRoot( ) {
return new Predicate<BlockDeviceMappingItemType> ( ) {
@Override
public boolean apply ( BlockDeviceMappingItemType input) {
return input.getEbs() != null &&
"snap-EUCARESERVED".equals(input.getEbs().getSnapshotId( ));
}
};
}
public static Predicate<DeviceMapping> findDeviceMap ( final String deviceName ) {
return new Predicate<DeviceMapping>( ) {
@Override
public boolean apply( DeviceMapping input ) {
return deviceName.equals( input.getDeviceName( ) );
}
};
}
public static Predicate<BlockDeviceMappingItemType> findBlockDeviceMappingItempType ( final String deviceName ) {
return new Predicate<BlockDeviceMappingItemType>( ) {
@Override
public boolean apply( BlockDeviceMappingItemType input ) {
return deviceName.equals( input.getDeviceName( ) );
}
};
}
public static Predicate<VmVolumeAttachment> findEbsRootVolumeAttachment( final String rootDevName ) {
return new Predicate<VmVolumeAttachment>() {
@Override
public boolean apply( VmVolumeAttachment input ) {
return ( input.getDevice().equals(rootDevName) || input.getIsRootDevice() );
}
};
}
public static ImageInfo createFromDeviceMapping(
final UserFullName userFullName,
final String imageName,
final String imageDescription,
final ImageMetadata.Platform platform,
String eki,
String eri,
final String rootDeviceName,
final List<BlockDeviceMappingItemType> blockDeviceMappings,
final Architecture imageArch
) throws EucalyptusCloudException {
final ImageMetadata.Platform imagePlatform = platform;
if(ImageMetadata.Platform.windows.equals(imagePlatform)){
eki = null;
eri = null;
}
// Block device mappings have been verified before control gets here.
// If anything has changed with regard to the snapshot state, it will be caught while data structures for the image.
final BlockDeviceMappingItemType rootBlockDevice = Iterables.find( blockDeviceMappings, findEbsRoot( rootDeviceName ), null );
if ( rootBlockDevice == null ) {
throw new EucalyptusCloudException( "Failed to create image, root device mapping not found: " + rootDeviceName );
}
final String snapshotId = ResourceIdentifiers.tryNormalize( ).apply( rootBlockDevice.getEbs( ).getSnapshotId( ) );
Snapshot snap;
try {
snap = Transactions.one(
Snapshot.named( userFullName.asAccountFullName(), snapshotId ),
RestrictedTypes.filterPrivileged( ),
Functions.<Snapshot>identity( ) );
} catch ( NoSuchElementException ex ) {
throw new EucalyptusCloudException( "Failed to create image from specified block device mapping: " + rootBlockDevice + " because of: Snapshot not found " + snapshotId );
} catch ( TransactionExecutionException ex ) {
throw new EucalyptusCloudException( "Failed to create image from specified block device mapping: " + rootBlockDevice + " because of: " + ex.getMessage( ) );
} catch ( ExecutionException ex ) {
LOG.error( ex, ex );
throw new EucalyptusCloudException( "Failed to create image from specified block device mapping: " + rootBlockDevice + " because of: " + ex.getMessage( ) );
}
final Integer suppliedVolumeSize = rootBlockDevice.getEbs().getVolumeSize() != null ? rootBlockDevice.getEbs().getVolumeSize() : snap.getVolumeSize();
final Long imageSizeBytes = suppliedVolumeSize * 1024l * 1024l * 1024l;
final Boolean targetDeleteOnTermination = Boolean.TRUE.equals( rootBlockDevice.getEbs( ).getDeleteOnTermination( ) );
final String imageId = ResourceIdentifiers.generateString( ImageMetadata.Type.machine.getTypePrefix() );
final boolean mapRoot = DEFAULT_PARTITIONED_ROOT_DEVICE.equals( rootDeviceName );
BlockStorageImageInfo ret = new BlockStorageImageInfo( userFullName, imageId, imageName, imageDescription, imageSizeBytes,
imageArch, imagePlatform, eki, eri, snap.getDisplayName( ),
targetDeleteOnTermination,
mapRoot ? DEFAULT_ROOT_DEVICE : rootDeviceName,
getImagePublicVisibilityDefault( ) );
final EntityTransaction tx = Entities.get( BlockStorageImageInfo.class );
try {
ret = Entities.merge( ret );
Iterables.addAll(
ret.getDeviceMappings( ),
Iterables.transform( blockDeviceMappings, Images.deviceMappingGenerator(
ret,
suppliedVolumeSize,
mapRoot ?
Collections.singletonMap( DEFAULT_PARTITIONED_ROOT_DEVICE, DEFAULT_ROOT_DEVICE ) :
Collections.<String,String>emptyMap( )) ) );
ret.setImageFormat(ImageMetadata.ImageFormat.fulldisk.toString());
ret.setState( ImageMetadata.State.available );
tx.commit( );
LOG.info( "Registering image pk=" + ret.getDisplayName( ) + " ownerId=" + userFullName );
} catch ( Exception e ) {
throw new EucalyptusCloudException( "Failed to register image using snapshot: " + snapshotId + " because of: " + e.getMessage( ), e );
} finally {
if ( tx.isActive() ) tx.rollback();
}
return ret;
}
public static ImageInfo createPendingFromDeviceMapping(UserFullName creator,
String imageNameArg,
String imageDescription,
ImageMetadata.Architecture requestArch,
ImageMetadata.Platform imagePlatform,
final List<BlockDeviceMappingItemType> blockDeviceMappings
) throws Exception {
final String imageId = ResourceIdentifiers.generateString( ImageMetadata.Type.machine.getTypePrefix() );
BlockStorageImageInfo ret = new BlockStorageImageInfo( creator, imageId, imageNameArg, imageDescription,
new Long(-1), requestArch, imagePlatform, null, null, "snap-EUCARESERVED", false, Images.DEFAULT_ROOT_DEVICE,
getImagePublicVisibilityDefault( ) );
/// device with snap-EUCARESERVED is the placeholder to indicate register is for create-image only
/// actual root device with snapshot is filled in later
BlockDeviceMappingItemType toRemove = null;
for(final BlockDeviceMappingItemType device : blockDeviceMappings){
if(Images.findCreateImageRoot().apply(device))
toRemove = device;
}
if(toRemove!=null)
blockDeviceMappings.remove(toRemove);
final EntityTransaction tx = Entities.get( BlockStorageImageInfo.class );
try {
ret = Entities.merge( ret );
ret.setState(ImageMetadata.State.pending);
ret.setImageFormat(ImageMetadata.ImageFormat.fulldisk.toString());
ret.getDeviceMappings( ).addAll( Lists.transform( blockDeviceMappings, Images.deviceMappingGenerator( ret, -1 ) ) );
tx.commit( );
LOG.info( "Registering image pk=" + ret.getDisplayName( ) + " ownerId=" + creator );
} catch ( Exception e ) {
tx.rollback( );
throw new EucalyptusCloudException( "Failed to register pending bfebs image because of: " + e.getMessage( ), e );
} finally {
if (tx.isActive())
tx.rollback();
}
return ret;
}
/***
* @param imageId: id of an image already registered as pending state
* @param accountFullName
* @param blockDeviceMappings: the mapping that contains the root device
* @return
* @throws EucalyptusCloudException
*/
public static ImageInfo updateWithDeviceMapping( String imageId, AccountFullName accountFullName, final String rootDeviceName,
final List<BlockDeviceMappingItemType> blockDeviceMappings ) throws EucalyptusCloudException {
// Block device mappings have been verified before control gets here.
// If anything has changed with regard to the snapshot state, it will be caught while data structures for the image.
final BlockDeviceMappingItemType rootBlockDevice = Iterables.find( blockDeviceMappings, findEbsRoot( rootDeviceName ) );
final String snapshotId = ResourceIdentifiers.tryNormalize( ).apply( rootBlockDevice.getEbs( ).getSnapshotId( ) );
try {
Snapshot snap = Transactions.find( Snapshot.named( accountFullName, snapshotId ) );
if ( !accountFullName.getAccountNumber( ).equals( snap.getOwnerAccountNumber( ) ) ) {
throw new EucalyptusCloudException( "Failed to create image from specified block device mapping: " + rootBlockDevice
+ " because of: you must be the owner of the source snapshot." );
}
Integer suppliedVolumeSize = rootBlockDevice.getEbs().getVolumeSize() != null ? rootBlockDevice.getEbs().getVolumeSize() : snap.getVolumeSize();
Long imageSizeBytes = suppliedVolumeSize * 1024l * 1024l * 1024l;
Boolean targetDeleteOnTermination = Boolean.TRUE.equals( rootBlockDevice.getEbs( ).getDeleteOnTermination( ) );
BlockStorageImageInfo ret = null;
final EntityTransaction tx = Entities.get( BlockStorageImageInfo.class );
try {
ret = (BlockStorageImageInfo) Entities.uniqueResult(BlockStorageImageInfo.named(imageId));
final List<DeviceMapping> mappings = Lists.transform( blockDeviceMappings, Images.deviceMappingGenerator( ret, suppliedVolumeSize ) );
ret.getDeviceMappings( ).addAll( mappings );
ret.setSnapshotId(snap.getDisplayName());
ret.setDeleteOnTerminate(targetDeleteOnTermination);
ret.setImageSizeBytes(imageSizeBytes);
ret.setRootDeviceName(rootDeviceName);
ret.setState( ImageMetadata.State.available );
Entities.persist(ret);
tx.commit( );
LOG.info( "Registering image pk=" + ret.getDisplayName( ) + " owner=" + accountFullName );
} catch ( Exception e ) {
tx.rollback( );
throw new EucalyptusCloudException( "Failed to register image using snapshot: " + snapshotId + " because of: " + e.getMessage( ), e );
}
return ret;
} catch ( TransactionExecutionException ex ) {
throw new EucalyptusCloudException( "Failed to update image with specified block device mapping: " + rootBlockDevice + " because of: " + ex.getMessage( ) );
} catch ( ExecutionException ex ) {
LOG.error( ex, ex );
throw new EucalyptusCloudException( "Failed to update image with specified block device mapping: " + rootBlockDevice + " because of: " + ex.getMessage( ) );
}
}
public static ImageInfo registerFromManifest( UserFullName creator,
String imageNameArg,
String imageDescription,
ImageMetadata.Architecture requestArch,
ImageMetadata.VirtualizationType virtType,
ImageMetadata.Platform platform,
ImageMetadata.ImageFormat imgFormat,
String eki,
String eri,
ImageManifest manifest ) throws Exception {
PutGetImageInfo ret = prepareFromManifest( creator, imageNameArg, imageDescription, requestArch, virtType, platform, imgFormat, eki, eri, manifest );
ret.setState( ImageMetadata.State.available );
ret = persistRegistration( creator, manifest, ret );
return ret;
}
private static PutGetImageInfo prepareFromManifest( UserFullName creator,
String imageNameArg,
String imageDescription,
ImageMetadata.Architecture requestArch,
ImageMetadata.VirtualizationType virtType,
ImageMetadata.Platform platform,
ImageMetadata.ImageFormat format,
String eki,
String eri,
ImageManifest manifest ) throws Exception {
PutGetImageInfo ret = null;
String imageName = ( imageNameArg != null )
? imageNameArg
: manifest.getName( );
eki = ( eki != null )
? eki
: manifest.getKernelId( );
eri = ( eri != null )
? eri
: manifest.getRamdiskId( );
ImageMetadata.Architecture imageArch = ( requestArch != null )
? requestArch
: manifest.getArchitecture( );
final ImageMetadata.Platform imagePlatform = platform;
switch ( manifest.getImageType( ) ) {
case kernel:
ret = new KernelImageInfo( creator, ResourceIdentifiers.generateString( ImageMetadata.Type.kernel.getTypePrefix() ),
imageName, imageDescription, manifest.getSize( ), imageArch, imagePlatform,
manifest.getImageLocation( ), manifest.getBundledSize( ), manifest.getChecksum( ), manifest.getChecksumType( ),
getImagePublicVisibilityDefault() );
break;
case ramdisk:
ret = new RamdiskImageInfo( creator, ResourceIdentifiers.generateString( ImageMetadata.Type.ramdisk.getTypePrefix() ),
imageName, imageDescription, manifest.getSize( ), imageArch, imagePlatform,
manifest.getImageLocation( ), manifest.getBundledSize( ), manifest.getChecksum( ), manifest.getChecksumType( ),
getImagePublicVisibilityDefault() );
break;
case machine:
if(ImageMetadata.Platform.windows.equals(imagePlatform)){
virtType = ImageMetadata.VirtualizationType.hvm;
}
String manifestAmi = manifest.getAmi();
ret = new MachineImageInfo( creator, ResourceIdentifiers.generateString( ImageMetadata.Type.machine.getTypePrefix() ),
imageName, imageDescription, manifest.getSize( ), imageArch, imagePlatform,
manifest.getImageLocation( ), manifest.getBundledSize( ), manifest.getChecksum( ), manifest.getChecksumType( ), eki, eri , virtType,
manifestAmi, manifest.getRoot(),
getImagePublicVisibilityDefault());
ret.setImageFormat(format.toString());
if( ImageMetadata.VirtualizationType.hvm.equals(virtType)
|| (!manifestAmi.isEmpty() && !ImageManager.isPathAPartition(manifestAmi) )){
((MachineImageInfo) ret).setRunManifestLocation(manifest.getImageLocation());
}
break;
}
if ( ret == null ) {
throw new IllegalArgumentException( "Failed to prepare image using the provided image manifest: " + manifest );
} else {
ret.setSignature( manifest.getSignature( ) );
ret.setManifestHash( ImageManifests.calculateManifestHash( manifest.getManifest() ) );
return ret;
}
}
private static PutGetImageInfo persistRegistration( UserFullName creator, ImageManifest manifest, PutGetImageInfo ret ) throws Exception {
try(TransactionResource tx = Entities.transactionFor( PutGetImageInfo.class )) {
ret = Entities.merge( ret );
tx.commit( );
LOG.info( "Registering image pk=" + ret.getDisplayName( ) + " ownerId=" + creator );
} catch ( Exception e ) {
throw new EucalyptusCloudException( "Failed to register image: " + manifest + " because of: " + e.getMessage( ), e );
}
// TODO:GRZE:RESTORE
// for( String p : extractProductCodes( inputSource, xpath ) ) {
// imageInfo.addProductCode( p );
// }
// imageInfo.grantPermission( ctx.getAccount( ) );
return ret;
}
public static ImageConfiguration configuration( ) {
return ImageConfiguration.getInstance( );
}
public static void setConversionTaskId(final String imageId, final String taskId){
try ( final TransactionResource db =
Entities.transactionFor( ImageInfo.class ) ) {
try{
final ImageInfo entity = Entities.uniqueResult(Images.exampleWithImageId(imageId));
((MachineImageInfo)entity).setImageConversionId(taskId);
Entities.persist(entity);
db.commit();
}catch(final Exception ex){
throw Exceptions.toUndeclared(ex);
}
}
}
public static void setImageFormat(final String imageId, ImageMetadata.ImageFormat format){
try ( final TransactionResource db =
Entities.transactionFor( ImageInfo.class ) ) {
try{
final ImageInfo entity = Entities.uniqueResult(Images.exampleWithImageId(imageId));
entity.setImageFormat(format.toString());
Entities.persist(entity);
db.commit();
}catch(final Exception ex){
throw Exceptions.toUndeclared(ex);
}
}
}
public static void setRunManifestLocation(final String imageId, final String runManifestLocation){
try ( final TransactionResource db =
Entities.transactionFor( ImageInfo.class ) ) {
try{
final ImageInfo entity = Entities.uniqueResult(Images.exampleWithImageId(imageId));
((MachineImageInfo)entity).setRunManifestLocation(runManifestLocation);
Entities.persist(entity);
db.commit();
}catch(final Exception ex){
throw Exceptions.toUndeclared(ex);
}
}
}
public static void setImageVirtualizationType(final String imageId, ImageMetadata.VirtualizationType virtType){
try ( final TransactionResource db =
Entities.transactionFor( ImageInfo.class ) ) {
try{
final ImageInfo entity = Entities.uniqueResult(Images.exampleWithImageId(imageId));
((MachineImageInfo) entity).setVirtualizationType(virtType);
Entities.persist(entity);
db.commit();
}catch(final Exception ex){
throw Exceptions.toUndeclared(ex);
}
}
}
public static class ImageInfoFilterSupport extends FilterSupport<ImageInfo> {
public ImageInfoFilterSupport() {
super( builderFor( ImageInfo.class )
.withTagFiltering( ImageInfoTag.class, "image" )
.withStringProperty( "architecture", FilterStringFunctions.ARCHITECTURE )
.withBooleanSetProperty( "block-device-mapping.delete-on-termination", FilterBooleanSetFunctions.BLOCK_DEVICE_MAPPING_DELETE_ON_TERMINATION )
.withStringSetProperty( "block-device-mapping.device-name", FilterStringSetFunctions.BLOCK_DEVICE_MAPPING_DEVICE_NAME )
.withStringSetProperty( "block-device-mapping.snapshot-id", FilterStringSetFunctions.BLOCK_DEVICE_MAPPING_SNAPSHOT_ID )
.withIntegerSetProperty( "block-device-mapping.volume-size", FilterIntegerSetFunctions.BLOCK_DEVICE_MAPPING_VOLUME_SIZE )
.withConstantProperty( "block-device-mapping.volume-type", "standard" )
.withStringProperty( "description", FilterStringFunctions.DESCRIPTION )
.withStringProperty( "image-id", CloudMetadatas.toDisplayName() )
.withStringProperty( "image-type", FilterStringFunctions.IMAGE_TYPE )
.withBooleanProperty( "is-public", FilterBooleanFunctions.IS_PUBLIC )
.withStringProperty( "kernel-id", asImageInfoFunction( BootableImageInfoFilterStringFunctions.KERNEL_ID ) )
.withStringProperty( "manifest-location", FilterStringFunctions.MANIFEST_LOCATION )
.withStringProperty( "name", FilterStringFunctions.NAME )
.withLikeExplodedProperty( "owner-alias", FilterStringFunctions.OWNER_ID, accountAliasExploder() )
.withStringProperty( "owner-id", FilterStringFunctions.OWNER_ID )
.withStringProperty( "platform", FilterStringFunctions.PLATFORM )
.withStringSetProperty( "product-code", FilterStringSetFunctions.PRODUCT_CODE )
.withUnsupportedProperty( "product-code.type" )
.withStringProperty( "ramdisk-id", asImageInfoFunction( BootableImageInfoFilterStringFunctions.RAMDISK_ID ) )
.withStringProperty( "root-device-name", asImageInfoFunction( BootableImageInfoFilterStringFunctions.ROOT_DEVICE_NAME ) )
.withStringProperty( "root-device-type", asImageInfoFunction( BootableImageInfoFilterStringFunctions.ROOT_DEVICE_TYPE ) )
.withStringProperty( "state", FilterStringFunctions.STATE )
.withUnsupportedProperty( "state-reason-code" )
.withUnsupportedProperty( "state-reason-message" )
.withStringProperty( "virtualization-type", FilterStringFunctions.VIRTUALIZATION_TYPE )
.withUnsupportedProperty( "hypervisor" )
.withPersistenceAlias( "deviceMappings", "deviceMappings" )
.withPersistenceFilter( "architecture", "architecture", FUtils.valueOfFunction( ImageMetadata.Architecture.class ) )
.withPersistenceFilter( "block-device-mapping.delete-on-termination", "deviceMappings.delete", PersistenceFilter.Type.Boolean )
.withPersistenceFilter( "block-device-mapping.device-name", "deviceMappings.deviceName" )
.withPersistenceFilter( "block-device-mapping.snapshot-id", "deviceMappings.snapshotId" )
.withPersistenceFilter( "block-device-mapping.volume-size", "deviceMappings.size", PersistenceFilter.Type.Integer )
.withPersistenceFilter( "description" )
.withPersistenceFilter( "image-id", "displayName" )
.withPersistenceFilter( "image-type", "imageType", FUtils.valueOfFunction( ImageMetadata.Type.class ) )
.withPersistenceFilter( "is-public", "imagePublic", PersistenceFilter.Type.Boolean )
.withPersistenceFilter( "kernel-id", "kernelId" )
.withPersistenceFilter( "manifest-location", "manifestLocation" )
.withPersistenceFilter( "name", "imageName" )
.withLikeExplodingPersistenceFilter( "owner-alias", "ownerAccountNumber", accountAliasExploder() )
.withPersistenceFilter( "owner-id", "ownerAccountNumber" )
.withPersistenceFilter( "platform", "platform", FUtils.valueOfFunction( ImageMetadata.Platform.class ) )
.withPersistenceFilter( "ramdisk-id", "ramdiskId" )
.withPersistenceFilter( "state", "state", FUtils.valueOfFunction( ImageMetadata.State.class ) )
.withPersistenceFilter( "virtualization-type", "virtType", ImageMetadata.VirtualizationType.fromString( ) )
);
}
}
private static Function<String,Collection> accountAliasExploder() {
return new Function<String,Collection>() {
@Override
public Collection<String> apply( final String accountAliasExpression ) {
try {
return Accounts.listAccountNumbersForName( accountAliasExpression );
} catch ( AuthException e ) {
LOG.error( e, e );
return Collections.emptySet();
}
}
};
}
private static <T> Function<ImageInfo,T> asImageInfoFunction( final Function<BootableImageInfo,T> bootableImageInfoFunction ) {
return Images.typedFunction( bootableImageInfoFunction, BootableImageInfo.class, null );
}
private static <R, T, TT> Function<T, R> typedFunction( final Function<TT,R> typeSpecificFunction,
final Class<TT> subClass,
@Nullable final R defaultValue ) {
return new Function<T,R>() {
@Override
public R apply( final T parameter ) {
return subClass.isInstance( parameter ) ?
typeSpecificFunction.apply( subClass.cast( parameter ) ) :
defaultValue;
}
};
}
private static <T> Set<T> blockDeviceSet( final ImageInfo imageInfo,
final Function<DeviceMapping,T> transform ) {
return Sets.newHashSet( Iterables.transform(
imageInfo.getDeviceMappings(),
transform ) );
}
private enum BlockDeviceMappingBooleanFilterFunctions implements Function<BlockStorageDeviceMapping,Boolean> {
DELETE_ON_TERMINATION {
@Override
public Boolean apply( final BlockStorageDeviceMapping deviceMapping ) {
return deviceMapping.getDelete();
}
}
}
private enum BlockDeviceMappingIntegerFilterFunctions implements Function<BlockStorageDeviceMapping,Integer> {
SIZE {
@Override
public Integer apply( final BlockStorageDeviceMapping deviceMapping ) {
return deviceMapping.getSize();
}
}
}
private enum BlockDeviceMappingFilterFunctions implements Function<BlockStorageDeviceMapping, String> {
SNAPSHOT_ID {
@Override
public String apply( final BlockStorageDeviceMapping deviceMapping ) {
return deviceMapping.getSnapshotId();
}
}
}
private enum DeviceMappingFilterFunctions implements Function<DeviceMapping, String> {
DEVICE_NAME {
@Override
public String apply( final DeviceMapping deviceMapping ) {
return deviceMapping.getDeviceName();
}
}
}
private enum FilterBooleanFunctions implements Function<ImageInfo,Boolean> {
IS_PUBLIC {
@Override
public Boolean apply( final ImageInfo imageInfo ) {
return imageInfo.getImagePublic();
}
}
}
private enum FilterBooleanSetFunctions implements Function<ImageInfo,Set<Boolean>> {
BLOCK_DEVICE_MAPPING_DELETE_ON_TERMINATION {
@Override
public Set<Boolean> apply( final ImageInfo imageInfo ) {
return blockDeviceSet( imageInfo,
Images.<Boolean, DeviceMapping, BlockStorageDeviceMapping>typedFunction( BlockDeviceMappingBooleanFilterFunctions.DELETE_ON_TERMINATION, BlockStorageDeviceMapping.class, null ) );
}
}
}
private enum FilterIntegerSetFunctions implements Function<ImageInfo,Set<Integer>> {
BLOCK_DEVICE_MAPPING_VOLUME_SIZE {
@Override
public Set<Integer> apply( final ImageInfo imageInfo ) {
return blockDeviceSet( imageInfo,
Images.<Integer, DeviceMapping, BlockStorageDeviceMapping>typedFunction( BlockDeviceMappingIntegerFilterFunctions.SIZE, BlockStorageDeviceMapping.class, null ) );
}
}
}
private enum BootableImageInfoFilterStringFunctions implements Function<BootableImageInfo,String> {
KERNEL_ID {
@Override
public String apply( final BootableImageInfo imageInfo ) {
return imageInfo.getKernelId();
}
},
RAMDISK_ID {
@Override
public String apply( final BootableImageInfo imageInfo ) {
return imageInfo.getRamdiskId();
}
},
ROOT_DEVICE_NAME {
@Override
public String apply( final BootableImageInfo imageInfo ) {
return imageInfo.getRootDeviceName();
}
},
ROOT_DEVICE_TYPE {
@Override
public String apply( final BootableImageInfo imageInfo ) {
return imageInfo.getRootDeviceType();
}
},
}
private enum FilterStringFunctions implements Function<ImageInfo,String> {
ARCHITECTURE {
@Override
public String apply( final ImageInfo imageInfo ) {
return Strings.toString( imageInfo.getArchitecture() );
}
},
DESCRIPTION {
@Override
public String apply( final ImageInfo imageInfo ) {
return imageInfo.getDescription();
}
},
IMAGE_TYPE {
@Override
public String apply( final ImageInfo imageInfo ) {
return Strings.toString( imageInfo.getImageType() );
}
},
MANIFEST_LOCATION {
@Override
public String apply( final ImageInfo imageInfo ) {
return imageInfo instanceof PutGetImageInfo ?
((PutGetImageInfo) imageInfo).getManifestLocation() :
null;
}
},
NAME {
@Override
public String apply( final ImageInfo imageInfo ) {
return imageInfo.getImageName();
}
},
OWNER_ID {
@Override
public String apply( final ImageInfo imageInfo ) {
return imageInfo.getOwnerAccountNumber();
}
},
PLATFORM {
@Override
public String apply( final ImageInfo imageInfo ) {
return Strings.toString( imageInfo.getPlatform() );
}
},
STATE {
@Override
public String apply( final ImageInfo imageInfo ) {
return Strings.toString( imageInfo.getState() );
}
},
VIRTUALIZATION_TYPE {
@Override
public String apply( final ImageInfo imageInfo ) {
return imageInfo instanceof MachineImageInfo ?
Strings.toString( ((MachineImageInfo)imageInfo).getVirtualizationType() ) :
null;
}
}
}
private enum FilterStringSetFunctions implements Function<ImageInfo,Set<String>> {
BLOCK_DEVICE_MAPPING_DEVICE_NAME {
@Override
public Set<String> apply( final ImageInfo imageInfo ) {
return blockDeviceSet( imageInfo, DeviceMappingFilterFunctions.DEVICE_NAME );
}
},
BLOCK_DEVICE_MAPPING_SNAPSHOT_ID {
@Override
public Set<String> apply( final ImageInfo imageInfo ) {
return blockDeviceSet( imageInfo,
Images.<String,DeviceMapping,BlockStorageDeviceMapping>typedFunction(BlockDeviceMappingFilterFunctions.SNAPSHOT_ID, BlockStorageDeviceMapping.class, null ) );
}
},
PRODUCT_CODE {
@Override
public Set<String> apply( final ImageInfo imageInfo ) {
return imageInfo.getProductCodes();
}
}
}
public static void cleanDeregistered( ) {
List<String> imageIdentifiers;
try {
imageIdentifiers = Transactions.filteredTransform(
Images.exampleWithImageState( ImageMetadata.State.deregistered ),
Predicates.alwaysTrue(),
RestrictedTypes.toDisplayName() );
} catch ( TransactionException e ) {
LOG.error( "Error loading deregistered image list", e );
imageIdentifiers = Collections.emptyList( );
}
for ( final String imageIdentifier : imageIdentifiers ) try {
Transactions.delete( Images.exampleWithImageId( imageIdentifier ) );
} catch ( RuntimeException | TransactionException e ) {
Logs.extreme().debug( "Attempted image delete failed (image still referenced?): " + imageIdentifier, e );
}
}
private static boolean getImagePublicVisibilityDefault( ) {
return Objects.firstNonNull( ImageConfiguration.getInstance( ).getDefaultVisibility( ), Boolean.FALSE );
}
public static class ImageCleanupEventListener implements EventListener<Hertz> {
private static final Supplier<Long> periodSupplier =
Suppliers.memoizeWithExpiration( ConfigurationValueSupplier.INSTANCE, 10, TimeUnit.SECONDS );
public static void register( ) {
Listeners.register( Hertz.class, new ImageCleanupEventListener( ) );
}
@Override
public void fireEvent( final Hertz hertz ) {
if ( Bootstrap.isOperational( ) &&
!Databases.isVolatile( ) &&
periodSupplier.get( ) > 0 &&
hertz.isAsserted( TimeUnit.MILLISECONDS.toSeconds( periodSupplier.get( ) ) ) &&
Topology.isEnabledLocally( Eucalyptus.class ) ) {
cleanDeregistered( );
}
}
private enum ConfigurationValueSupplier implements Supplier<Long> {
INSTANCE;
@Override
public Long get() {
return ImageConfiguration.getInstance( ).getCleanupPeriodMillis( );
}
}
}
}