/*************************************************************************
 * Copyright 2009-2014 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 java.util.NoSuchElementException;

import javax.annotation.Nonnull;
import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceException;

import com.eucalyptus.compute.common.internal.images.BlockStorageImageInfo;
import com.eucalyptus.compute.common.internal.images.BootableImageInfo;
import com.eucalyptus.compute.common.internal.images.ImageInfo;
import com.eucalyptus.compute.common.internal.images.KernelImageInfo;
import com.eucalyptus.compute.common.internal.images.MachineImageInfo;
import com.eucalyptus.compute.common.internal.images.RamdiskImageInfo;
import com.eucalyptus.compute.common.internal.util.NoSuchImageIdException;

import org.apache.log4j.Logger;

import com.eucalyptus.compute.common.CloudMetadatas;
import com.eucalyptus.compute.common.ImageMetadata;
import com.eucalyptus.compute.common.ImageMetadata.Platform;
import com.eucalyptus.compute.common.RunInstancesType;
import com.eucalyptus.compute.common.StaticDiskImage;
import com.eucalyptus.compute.common.internal.util.IllegalMetadataAccessException;
import com.eucalyptus.compute.common.internal.util.InvalidMetadataException;
import com.eucalyptus.compute.common.internal.util.MetadataException;
import com.eucalyptus.compute.common.internal.util.NoSuchMetadataException;
import com.eucalyptus.component.Partition;
import com.eucalyptus.context.Context;
import com.eucalyptus.context.Contexts;
import com.eucalyptus.context.IllegalContextAccessException;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.imaging.manifest.BundleImageManifest;
import com.eucalyptus.imaging.manifest.DownloadManifestException;
import com.eucalyptus.imaging.manifest.DownloadManifestFactory;
import com.eucalyptus.imaging.manifest.ImageManifestFile;
import com.eucalyptus.records.Logs;
import com.eucalyptus.util.EucalyptusCloudException;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.util.NonNullFunction;
import com.eucalyptus.util.RestrictedTypes;
import com.eucalyptus.util.RestrictedTypes.Resolver;
import com.eucalyptus.compute.common.internal.vm.VmInstance;
import com.eucalyptus.compute.common.internal.vmtypes.VmType;
import com.eucalyptus.vmtypes.VmTypes;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;

import edu.ucsb.eucalyptus.msgs.VmTypeInfo;

public class Emis {
  private static Logger LOG = Logger.getLogger( Emis.class );
  
  enum VBRTypes {
    MACHINE( "objectstorage://" ),
    EBS,
    KERNEL( "objectstorage://" ),
    RAMDISK( "objectstorage://" ),
    EPHEMERAL,
    SWAP;
    String prefix;
    
    private VBRTypes( ) {
      this( "" );
    }
    
    private VBRTypes( final String prefix ) {
      this.prefix = prefix;
    }
    
  }
  
  @Resolver( ImageMetadata.class )
  public enum LookupImage implements Function<String, ImageInfo> {
    INSTANCE;
    
    @Override
    public ImageInfo apply( final String input ) {
      if ( CloudMetadatas.isKernelImageIdentifier( input ) ) {
        return LookupKernel.INSTANCE.apply( input );
      } else if ( CloudMetadatas.isRamdiskImageIdentifier( input ) ) {
        return LookupRamdisk.INSTANCE.apply( input );
      } else if ( CloudMetadatas.isMachineImageIdentifier( input ) ) {
        try {
          return LookupMachine.INSTANCE.apply( input );
        } catch ( final Exception ex ) {
          return LookupAvailableBlockStorage.INSTANCE.apply( input );
        }
      } else {
        throw new NoSuchElementException( "Failed to lookup image: " + input );
      }
    }
  }
  
  public enum LookupBlockStorage implements Function<String, BlockStorageImageInfo> {
    INSTANCE;
    @Override
    public BlockStorageImageInfo apply( final String identifier ) {
      final EntityTransaction db = Entities.get( BlockStorageImageInfo.class );
      try {
        final BlockStorageImageInfo ret = Entities.uniqueResult( Images.exampleBlockStorageWithImageId( identifier ) );
        if ( Platform.windows.name( ).equals( ret.getKernelId( ) ) || ret.getImageName( ).startsWith( Platform.windows.name( ) ) ) {
          ret.setPlatform( Platform.windows );
        }
        db.rollback( );
        return ret;
      } catch ( final Exception ex ) {
        Logs.exhaust( ).error( ex, ex );
        db.rollback( );
        throw new NoSuchElementException( "Failed to lookup image: " + identifier + " because of " + ex.getMessage( ) );
      }
    }
  }
  
  public enum LookupAvailableBlockStorage implements Function<String, BlockStorageImageInfo> {
    INSTANCE;
    @Override
    public BlockStorageImageInfo apply( final String identifier ) {
      final EntityTransaction db = Entities.get( BlockStorageImageInfo.class );
      try {
        final BlockStorageImageInfo ret = Entities.uniqueResult( Images.exampleBlockStorageWithImageId( identifier ) );
        if ( Platform.windows.name( ).equals( ret.getKernelId( ) ) || ret.getImageName( ).startsWith( Platform.windows.name( ) ) ) {
          ret.setPlatform( Platform.windows );
        }
        if ( !ImageMetadata.State.available.equals( ret.getState( )) &&
        		!ImageMetadata.State.pending.equals( ret.getState( )) ) {
          db.rollback( );
          throw new NoSuchElementException( "Unable to start instance with deregistered/failed image : " + ret );
        } else {
          db.rollback( );
          return ret;
        }
      } catch ( final Exception ex ) {
        Logs.exhaust( ).error( ex, ex );
        db.rollback( );
        throw new NoSuchElementException( "Failed to lookup image: " + identifier + " because of " + ex.getMessage( ) );
      }
    }
  }
  
  public enum LookupMachine implements NonNullFunction<String, MachineImageInfo> {
    INSTANCE;
    @Nonnull
    @Override
    public MachineImageInfo apply( final String identifier ) {
      final EntityTransaction db = Entities.get( MachineImageInfo.class );
      try {
        final MachineImageInfo ret = Entities.uniqueResult( Images.exampleMachineWithImageId( identifier ) );
        if ( !ImageMetadata.State.available.name().equals( ret.getState( ).getExternalStateName() ) ) {
          db.rollback( );
          throw new NoSuchElementException( "Unable to start instance with deregistered/failed image : " + ret );
        } else {
          db.rollback( );
          return ret;
        }
      } catch ( final Exception ex ) {
        Logs.exhaust( ).error( ex, ex );
        db.rollback( );
        throw new NoSuchElementException( "Failed to lookup image: " + identifier + " because of " + ex.getMessage( ) );
      }
    }
  }
  
  public enum LookupKernel implements NonNullFunction<String, KernelImageInfo> {
    INSTANCE;
    @Nonnull
    @Override
    public KernelImageInfo apply( final String identifier ) {
      final EntityTransaction db = Entities.get( KernelImageInfo.class );
      try {
        final KernelImageInfo ret = Entities.uniqueResult( Images.exampleKernelWithImageId( identifier ) );
        if ( !ImageMetadata.State.available.name().equals( ret.getState( ).getExternalStateName() ) ) {
          db.rollback( );
          throw new NoSuchElementException( "Unable to start instance with deregistered/failed image : " + ret );
        } else {
          db.rollback( );
          return ret;
        }
      } catch ( final Exception ex ) {
        Logs.exhaust( ).error( ex, ex );
        db.rollback( );
        throw new NoSuchElementException( "Failed to lookup image: " + identifier + " because of " + ex.getMessage( ) );
      }
    }
  }
  
  public enum LookupRamdisk implements NonNullFunction<String, RamdiskImageInfo> {
    INSTANCE;
    @Nonnull
    @Override
    public RamdiskImageInfo apply( final String identifier ) {
      final EntityTransaction db = Entities.get( RamdiskImageInfo.class );
      try {
        final RamdiskImageInfo ret = Entities.uniqueResult( Images.exampleRamdiskWithImageId( identifier ) );
        if ( !ImageMetadata.State.available.name().equals( ret.getState( ).getExternalStateName() ) ) {
          db.rollback( );
          throw new NoSuchElementException( "Unable to start instance with deregistered/failed image : " + ret );
        } else {
          db.rollback( );
          return ret;
        }
      } catch ( final Exception ex ) {
        Logs.exhaust( ).error( ex, ex );
        db.rollback( );
        throw new NoSuchElementException( "Failed to lookup image: " + identifier + " because of " + ex.getMessage( ) );
      }
    }
  }
  
  public static class BootableSet {
    private final BootableImageInfo disk;
    
    private BootableSet( final BootableImageInfo bootableImageInfo ) {
    	this.disk = bootableImageInfo;
    }
    
    public BootableImageInfo getMachine( ) {
      return this.disk;
    }
    
    public RamdiskImageInfo getRamdisk( ) {
      throw new NoSuchElementException( "BootableSet:machine=" + this.getMachine( ) + " does not have a kernel." );
    }
    
    public KernelImageInfo getKernel( ) {
      throw new NoSuchElementException( "BootableSet:machine=" + this.getMachine( ) + " does not have a kernel." );
    }
    
    public boolean hasKernel( ) {
      try {
        this.getKernel( );
        return true;
      } catch ( final NoSuchElementException ex ) {
        return false;
      }
    }
    
    public boolean hasRamdisk( ) {
      try {
        this.getRamdisk( );
        return true;
      } catch ( final NoSuchElementException ex ) {
        return false;
      }
    }
    
    public boolean isBlockStorage( ) {
      return this.getMachine( ) instanceof BlockStorageImageInfo;
    }
    
    public boolean isLinux( ) {
      return ImageMetadata.Platform.linux.equals( this.getMachine( ).getPlatform( ) ) || ( this.getMachine( ).getPlatform( ) == null );
    }
    
    public boolean isHvm(){
    	return ImageMetadata.VirtualizationType.hvm.equals(this.getMachine().getVirtualizationType());
    }
    
    @Override
    public String toString( ) {
      return String.format( "BootableSet:machine=%s:ramdisk=%s:kernel=%s:isLinux=%s", this.getMachine( ),
                            this.hasRamdisk( )
                                              ? this.getRamdisk( )
                                              : "false",
                              this.hasKernel( )
                                               ? this.getKernel( )
                                               : "false",
                                this.isLinux( ) );
    }
    
    public VmTypeInfo populateVirtualBootRecord( final VmType vmType, final Partition partition,
    		final String reservationId) throws MetadataException {
      final VmTypeInfo vmTypeInfo = VmTypes.asVmTypeInfo( vmType, this.getMachine( ) );
      try {
        if ( this.isLinux( ) ) {
          if ( this.hasKernel( ) ) {
            if ( !isEmptyString( this.getKernel().getManifestHash() )
                 && !this.getKernel().getManifestHash().equals(
               ImageManifests.getManifestHash( this.getKernel( ).getManifestLocation( ) )) )
              throw new MetadataException("Kernel manifest was changed after registration");
            String manifestLocation = DownloadManifestFactory.generateDownloadManifest(
                new ImageManifestFile(
                  this.getKernel( ).getManifestLocation( ),
                  BundleImageManifest.INSTANCE,
                  ImageConfiguration.getInstance( ).getMaxManifestSizeBytes( ) ),
                partition.getNodeCertificate().getPublicKey(),
                this.getKernel( ).getDisplayName( ) + "-" + reservationId, true);
            vmTypeInfo.setKernel( this.getKernel( ).getDisplayName( ), manifestLocation,
                                  this.getKernel( ).getImageSizeBytes() );
          }
          if ( this.hasRamdisk( ) ) {
            if ( !isEmptyString( this.getRamdisk().getManifestHash() )
                 && !this.getRamdisk().getManifestHash().equals(
                ImageManifests.getManifestHash( this.getRamdisk().getManifestLocation( ) )) )
              throw new MetadataException("Ramdisk manifest was changed after registration");
            String manifestLocation = DownloadManifestFactory.generateDownloadManifest(
                new ImageManifestFile( 
                  this.getRamdisk( ).getManifestLocation( ),
                  BundleImageManifest.INSTANCE,
                  ImageConfiguration.getInstance( ).getMaxManifestSizeBytes( )),
                partition.getNodeCertificate().getPublicKey(),
                this.getRamdisk( ).getDisplayName( ) + "-" + reservationId, true);
            vmTypeInfo.setRamdisk( this.getRamdisk( ).getDisplayName( ), manifestLocation,
                                   this.getRamdisk( ).getImageSizeBytes() );
          }
        }
        if ( this.getMachine( ) instanceof StaticDiskImage ) { // BootableImage+StaticDiskImage = MachineImageInfo
          StaticDiskImage diskImage = (StaticDiskImage) this.getMachine( );
          if (!isEmptyString( diskImage.getManifestHash() )
              && !diskImage.getManifestHash().equals( ImageManifests.getManifestHash(
                                                      diskImage.getManifestLocation() )) )
            throw new MetadataException("Instance manifest was changed after registration");
          String manifestLocation = DownloadManifestFactory.generateDownloadManifest(
                new ImageManifestFile(
                  diskImage.getManifestLocation( ),
                  BundleImageManifest.INSTANCE,
                  ImageConfiguration.getInstance( ).getMaxManifestSizeBytes( ) ),
                  partition.getNodeCertificate( ).getPublicKey( ),
                  reservationId, true);
          vmTypeInfo.setRoot( diskImage.getDisplayName( ), manifestLocation,
                              this.getMachine( ).getImageSizeBytes() );
        }
      } catch (DownloadManifestException ex) {
        throw new MetadataException(ex);
      } catch (Exception ex){
        throw new MetadataException(ex);
      }
      return vmTypeInfo;
    }
  }
  
  private static boolean isEmptyString(String string) {
    return (string == null || "".equals(string));
  }

  static class NoRamdiskBootableSet extends BootableSet {
    private final KernelImageInfo kernel;
    
    private NoRamdiskBootableSet( final BootableImageInfo bootableImageInfo, final KernelImageInfo kernel ) {
      super( bootableImageInfo );
      this.kernel = kernel;
    }
    
    @Override
    public KernelImageInfo getKernel( ) {
      return this.kernel;
    }
  }
  
  static class TrifectaBootableSet extends NoRamdiskBootableSet {
    private final RamdiskImageInfo ramdisk;
    
    public TrifectaBootableSet( final BootableImageInfo bootableImageInfo, final KernelImageInfo kernel, final RamdiskImageInfo ramdisk ) {
      super( bootableImageInfo, kernel );
      this.ramdisk = ramdisk;
    }
    
    @Override
    public RamdiskImageInfo getRamdisk( ) {
      return this.ramdisk;
    }
  }
  
  public static BootableSet recreateBootableSet( final VmInstance vm ) {
    final BootableImageInfo bootableImageInfo = vm.getBootRecord( ).getMachine( );
    final KernelImageInfo kernel = vm.getBootRecord( ).getKernel( );
    final RamdiskImageInfo ramdisk = vm.getBootRecord( ).getRamdisk( );
    if ( ( kernel != null ) && ( ramdisk != null ) ) {
      return new TrifectaBootableSet( bootableImageInfo, kernel, ramdisk );
    } else if ( kernel != null ) {
      return new NoRamdiskBootableSet( bootableImageInfo, kernel );
    } else {
      return new BootableSet( bootableImageInfo );
    }
  }
  
  public static BootableSet recreateBootableSet( final String imageId, final String kernelId, final String ramdiskId ) throws MetadataException {
    try {
      final BootsetBuilder builder = new BootsetBuilder( ).imageId( imageId );
      if ( kernelId != null ) {
        builder.kernelId( kernelId );
        if ( ramdiskId != null ) {
          builder.ramdiskId( ramdiskId );
        }
      }
      return builder.start( );
    } catch ( final MetadataException ex ) {
      throw ex;
    } catch ( final Exception ex ) {
      throw new InvalidMetadataException( "Failed to construct bootset for image id: " + imageId + " because of: " + ex.getMessage( ), ex );
    }
  }

  public static BootableSet unavailableBootableSet( final Platform platform ) {
    return new BootableSet( new UnavailableImageInfo( platform ) );
  }
  
  public static BootableSet newBootableSet( final String imageId ) throws MetadataException {
    try {
      return new BootsetBuilder( ).imageId( imageId ).run( );
    } catch ( final MetadataException ex ) {
      throw ex;
    } catch ( final Exception ex ) {
      throw new InvalidMetadataException( "Failed to construct bootset for image id: " + imageId + " because of: " + ex.getMessage( ), ex );
    }
  }
  
  private static class BootsetBuilder {
    private String imageId;
    private String kernelId;
    private String ramdiskId;
    
    public BootsetBuilder imageId( final String imageId ) {
      this.imageId = imageId;
      return this;
    }
    
    public BootsetBuilder ramdiskId( final String ramdiskId ) {
      this.ramdiskId = ramdiskId;
      return this;
    }
    
    public BootsetBuilder kernelId( final String kernelId ) {
      this.kernelId = kernelId;
      return this;
    }
    
    public BootableSet run( ) throws MetadataException {
      final Function<String, BootableSet> create = Functions.compose( BootsetWithRamdisk.INSTANCE,
                                                                Functions.compose( BootsetWithKernel.INSTANCE, BootsetFromId.INSTANCE ) );
      return this.prepareBootset( create );
    }
    
    private BootableSet prepareBootset( final Function<String, BootableSet> create ) throws MetadataException {
      try {
        final BootableSet bootSet = create.apply( this.imageId );
        return bootSet;
      } catch ( final RuntimeException ex ) {
        if ( ex.getCause( ) instanceof MetadataException ) {
          throw ( MetadataException ) ex.getCause( );
        } else {
          throw ex;
        }
      }
    }
    
    public BootableSet start( ) throws MetadataException {
      final Function<String, BootableSet> create = new Function<String, BootableSet>( ) {
        @Override
        public BootableSet apply( final String input ) {
          final BootableSet b = BootsetFromId.INSTANCE.apply( BootsetBuilder.this.imageId );
          try {
            final KernelImageInfo kernel = ( BootsetBuilder.this.kernelId == null
                                                                           ? null
                                                                           : LookupKernel.INSTANCE.apply( BootsetBuilder.this.kernelId ) );
            final RamdiskImageInfo ramdisk = ( BootsetBuilder.this.ramdiskId == null
                                                                              ? null
                                                                              : LookupRamdisk.INSTANCE.apply( BootsetBuilder.this.ramdiskId ) );
            if ( ( kernel != null ) && ( ramdisk != null ) ) {
              return new TrifectaBootableSet( b.getMachine( ), kernel, ramdisk );
            } else if ( kernel != null ) {
              return new NoRamdiskBootableSet( b.getMachine( ), kernel );
            } else {
              return b;
            }
          } catch ( NoSuchElementException e ) {
            throw Exceptions.toUndeclared( new NoSuchMetadataException( e.getMessage(), e ) );
          }
        }
      };
      return this.prepareBootset( create );
    }
  }
  
  private static <T extends ImageInfo> T resolveDiskImage( final String imageId, final Function<String, T> resolver ) throws IllegalMetadataAccessException {
    final T img = resolver.apply( imageId );
    if ( Contexts.exists( ) ) {
      final Predicate<T> filter = Predicates.and( Images.FilterPermissions.INSTANCE, RestrictedTypes.filterPrivilegedWithoutOwner( ) );
      if ( filter.apply( img ) ) {
    	  if( ! Images.FilterImageStates.INSTANCE.apply( img )) 
    		  throw Exceptions.toUndeclared(new EucalyptusCloudException("Image state is not available"));
    	  return img;
      } else {
        throw new IllegalMetadataAccessException( imageId + ": permission denied." );
      }
    } else {
      return img;
    }
  }
  
  enum BootsetFromId implements Function<String, BootableSet> {
    INSTANCE;
    
    @Override
    public BootableSet apply( final String input ) {
      BootableSet bootSet;
      try {
        bootSet = new BootableSet( resolveDiskImage( input, LookupMachine.INSTANCE ) );
      } catch ( final IllegalContextAccessException ex ) {
        throw Exceptions.toUndeclared( new IllegalMetadataAccessException( ex ) );
      } catch ( final IllegalMetadataAccessException ex ) {
        throw Exceptions.toUndeclared( ex );
      } catch ( final Exception e ) {
        try {
          bootSet = new BootableSet( resolveDiskImage( input, LookupAvailableBlockStorage.INSTANCE ) );
        } catch ( final IllegalContextAccessException ex ) {
          throw Exceptions.toUndeclared( new IllegalMetadataAccessException( ex ) );
        } catch ( final IllegalMetadataAccessException ex ) {
          throw Exceptions.toUndeclared( ex );
        } catch ( final NoSuchElementException ex ) {
          throw Exceptions.toUndeclared( new NoSuchImageIdException( "Failed to lookup image named: " + input, ex ) );
        } catch ( final PersistenceException ex ) {
          throw Exceptions.toUndeclared( new InvalidMetadataException( "Error occurred while trying to lookup image named: " + input, ex ) );
        }
      }
      return bootSet;
    }
    
  }
  
  enum BootsetWithRamdisk implements Function<BootableSet, BootableSet> {
    INSTANCE;
    
    @Override
    public BootableSet apply( final BootableSet input ) {
      if ( !input.isLinux( ) || input.isHvm()) {
        return input;
      } else {
        String ramdiskId = null;
        try {
          ramdiskId = determineRamdiskId( input );
          LOG.debug( "Determined the appropriate ramdiskId to be " + ramdiskId + " for " + input.toString( ) );
          if ( ramdiskId == null ) {
            return input;
          } else {
            final RamdiskImageInfo ramdisk = RestrictedTypes.doPrivilegedWithoutOwner( ramdiskId, LookupRamdisk.INSTANCE );
            return new TrifectaBootableSet( input.getMachine( ), input.getKernel( ), ramdisk );
          }
        } catch ( final InvalidMetadataException ex ) {
          return input;
        } catch ( final Exception ex ) {
          if ( input.isBlockStorage( ) ) {
            return input;
          } else {
            throw Exceptions.toUndeclared( new NoSuchMetadataException( "Failed to lookup ramdisk image information: " + ramdiskId
                                                                        + " because of: "
                                                                        + ex.getMessage( ), ex ) );
          }
        }
      }
    }
  }
  
  enum BootsetWithKernel implements Function<BootableSet, BootableSet> {
    INSTANCE;
    
    @Override
    public BootableSet apply( final BootableSet input ) {
      if ( !input.isLinux( ) || input.isHvm() ) {
        return input;
      } else {
        String kernelId = "unknown";
        try {
          kernelId = determineKernelId( input );
          LOG.debug( "Determined the appropriate kernelId to be " + kernelId + " for " + input.toString( ) );
          final KernelImageInfo kernel = RestrictedTypes.doPrivilegedWithoutOwner( kernelId, LookupKernel.INSTANCE );
          return new NoRamdiskBootableSet( input.getMachine( ), kernel );
        } catch ( final Exception ex ) {
          if ( input.isBlockStorage( ) ) {
            return input;
          } else if (input.isHvm()) {
        	return input;  
          } else {
            throw Exceptions.toUndeclared( new NoSuchMetadataException( "Failed to lookup kernel image information " + kernelId
                                                                        + " because of: "
                                                                        + ex.getMessage( ), ex ) );
          }
        }
      }
    }
    
  }
  
  private static String determineKernelId( final BootableSet bootSet ) throws MetadataException {
    final BootableImageInfo disk = bootSet.getMachine( );
    String kernelId = null;
    Context ctx = null;
    try {
      ctx = Contexts.lookup( );
      if ( ctx.getRequest( ) instanceof RunInstancesType ) {
        kernelId = ( ( RunInstancesType ) ctx.getRequest( ) ).getKernelId( );
      }
    } catch ( final IllegalContextAccessException ex ) {
      LOG.debug( "Context not found when determining kernel id:" + ex.getMessage( ) );
    }
    if ( ( kernelId == null ) || "".equals( kernelId ) ) {
      kernelId = disk.getKernelId( );
    }
   
    Preconditions.checkNotNull( kernelId, "Attempt to resolve a kerneId for "
                                          + bootSet.toString( )
                                          + " during request "
                                          + ( ctx != null
                                                         ? ctx.getRequest( ).toSimpleString( )
                                                         : "UNKNOWN" ) );
    if ( kernelId == null ) {
      throw new NoSuchMetadataException( "Unable to determine required kernel image for " + disk.getDisplayName( ) );
    } else if ( !CloudMetadatas.isKernelImageIdentifier( kernelId ) ) {
      throw new InvalidMetadataException( "Image specified is not a kernel: " + kernelId );
    }
    return kernelId;
  }
  
  private static String determineRamdiskId( final BootableSet bootSet ) throws MetadataException {
    if ( !bootSet.hasKernel( ) ) {
      throw new InvalidMetadataException( "Image specified does not have a kernel: " + bootSet );
    }
    String ramdiskId = bootSet.getMachine( ).getRamdiskId( );//GRZE: use the ramdisk that is part of the registered image definition to start.
    try {
      final Context ctx = Contexts.lookup( );
      if ( ctx.getRequest( ) instanceof RunInstancesType ) {
        final RunInstancesType msg = ( RunInstancesType ) ctx.getRequest( );
        if ( ( msg.getRamdiskId( ) != null ) && !"".equals( msg.getRamdiskId( ) ) ) {
          ramdiskId = msg.getRamdiskId( );//GRZE: maybe update w/ a specific ramdisk user requests
        }
      }
    } catch ( final IllegalContextAccessException ex ) {
      LOG.debug( "Context not found when determining ramdisk id:" + ex.getMessage( ) );
    }
    //GRZE: perfectly legitimate for there to be no ramdisk, carry on. **/
    if ( ramdiskId == null ) {
      return ramdiskId;
    } else if ( !CloudMetadatas.isRamdiskImageIdentifier( ramdiskId ) ) {
      throw new InvalidMetadataException( "Image specified is not a ramdisk: " + ramdiskId );
    } else {
      return ramdiskId;
    }
  }
}
