/************************************************************************* * 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( ); } } } }