/*************************************************************************
* 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.
************************************************************************/
package com.eucalyptus.imaging.backend;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.annotation.Nullable;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.Lob;
import javax.persistence.PersistenceContext;
import javax.persistence.PostLoad;
import javax.persistence.Transient;
import org.apache.log4j.Logger;
import org.hibernate.annotations.Type;
import com.eucalyptus.blockstorage.Volumes;
import com.eucalyptus.compute.common.ConversionTask;
import com.eucalyptus.compute.common.DiskImage;
import com.eucalyptus.compute.common.DiskImageDescription;
import com.eucalyptus.compute.common.DiskImageVolumeDescription;
import com.eucalyptus.compute.common.ImageDetails;
import com.eucalyptus.compute.common.ImportInstanceLaunchSpecification;
import com.eucalyptus.compute.common.ImportInstanceTaskDetails;
import com.eucalyptus.compute.common.ImportInstanceType;
import com.eucalyptus.compute.common.ImportInstanceVolumeDetail;
import com.eucalyptus.compute.common.RunningInstancesItemType;
import com.eucalyptus.compute.common.Snapshot;
import com.eucalyptus.compute.common.Volume;
import com.eucalyptus.compute.common.internal.identifier.ResourceIdentifiers;
import com.eucalyptus.context.Contexts;
import com.eucalyptus.imaging.ImportTaskProperties;
import com.eucalyptus.resources.client.Ec2Client;
import com.eucalyptus.util.Dates;
import com.eucalyptus.auth.principal.OwnerFullName;
import com.eucalyptus.util.TypeMapper;
import com.google.common.base.Function;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* @author Sang-Min Park
*
*/
@Entity
@PersistenceContext( name = "eucalyptus_imaging" )
@DiscriminatorValue( value = "instance-imaging-task" )
public class ImportInstanceImagingTask extends VolumeImagingTask {
private static Logger LOG = Logger.getLogger( ImportInstanceImagingTask.class );
@Column ( name = "metadata_launchspec_architecture")
private String architecture;
@ElementCollection
@CollectionTable( name = "metadata_launchspec_security_groups" )
private List<String> groupNames;
@Transient
private ImmutableList<String> groupNamesCopy;
@ElementCollection
@CollectionTable( name = "metadata_snapshots")
private Set<String> snapshotIds;
@Transient
private ImmutableList<String> snapshotIdsCopy;
@Type( type = "org.hibernate.type.StringClobType" )
@Lob
@Column( name = "metadata_launchspec_userdata")
private String userData;
@Column ( name = "metadata_launchspec_instance_type")
private String instanceType;
@Column ( name = "metadata_launchspec_availability_zone")
private String availabilityZone;
@Column ( name = "metadata_launchspec_monitoring_enabled")
private Boolean monitoringEnabled;
/// VPC
@Column ( name = "metadata_launchspec_subnet_id")
private String subnetId;
/// NOT SUPPORTED
@Column ( name = "metadata_launchspec_shutdown_behavior")
private String shutdownBehavior;
/// VPC
@Column ( name = "metadata_launchspec_private_ip_address")
private String privateIpAddress;
// EUCA-specific extension
@Column ( name = "metadata_launchspec_key_name")
private String keyName;
@Column ( name = "metadata_image_id")
private String imageId;
protected ImportInstanceImagingTask(){}
protected ImportInstanceImagingTask(OwnerFullName ownerFullName,
ConversionTask conversionTask) {
super(ownerFullName, conversionTask, ImportTaskState.NEW, 0L);
}
public void setLaunchSpecArchitecture(final String architecture){
this.architecture = architecture;
}
public String getLaunchSpecArchitecture(){
return this.architecture;
}
public List<String> getLaunchSpecGroupNames(){
return groupNamesCopy;
}
public void addLaunchSpecGroupName(final String groupName){
if(this.groupNames==null)
this.groupNames = Lists.newArrayList();
this.groupNames.add(groupName);
}
public void setLaunchSpecUserData(final String userData){
this.userData = userData;
}
public String getLaunchSpecUserData(){
return this.userData;
}
public void setLaunchSpecInstanceType(final String instanceType){
this.instanceType = instanceType;
}
public String getLaunchSpecInstanceType(){
return this.instanceType;
}
public void setLaunchSpecAvailabilityZone(final String availabilityZone){
this.availabilityZone = availabilityZone;
}
public String getLaunchSpecAvailabilityZone(){
return this.availabilityZone;
}
public void setLaunchSpecMonitoringEnabled(final Boolean monitoringEnabled){
this.monitoringEnabled = monitoringEnabled;
}
public Boolean getLaunchSpecMonitoringEnabled(){
return this.monitoringEnabled;
}
public void setLaunchSpecKeyName(final String keyName){
this.keyName = keyName;
}
public String getLaunchSpecKeyName(){
return this.keyName;
}
public String getLaunchSpecSubnetId() {
return subnetId;
}
public void setLaunchSpecSubnetId( final String subnetId ) {
this.subnetId = subnetId;
}
public String getLaunchSpecPrivateIpAddress() {
return privateIpAddress;
}
public void setLaunchSpecPrivateIpAddress( final String privateIpAddress ) {
this.privateIpAddress = privateIpAddress;
}
public List<ImportInstanceVolumeDetail> getVolumes(){
final ImportInstanceTaskDetails importTask = this.getTask().getImportInstance();
return importTask.getVolumes();
}
public String getInstanceId() {
try{
final ImportInstanceTaskDetails importTask = this.getTask().getImportInstance();
return importTask.getInstanceId();
}catch(final Exception ex){
return null;
}
}
public List<String> getSnapshotIds(){
return this.snapshotIdsCopy;
}
public void addSnapshotId(final String snapshotId){
if(this.snapshotIds==null)
this.snapshotIds = Sets.newHashSet();
this.snapshotIds.add(snapshotId);
}
public void setImageId(final String imageId){
this.imageId = imageId;
}
public String getImageId(){
return this.imageId;
}
@PostLoad
protected void onLoad(){
this.snapshotIdsCopy = ImmutableList.copyOf(this.snapshotIds);
this.groupNamesCopy = ImmutableList.copyOf(this.groupNames);
super.onLoad();
}
@Override
public boolean cleanUp(){
if (getCleanUpDone())
return true;
final String instanceId = this.getInstanceId();
if(instanceId!=null && instanceId.length()>0) {
try{
final List<RunningInstancesItemType> instances =
Ec2Client.getInstance().describeInstances(this.getOwnerUserId(), Lists.newArrayList(instanceId));
final Set<String> statesToTerminate = Sets.newHashSet("running", "pending");
for (final RunningInstancesItemType instance: instances) {
if(instanceId.equals(instance.getInstanceId()) && statesToTerminate.contains(instance.getStateName())){
Ec2Client.getInstance().terminateInstances(this.getOwnerUserId(), Lists.newArrayList(instanceId));
LOG.info(String.format("Instance %s is terminated because import task was cancelled or failed", instanceId));
}
}
}catch(final Exception ex) {
;
}
}
boolean cleanedImage = true;
if(this.imageId != null) {
try{
final List<ImageDetails> images = Ec2Client.getInstance().describeImages(this.getOwnerUserId(), Lists.newArrayList(this.imageId));
for(final ImageDetails image : images) {
if (this.imageId.equals(image.getImageId()) && "available".equals(image.getImageState()))
cleanedImage = false;
}
}catch(final Exception ex) {
;
}
if(!cleanedImage) {
try{
Ec2Client.getInstance().deregisterImage(this.getOwnerUserId(), this.imageId);
LOG.info(String.format("Image %s is deregistered because import task was cancelled or failed", this.imageId));
}catch(final Exception ex) {
LOG.error("Failed to deregister image '" + this.imageId +"' after cancelled/failed import task");
}
cleanedImage = true;
}
}
if(!cleanedImage)
return false;
boolean cleanedSnapshots = false;
final List<String> snapshotIds = this.getSnapshotIds();
if(snapshotIds != null && snapshotIds.size()>0) {
int numCompletedSnapshots = 0;
try{
final List<Snapshot> snapshots =
Ec2Client.getInstance().describeSnapshots(this.getOwnerUserId(), snapshotIds);
if(snapshots == null || snapshots.size()<=0)
cleanedSnapshots = true;
else{
for(final Snapshot snapshot : snapshots) {
if(! "pending".equals(snapshot.getStatus())) {
numCompletedSnapshots++;
}
}
}
}catch(final Exception ex) {
cleanedSnapshots = true;
}
// wait until in-progress snapshots are complete before attempting to delete them
if(!cleanedSnapshots && numCompletedSnapshots >= snapshotIds.size()) {
for(final String snapshotId : snapshotIds ) {
try{
Ec2Client.getInstance().deleteSnapshot(this.getOwnerUserId(), snapshotId);
LOG.info(String.format("Snapshot %s is deleted because import task was cancelled or failed", snapshotId));
}catch(final Exception ex) {
LOG.error("Failed to delete snapshot '" +snapshotId + "' after cancelled/failed import task");
}
}
cleanedSnapshots = true;
}
}else{
cleanedSnapshots = true;
}
if(!cleanedSnapshots)
return false;
final ImportInstanceTaskDetails instanceDetails =
this.getTask().getImportInstance();
if(instanceDetails.getVolumes()!=null) {
for(final ImportInstanceVolumeDetail volumeDetail : instanceDetails.getVolumes()){
if(volumeDetail.getVolume()!=null && volumeDetail.getVolume().getId()!=null){
String volumeId = volumeDetail.getVolume().getId();
try{
/// TODO: IMAGING TASK SHOULD NOT TOUCH VOLUMES DIRECTLY!!
Volumes.setSystemManagedFlag(null, volumeId, false);
final List<Volume> eucaVolumes =
Ec2Client.getInstance().describeVolumes(this.getOwnerUserId(), Lists.newArrayList(volumeId));
if (eucaVolumes!=null && eucaVolumes.size() != 0) {
Ec2Client.getInstance().deleteVolume(this.getOwnerUserId(), volumeId);
LOG.info(String.format("Volume %s is deleted because import task was cancelled or failed", volumeId));
}
} catch(final NoSuchElementException ex) {
;
} catch(final Exception ex) {
LOG.warn(String.format("Failed to delete volume %s for cancelled/failed import task %s",
volumeId, this.getDisplayName()));
}
}
}
}
setCleanUpDone(true);
return true;
}
@TypeMapper
enum InstanceImagingTaskTransform implements Function<ImportInstanceType, ImportInstanceImagingTask> {
INSTANCE;
@Nullable
@Override
public ImportInstanceImagingTask apply(ImportInstanceType input) {
final ConversionTask ct = new ConversionTask();
// TODO: do we use the instance id when launched?
String conversionTaskId = ResourceIdentifiers.generateString("import-i");
conversionTaskId = conversionTaskId.toLowerCase();
ct.setConversionTaskId( conversionTaskId );
ct.setExpirationTime( new Date( Dates.hoursFromNow( Integer.parseInt(ImportTaskProperties.IMPORT_TASK_EXPIRATION_HOURS) ).getTime( ) ).toString( ) );
ct.setState( ImportTaskState.NEW.getExternalTaskStateName() );
ct.setStatusMessage( "" );
final ImportInstanceTaskDetails instanceTask = new ImportInstanceTaskDetails();
instanceTask.setDescription(input.getDescription());
instanceTask.setPlatform(input.getPlatform());
final ImportInstanceLaunchSpecification launchSpec = input.getLaunchSpecification();
final List<ImportInstanceVolumeDetail> volumes = Lists.newArrayList();
final List<DiskImage> disks = input.getDiskImageSet();
if(disks!=null){
for(final DiskImage disk : disks){
final ImportInstanceVolumeDetail volume = new ImportInstanceVolumeDetail();
if(launchSpec!=null && launchSpec.getPlacement()!=null)
volume.setAvailabilityZone(launchSpec.getPlacement().getAvailabilityZone());
volume.setImage(new DiskImageDescription());
volume.getImage().setFormat(disk.getImage().getFormat());
//TODO: remove this later
String manifestUrl = disk.getImage().getImportManifestUrl();
volume.getImage().setImportManifestUrl(manifestUrl);
volume.getImage().setSize(disk.getImage().getBytes()); // TODO: is this right?
volume.setVolume(new DiskImageVolumeDescription());
volume.getVolume().setSize(disk.getVolume().getSize());
volume.setBytesConverted(0L);
volume.setStatus( ImportTaskState.NEW.getExternalTaskStateName( ) );
volumes.add(volume);
}
}
instanceTask.setVolumes((ArrayList<ImportInstanceVolumeDetail>) volumes);
ct.setImportInstance(instanceTask);
final ImportInstanceImagingTask newTask = new ImportInstanceImagingTask(Contexts.lookup().getUserFullName(), ct);
newTask.serializeTaskToJSON();
if(launchSpec.getArchitecture()==null || launchSpec.getArchitecture().length()<=0)
newTask.setLaunchSpecArchitecture("i386");
else
newTask.setLaunchSpecArchitecture(launchSpec.getArchitecture());
if(launchSpec.getUserData()!=null && launchSpec.getUserData().getData()!=null) //base64 encoded string
newTask.setLaunchSpecUserData(launchSpec.getUserData().getData());
newTask.setLaunchSpecInstanceType(launchSpec.getInstanceType());
if(launchSpec.getPlacement()!=null)
newTask.setLaunchSpecAvailabilityZone( launchSpec.getPlacement().getAvailabilityZone() );
if(launchSpec.getMonitoring()!=null)
newTask.setLaunchSpecMonitoringEnabled( launchSpec.getMonitoring().getEnabled() );
if(launchSpec.getGroupName()!=null){
for(final String groupName : launchSpec.getGroupName()){
newTask.addLaunchSpecGroupName(groupName);
}
}
if(launchSpec.getKeyName()!=null && launchSpec.getKeyName().length()>0){
newTask.setLaunchSpecKeyName(launchSpec.getKeyName());
}
if ( !Strings.isNullOrEmpty( launchSpec.getSubnetId( ) ) ) {
newTask.setLaunchSpecSubnetId( launchSpec.getSubnetId( ) );
newTask.setLaunchSpecPrivateIpAddress( launchSpec.getPrivateIpAddress( ) );
}
if(launchSpec.getInstanceInitiatedShutdownBehavior()!=null)
LOG.warn("InitiatedShutdownBehavior is not supported for import-instance");
return newTask;
}
}
}