Java tutorial
/************************************************************************* * 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 static com.eucalyptus.images.Images.DeviceMappingValidationOption.AllowDevSda1; import static com.eucalyptus.images.Images.DeviceMappingValidationOption.AllowEbsMapping; import static com.eucalyptus.images.Images.DeviceMappingValidationOption.AllowSuppressMapping; import static com.eucalyptus.util.Parameters.checkParam; import static org.hamcrest.Matchers.notNullValue; import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import javax.annotation.Nullable; import javax.persistence.EntityTransaction; import javax.persistence.PersistenceException; import com.eucalyptus.cloud.util.MetadataException; import com.eucalyptus.compute.ClientComputeException; 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.compute.common.CloudMetadatas; import com.eucalyptus.compute.common.ImageMetadata; import com.eucalyptus.cluster.Cluster; import com.eucalyptus.cluster.Clusters; import com.eucalyptus.component.ServiceConfiguration; import com.eucalyptus.component.Topology; import com.eucalyptus.component.id.ClusterController; import com.eucalyptus.compute.ComputeException; import com.eucalyptus.compute.identifier.InvalidResourceIdentifier; import com.eucalyptus.compute.identifier.ResourceIdentifiers; import com.eucalyptus.context.Context; import com.eucalyptus.context.Contexts; import com.eucalyptus.context.IllegalContextAccessException; import com.eucalyptus.entities.Entities; import com.eucalyptus.entities.TransactionException; import com.eucalyptus.entities.Transactions; import com.eucalyptus.images.ImageManifests.ImageManifest; import com.eucalyptus.records.Logs; import com.eucalyptus.tags.Filter; import com.eucalyptus.tags.Filters; import com.eucalyptus.tags.Tag; import com.eucalyptus.tags.TagSupport; import com.eucalyptus.tags.Tags; import com.eucalyptus.util.EucalyptusCloudException; import com.eucalyptus.util.Exceptions; import com.eucalyptus.util.RestrictedTypes; import com.eucalyptus.vm.CreateImageTask; import com.eucalyptus.vm.VmInstance; import com.eucalyptus.vm.VmInstance.VmState; import com.eucalyptus.vm.VmInstances; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Strings; import com.google.common.base.Supplier; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import edu.ucsb.eucalyptus.msgs.BlockDeviceMappingItemType; import edu.ucsb.eucalyptus.msgs.ConfirmProductInstanceResponseType; import edu.ucsb.eucalyptus.msgs.ConfirmProductInstanceType; import edu.ucsb.eucalyptus.msgs.CreateImageResponseType; import edu.ucsb.eucalyptus.msgs.CreateImageType; import edu.ucsb.eucalyptus.msgs.DeregisterImageResponseType; import edu.ucsb.eucalyptus.msgs.DeregisterImageType; import edu.ucsb.eucalyptus.msgs.DescribeImageAttributeResponseType; import edu.ucsb.eucalyptus.msgs.DescribeImageAttributeType; import edu.ucsb.eucalyptus.msgs.DescribeImagesResponseType; import edu.ucsb.eucalyptus.msgs.DescribeImagesType; import edu.ucsb.eucalyptus.msgs.EbsDeviceMapping; import edu.ucsb.eucalyptus.msgs.ImageDetails; import edu.ucsb.eucalyptus.msgs.LaunchPermissionItemType; import edu.ucsb.eucalyptus.msgs.ModifyImageAttributeResponseType; import edu.ucsb.eucalyptus.msgs.ModifyImageAttributeType; import edu.ucsb.eucalyptus.msgs.RegisterImageResponseType; import edu.ucsb.eucalyptus.msgs.RegisterImageType; import edu.ucsb.eucalyptus.msgs.ResetImageAttributeResponseType; import edu.ucsb.eucalyptus.msgs.ResetImageAttributeType; import edu.ucsb.eucalyptus.msgs.ResourceTag; public class ImageManager { public static Logger LOG = Logger.getLogger(ImageManager.class); private static final long GB = 1024 * 1024 * 1024; //bytes-per-gb public DescribeImagesResponseType describe(final DescribeImagesType request) throws EucalyptusCloudException, TransactionException { DescribeImagesResponseType reply = request.getReply(); final Context ctx = Contexts.lookup(); final boolean showAllStates = ctx.isAdministrator() && request.getImagesSet().remove("verbose"); final String requestAccountId = ctx.getUserFullName().getAccountNumber(); final List<String> imageIds = normalizeImageIdentifiers(request.getImagesSet()); final List<String> ownersSet = request.getOwnersSet(); if (ownersSet.remove(Images.SELF)) { ownersSet.add(requestAccountId); } final Filter filter = Filters.generate(request.getFilterSet(), ImageInfo.class); final Predicate<? super ImageInfo> requestedAndAccessible = CloudMetadatas.filteringFor(ImageInfo.class) .byId(imageIds).byOwningAccount(request.getOwnersSet()) .byPredicate(showAllStates ? Predicates.<ImageInfo>alwaysTrue() : Images.standardStatePredicate()) .byPredicate(Images.filterExecutableBy(request.getExecutableBySet())) .byPredicate(filter.asPredicate()).byPredicate(Images.FilterPermissions.INSTANCE) .byPrivilegesWithoutOwner().buildPredicate(); final List<ImageDetails> imageDetailsList = Transactions.filteredTransform(new ImageInfo(), filter.asCriterion(), filter.getAliases(), requestedAndAccessible, Images.TO_IMAGE_DETAILS); final Map<String, List<Tag>> tagsMap = TagSupport.forResourceClass(ImageInfo.class).getResourceTagMap( AccountFullName.getInstance(ctx.getAccount()), Iterables.transform(imageDetailsList, ImageDetailsToImageId.INSTANCE)); for (final ImageDetails details : imageDetailsList) { Tags.addFromTags(details.getTagSet(), ResourceTag.class, tagsMap.get(details.getImageId())); } reply.getImagesSet().addAll(imageDetailsList); return reply; } public RegisterImageResponseType register(final RegisterImageType request) throws EucalyptusCloudException, AuthException, IllegalContextAccessException, NoSuchElementException, PersistenceException { final Context ctx = Contexts.lookup(); ImageInfo imageInfo = null; final String rootDevName = (request.getRootDeviceName() != null) ? request.getRootDeviceName() : Images.DEFAULT_ROOT_DEVICE; final String eki = ImageMetadata.Platform.windows.name().equals(request.getKernelId()) ? request.getKernelId() : normalizeOptionalImageIdentifier(request.getKernelId()); final String eri = normalizeOptionalImageIdentifier(request.getRamdiskId()); verifyImageNameAndDescription(request.getName(), request.getDescription()); ImageMetadata.VirtualizationType virtType = ImageMetadata.VirtualizationType.paravirtualized; if (request.getVirtualizationType() != null) { if (StringUtils.equalsIgnoreCase("paravirtual", request.getVirtualizationType())) virtType = ImageMetadata.VirtualizationType.paravirtualized; else if (StringUtils.equalsIgnoreCase("hvm", request.getVirtualizationType())) virtType = ImageMetadata.VirtualizationType.hvm; else throw new EucalyptusCloudException("Unknown virtualization-type"); } if (request.getImageLocation() != null) { // Verify all the device mappings first. bdmInstanceStoreImageVerifier().apply(request); //When there is more than one verifier, something like this can be handy: Predicates.and(bdmVerifier(Boolean.FALSE)...).apply(request); final ImageManifest manifest = ImageManifests.lookup(request.getImageLocation(), ctx.getUser()); LOG.debug("Obtained manifest information for requested image registration: " + manifest); final ImageMetadata.Platform imagePlatform = request.getPlatform() != null ? ImageMetadata.Platform.valueOf(request.getPlatform()) : manifest.getPlatform(); if (ImageMetadata.Platform.windows.equals(imagePlatform)) virtType = ImageMetadata.VirtualizationType.hvm; final ImageMetadata.VirtualizationType virtualizationType = virtType; if (ImageMetadata.Type.machine.equals(manifest.getImageType()) && ImageMetadata.VirtualizationType.paravirtualized.equals(virtualizationType)) { // make sure kernel and ramdisk are present with the request or manifest if (request.getKernelId() == null && manifest.getKernelId() == null) throw new ClientComputeException("MissingParameter", "Kernel ID must be specified"); if (request.getRamdiskId() == null && manifest.getRamdiskId() == null) throw new ClientComputeException("MissingParameter", "Ramdisk ID must be specified"); } //Check that the manifest-specified size of the image is within bounds. //If null image max size then always allow Integer maxSize = ImageConfiguration.getInstance().getMaxImageSizeGb(); if (maxSize != null && maxSize > 0 && manifest.getSize() > (maxSize * GB)) { throw new EucalyptusCloudException("Cannot register image of size " + manifest.getSize() + " bytes because it exceeds the configured maximum instance-store image size of " + maxSize + " GB. Please contact your administrator "); } List<DeviceMapping> vbr = Lists.transform(request.getBlockDeviceMappings(), Images.deviceMappingGenerator(imageInfo, null)); final ImageMetadata.Architecture arch = (request.getArchitecture() == null ? null : ImageMetadata.Architecture.valueOf(request.getArchitecture())); Supplier<ImageInfo> allocator = new Supplier<ImageInfo>() { @Override public ImageInfo get() { try { /// TODO: we use virt-type as the heuristics for determining image-format /// In the future, we should manifest's block device mapping which is an ec2-way for expressing the image format if (ImageMetadata.Type.machine.equals(manifest.getImageType()) && ImageMetadata.VirtualizationType.paravirtualized.equals(virtualizationType)) return Images.createPendingAvailableFromManifest(ctx.getUserFullName(), request.getName(), request.getDescription(), arch, virtualizationType, ImageMetadata.Platform.linux, ImageMetadata.ImageFormat.partitioned, eki, eri, manifest); else return Images.registerFromManifest(ctx.getUserFullName(), request.getName(), request.getDescription(), arch, virtualizationType, imagePlatform, ImageMetadata.ImageFormat.fulldisk, eki, eri, manifest); } catch (Exception ex) { LOG.error(ex); Logs.extreme().error(ex, ex); throw Exceptions.toUndeclared(ex); } } }; imageInfo = RestrictedTypes.allocateUnitlessResource(allocator); imageInfo.getDeviceMappings().addAll(vbr); } else if (rootDevName != null && Iterables.any(request.getBlockDeviceMappings(), Images.findEbsRoot(rootDevName))) { Supplier<ImageInfo> allocator = null; // Verify all the device mappings first. Dont fuss if both snapshot id and volume size are left blank bdmBfebsImageVerifier().apply(request); ImageMetadata.Platform platform = ImageMetadata.Platform.linux; if (request.getPlatform() != null) platform = ImageMetadata.Platform.valueOf(request.getPlatform()); else if (ImageMetadata.Platform.windows.name().equals(eki)) platform = ImageMetadata.Platform.windows; final ImageMetadata.Platform imagePlatform = platform; allocator = new Supplier<ImageInfo>() { @Override public ImageInfo get() { try { return Images.createFromDeviceMapping(ctx.getUserFullName(), request.getName(), request.getDescription(), imagePlatform, eki, eri, rootDevName, request.getBlockDeviceMappings()); } catch (EucalyptusCloudException ex) { throw new RuntimeException(ex); } } }; imageInfo = RestrictedTypes.allocateUnitlessResource(allocator); } else { throw new EucalyptusCloudException( "Invalid request: the request must specify either ImageLocation for an " + "instance-store image or a snapshot for the root device for an EBS image. " + "Provided values were: ImageLocation=" + request.getImageLocation() + " BlockDeviceMappings=" + request.getBlockDeviceMappings()); } RegisterImageResponseType reply = (RegisterImageResponseType) request.getReply(); reply.setImageId(imageInfo.getDisplayName()); return reply; } public DeregisterImageResponseType deregister(DeregisterImageType request) throws EucalyptusCloudException { DeregisterImageResponseType reply = request.getReply(); EntityTransaction tx = Entities.get(ImageInfo.class); try { ImageInfo imgInfo = Entities .uniqueResult(Images.exampleWithImageId(imageIdentifier(request.getImageId()))); if (!canModifyImage(imgInfo)) { throw new EucalyptusCloudException("Not authorized to deregister image"); } Images.deregisterImage(imgInfo.getDisplayName()); tx.commit(); return reply; } catch (NoSuchImageException | NoSuchElementException ex) { throw new ClientComputeException("InvalidAMIID.NotFound", "The image ID '" + request.getImageId() + "' does not exist"); } catch (InstanceNotTerminatedException | ConstraintViolationException re) { throw new ClientComputeException("InvalidAMIID.Unavailable", "The image ID '" + request.getImageId() + "' is no longer available"); } catch (TransactionException ex) { if (ex.getCause() instanceof NoSuchElementException) throw new ClientComputeException("InvalidAMIID.NotFound", "The image ID '" + request.getImageId() + "' does not exist"); else throw new EucalyptusCloudException(ex); } finally { if (tx.isActive()) tx.rollback(); } } public ConfirmProductInstanceResponseType confirmProductInstance(ConfirmProductInstanceType request) throws EucalyptusCloudException { ConfirmProductInstanceResponseType reply = (ConfirmProductInstanceResponseType) request.getReply(); reply.set_return(false); VmInstance vm = null; try { vm = VmInstances.lookup(request.getInstanceId()); //ASAP: FIXME: GRZE: RESTORE! // EntityWrapper<ImageInfo> db = EntityWrapper.get( ImageInfo.class ); // try { // ImageInfo found = db.getUnique( new ImageInfo( vm.getImageInfo( ).getImageId( ) ) ); // if ( found.getProductCodes( ).contains( new ProductCode( request.getProductCode( ) ) ) ) { // reply.set_return( true ); // reply.setOwnerId( found.getImageOwnerId( ) ); // } // db.commit( ); // } catch ( EucalyptusCloudException e ) { // db.commit( ); // } } catch (NoSuchElementException e) { } return reply; } public DescribeImageAttributeResponseType describeImageAttribute(final DescribeImageAttributeType request) throws EucalyptusCloudException { DescribeImageAttributeResponseType reply = (DescribeImageAttributeResponseType) request.getReply(); if (request.getAttribute() != null) request.applyAttribute(); final EntityTransaction tx = Entities.get(ImageInfo.class); try { final ImageInfo imgInfo = Entities .uniqueResult(Images.exampleWithImageId(imageIdentifier(request.getImageId()))); if (!canModifyImage(imgInfo)) { throw new EucalyptusCloudException("Not authorized to describe image attribute"); } reply.setImageId(imgInfo.getDisplayName()); if (request.getKernel() != null) { if (imgInfo instanceof MachineImageInfo) { if (((MachineImageInfo) imgInfo).getKernelId() != null) { reply.getKernel().add(((MachineImageInfo) imgInfo).getKernelId()); } } } else if (request.getRamdisk() != null) { if (imgInfo instanceof MachineImageInfo) { if (((MachineImageInfo) imgInfo).getRamdiskId() != null) { reply.getRamdisk().add(((MachineImageInfo) imgInfo).getRamdiskId()); } } } else if (request.getLaunchPermission() != null) { if (imgInfo.getImagePublic()) { reply.getLaunchPermission().add(LaunchPermissionItemType.newGroupLaunchPermission()); } for (final String permission : imgInfo.getPermissions()) reply.getLaunchPermission().add(LaunchPermissionItemType.newUserLaunchPermission(permission)); } else if (request.getProductCodes() != null) { reply.getProductCodes().addAll(imgInfo.getProductCodes()); } else if (request.getBlockDeviceMapping() != null) { if (imgInfo instanceof BlockStorageImageInfo) { BlockStorageImageInfo bfebsImage = (BlockStorageImageInfo) imgInfo; reply.getBlockDeviceMapping().add(new BlockDeviceMappingItemType( VmInstances.EBS_ROOT_DEVICE_NAME, bfebsImage.getRootDeviceName())); reply.getBlockDeviceMapping() .add(new BlockDeviceMappingItemType("root", bfebsImage.getRootDeviceName())); int i = 0; for (DeviceMapping mapping : bfebsImage.getDeviceMappings()) { if (mapping.getDeviceName().equalsIgnoreCase(bfebsImage.getRootDeviceName())) { continue; } switch (mapping.getDeviceMappingType()) { case blockstorage: BlockStorageDeviceMapping bsdm = (BlockStorageDeviceMapping) mapping; BlockDeviceMappingItemType bdmItem = new BlockDeviceMappingItemType("ebs" + (++i), mapping.getDeviceName()); EbsDeviceMapping ebsItem = new EbsDeviceMapping(); ebsItem.setSnapshotId(bsdm.getSnapshotId()); ebsItem.setVolumeSize(bsdm.getSize()); ebsItem.setDeleteOnTermination(bsdm.getDelete()); bdmItem.setEbs(ebsItem); reply.getBlockDeviceMapping().add(bdmItem); break; case ephemeral: reply.getBlockDeviceMapping().add(new BlockDeviceMappingItemType( mapping.getVirtualName(), mapping.getDeviceName())); break; default: break; } } } else { reply.getBlockDeviceMapping() .add(new BlockDeviceMappingItemType(VmInstances.EBS_ROOT_DEVICE_NAME, "sda1")); reply.getBlockDeviceMapping().add(new BlockDeviceMappingItemType("ephemeral0", "sda2")); reply.getBlockDeviceMapping().add(new BlockDeviceMappingItemType("swap", "sda3")); reply.getBlockDeviceMapping().add(new BlockDeviceMappingItemType("root", "/dev/sda1")); } } else if (request.getDescription() != null) { if (imgInfo.getDescription() != null) { reply.getDescription().add(imgInfo.getDescription()); } } else { throw new EucalyptusCloudException("invalid image attribute request."); } } catch (TransactionException | NoSuchElementException ex) { throw new EucalyptusCloudException("Error handling image attribute request: " + ex.getMessage(), ex); } finally { tx.commit(); } return reply; } private static List<String> verifyUserIds(final List<String> userIds) throws EucalyptusCloudException { final Set<String> validUserIds = Sets.newHashSet(); for (String userId : userIds) { try { validUserIds.add(Accounts.lookupAccountById(userId).getAccountNumber()); } catch (final Exception e) { try { validUserIds.add(Accounts.lookupUserById(userId).getAccount().getAccountNumber()); } catch (AuthException ex) { try { validUserIds.add(Accounts.lookupUserByAccessKeyId(userId).getAccount().getAccountNumber()); } catch (AuthException ex1) { throw new EucalyptusCloudException("Not a valid userId : " + userId); } } } } return Lists.newArrayList(validUserIds); } public ModifyImageAttributeResponseType modifyImageAttribute(final ModifyImageAttributeType request) throws EucalyptusCloudException { final ModifyImageAttributeResponseType reply = (ModifyImageAttributeResponseType) request.getReply(); final EntityTransaction tx = Entities.get(ImageInfo.class); try { final ImageInfo imgInfo = Entities .uniqueResult(Images.exampleWithImageId(imageIdentifier(request.getImageId()))); if (!canModifyImage(imgInfo)) { throw new EucalyptusCloudException("Not authorized to modify image attribute"); } switch (request.imageAttribute()) { case LaunchPermission: if (request.add()) { imgInfo.addPermissions(verifyUserIds(request.userIds())); if (request.groupAll()) { imgInfo.setImagePublic(true); } } else { imgInfo.removePermissions(request.userIds()); if (request.groupAll()) { imgInfo.setImagePublic(false); } } break; case ProductCode: for (String productCode : request.getProductCodes()) { imgInfo.addProductCode(productCode); } break; case Description: imgInfo.setDescription(request.getDescription()); break; } tx.commit(); reply.set_return(true); } catch (EucalyptusCloudException e) { tx.rollback(); reply.set_return(false); throw e; } catch (TransactionException | NoSuchElementException ex) { tx.rollback(); throw new EucalyptusCloudException(ex); } return reply; } public ResetImageAttributeResponseType resetImageAttribute(ResetImageAttributeType request) throws EucalyptusCloudException { ResetImageAttributeResponseType reply = (ResetImageAttributeResponseType) request.getReply(); reply.set_return(true); EntityTransaction tx = Entities.get(ImageInfo.class); try { ImageInfo imgInfo = Entities .uniqueResult(Images.exampleWithImageId(imageIdentifier(request.getImageId()))); if (canModifyImage(imgInfo)) { imgInfo.resetPermission(); tx.commit(); return reply.markWinning(); } else { tx.rollback(); return reply.markFailed(); } } catch (EucalyptusCloudException e) { LOG.error(e, e); tx.rollback(); return reply.markFailed(); } catch (TransactionException | NoSuchElementException ex) { tx.rollback(); return reply.markFailed(); } } public CreateImageResponseType createImage(CreateImageType request) throws EucalyptusCloudException { final CreateImageResponseType reply = request.getReply(); final Context ctx = Contexts.lookup(); verifyImageNameAndDescription(request.getName(), request.getDescription()); final String instanceId = normalizeInstanceIdentifier(request.getInstanceId()); VmInstance vm; // IAM auth check, validate instance states, etc try { vm = RestrictedTypes.doPrivileged(instanceId, VmInstance.class); if (!vm.isBlockStorage()) throw new EucalyptusCloudException( "Cannot create an image from an instance which is not booted from an EBS volume"); if (!VmState.RUNNING.equals(vm.getState()) && !VmState.STOPPED.equals(vm.getState())) { throw new EucalyptusCloudException( "Cannot create an image from an instance which is not in either the 'running' or 'stopped' state: " + vm.getInstanceId() + " is in state " + vm.getState().getName()); } Cluster cluster = null; try { ServiceConfiguration ccConfig = Topology.lookup(ClusterController.class, vm.lookupPartition()); cluster = Clusters.lookup(ccConfig); } catch (NoSuchElementException e) { LOG.debug(e); throw new EucalyptusCloudException("Cluster does not exist: " + vm.getPartition()); } } catch (AuthException ex) { throw new EucalyptusCloudException("Not authorized to create an image from instance " + request.getInstanceId() + " as " + ctx.getUser().getName()); } catch (NoSuchElementException ex) { throw new EucalyptusCloudException("Instance does not exist: " + request.getInstanceId(), ex); } catch (PersistenceException ex) { throw new EucalyptusCloudException("Instance does not exist: " + request.getInstanceId(), ex); } final String userId = ctx.getUser().getUserId(); final String name = request.getName(); final boolean noReboot = true ? request.getNoReboot() != null && request.getNoReboot().booleanValue() : false; final String desc = request.getDescription(); String rootDeviceName = null; List<BlockDeviceMappingItemType> blockDevices = request.getBlockDeviceMappings(); ImageMetadata.Architecture arch = null; ImageMetadata.Platform platform = null; try { final BlockStorageImageInfo image = Emis.LookupBlockStorage.INSTANCE.apply(vm.getImageId()); arch = image.getArchitecture(); platform = image.getPlatform(); rootDeviceName = image.getRootDeviceName(); } catch (final Exception ex) { throw new EucalyptusCloudException("Unable to get the image information"); } final ImageMetadata.Architecture imageArch = arch; final ImageMetadata.Platform imagePlatform = platform; // if device mapping is not requested, we copy it (ephemeral only; createImageTask will perform snapshot) from the instance if (blockDevices == null || blockDevices.size() <= 0) { try { blockDevices = Lists.transform(VmInstances.lookupEphemeralDevices(instanceId), VmInstances.EphemeralAttachmentToDevice); } catch (final Exception ex) { LOG.warn("Failed to retrieve ephemeral device information", ex); blockDevices = Lists.newArrayList(); } } else { for (final BlockDeviceMappingItemType device : blockDevices) { if (rootDeviceName != null && rootDeviceName.equals(device.getDeviceName())) throw new ClientComputeException("InvalidBlockDeviceMapping", "The device names should not contain root device"); } if (!bdmCreateImageVerifier().apply(request)) { throw new ClientComputeException("InvalidBlockDeviceMapping", "A block device mapping parameter is not valid"); } } final List<BlockDeviceMappingItemType> blockDeviceMapping = blockDevices; Supplier<ImageInfo> allocator = new Supplier<ImageInfo>() { @Override public ImageInfo get() { try { return Images.createPendingFromDeviceMapping(ctx.getUserFullName(), name, desc, imageArch, imagePlatform, blockDeviceMapping); } catch (final Exception ex) { throw new RuntimeException(ex); } } }; Predicate<ImageInfo> deallocator = new Predicate<ImageInfo>() { @Override public boolean apply(@Nullable ImageInfo input) { try { Images.setImageState(input.getDisplayName(), ImageMetadata.State.failed); Images.deregisterImage(input.getDisplayName()); } catch (final Exception ex) { LOG.error("failed to delete image from unsucccessful create-image request", ex); return false; } return true; } }; ImageInfo imageInfo = null; try { imageInfo = RestrictedTypes.allocateUnitlessResource(allocator); reply.setImageId(imageInfo.getDisplayName()); } catch (final AuthException ex) { throw new ClientComputeException("AuthFailure", "Not authorized to create an image"); } catch (final Exception ex) { LOG.error("Unable to register the image", ex); throw new EucalyptusCloudException("Unable to register the image", ex); } final CreateImageTask task = new CreateImageTask(userId, instanceId, noReboot, blockDevices); try { task.create(imageInfo.getDisplayName()); } catch (final Exception ex) { deallocator.apply(imageInfo); LOG.error("CreateImage task failed", ex); if (ex instanceof EucalyptusCloudException) throw (EucalyptusCloudException) ex; else throw new EucalyptusCloudException("Create-image has failed", ex); } return reply; } private static String imageIdentifier(final String identifier) throws EucalyptusCloudException { if (!CloudMetadatas.isImageIdentifier(identifier)) throw new EucalyptusCloudException("Invalid id: " + "\"" + identifier + "\""); return normalizeImageIdentifier(identifier); } private enum ImageDetailsToImageId implements Function<ImageDetails, String> { INSTANCE { @Override public String apply(ImageDetails imageDetails) { return imageDetails.getImageId(); } } } /** * <p>Predicate to validate the block device mappings in instance store image registration request. * Suppressing a device mapping is allowed and ebs mappings are considered invalid.</p> */ private static Predicate<RegisterImageType> bdmInstanceStoreImageVerifier() { return new Predicate<RegisterImageType>() { @Override public boolean apply(RegisterImageType arg0) { checkParam(arg0, notNullValue()); try { Images.validateBlockDeviceMappings(arg0.getBlockDeviceMappings(), EnumSet.of(AllowSuppressMapping)); return true; } catch (MetadataException e) { throw Exceptions.toUndeclared(e); } } }; } /** * <p>Predicate to validate the block device mappings in boot from ebs image registration request. * Suppressing a device mapping is not allowed and ebs mappings are considered valid</p> */ private static Predicate<RegisterImageType> bdmBfebsImageVerifier() { return new Predicate<RegisterImageType>() { @Override public boolean apply(RegisterImageType arg0) { checkParam(arg0, notNullValue()); try { Images.validateBlockDeviceMappings(arg0.getBlockDeviceMappings(), EnumSet.of(AllowEbsMapping, AllowDevSda1)); return true; } catch (MetadataException e) { throw Exceptions.toUndeclared(e); } } }; } /* * <p>Predicate to validate the block device mappings in create image request. * Suppressing a device mapping is not allowed and ebs mappings are considered valid</p> */ private static Predicate<CreateImageType> bdmCreateImageVerifier() { return new Predicate<CreateImageType>() { @Override public boolean apply(CreateImageType arg0) { checkParam(arg0, notNullValue()); try { Images.validateBlockDeviceMappings(arg0.getBlockDeviceMappings(), EnumSet.of(AllowEbsMapping)); return true; } catch (MetadataException e) { throw Exceptions.toUndeclared(e); } } }; } private static void verifyImageNameAndDescription(final String name, final String description) throws ComputeException { final Context ctx = Contexts.lookup(); if (name != null) { if (!Images.isImageNameValid(name)) { throw new ClientComputeException("InvalidAMIName.Malformed", "AMI names must be between 3 and 128 characters long, and may contain letters, numbers, '(', ')', '.', '-', '/' and '_'"); } final EntityTransaction db = Entities.get(ImageInfo.class); try { final List<ImageInfo> images = Lists.newArrayList(Iterables.filter( Entities.query(Images.exampleWithName(ctx.getUserFullName().asAccountFullName(), name), Entities.queryOptions().withReadonly(true).build()), new Predicate<ImageInfo>() { @Override public boolean apply(ImageInfo arg0) { return ImageMetadata.State.available.name() .equals(arg0.getState().getExternalStateName()) || ImageMetadata.State.pending.name() .equals(arg0.getState().getExternalStateName()); } })); if (images.size() > 0) throw new ClientComputeException("InvalidAMIName.Duplicate", String.format( "AMI name %s is already in use by EMI %s", name, images.get(0).getDisplayName())); } catch (final ComputeException e) { throw e; } catch (final Exception ex) { LOG.error("Error checking for duplicate image name", ex); throw new ComputeException("InternalError", "Error processing request."); } finally { db.rollback(); } } if (description != null && !Images.isImageDescriptionValid(description)) { throw new ClientComputeException("InvalidParameter", "AMI descriptions must be less than 256 characters long"); } } private static boolean canModifyImage(final ImageInfo imgInfo) { final Context ctx = Contexts.lookup(); final String requestAccountId = ctx.getUserFullName().getAccountNumber(); return (ctx.isAdministrator() || imgInfo.getOwnerAccountNumber().equals(requestAccountId)) && RestrictedTypes.filterPrivileged().apply(imgInfo); } private static String normalizeIdentifier(final String identifier, final String prefix, final boolean required, final String message) throws ClientComputeException { try { return Strings.emptyToNull(identifier) == null && !required ? null : ResourceIdentifiers.parse(prefix, identifier).getIdentifier(); } catch (final InvalidResourceIdentifier e) { throw new ClientComputeException("InvalidParameterValue", String.format(message, e.getIdentifier())); } } private static String normalizeInstanceIdentifier(final String identifier) throws EucalyptusCloudException { return normalizeIdentifier(identifier, VmInstance.ID_PREFIX, true, "Value (%s) for parameter instanceId is invalid. Expected: 'i-...'."); } private static String normalizeImageIdentifier(final String identifier) throws EucalyptusCloudException { return normalizeIdentifier(identifier, null, true, "Value (%s) for parameter image is invalid."); } @Nullable private static String normalizeOptionalImageIdentifier(final String identifier) throws EucalyptusCloudException { return normalizeIdentifier(identifier, null, false, "Value (%s) for parameter image is invalid."); } private static List<String> normalizeImageIdentifiers(final List<String> identifiers) throws EucalyptusCloudException { try { return ResourceIdentifiers.normalize(identifiers); } catch (final InvalidResourceIdentifier e) { throw new ClientComputeException("InvalidParameterValue", "Value (" + e.getIdentifier() + ") for parameter images is invalid."); } } }