/*************************************************************************
* 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.vm;
import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityTransaction;
import org.apache.log4j.Logger;
import com.amazonaws.services.s3.model.AmazonS3Exception;
import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.amazonaws.services.s3.model.VersionListing;
import com.eucalyptus.auth.AuthException;
import com.eucalyptus.auth.principal.AccountFullName;
import com.eucalyptus.auth.principal.User;
import com.eucalyptus.auth.tokens.SecurityTokenAWSCredentialsProvider;
import com.eucalyptus.cluster.common.msgs.ClusterBundleInstanceResponseType;
import com.eucalyptus.cluster.common.msgs.ClusterBundleInstanceType;
import com.eucalyptus.cluster.common.msgs.ClusterBundleRestartInstanceResponseType;
import com.eucalyptus.cluster.common.msgs.ClusterBundleRestartInstanceType;
import com.eucalyptus.cluster.common.msgs.ClusterCancelBundleTaskResponseType;
import com.eucalyptus.cluster.common.msgs.ClusterCancelBundleTaskType;
import com.eucalyptus.component.ServiceConfiguration;
import com.eucalyptus.component.ServiceUris;
import com.eucalyptus.component.Topology;
import com.eucalyptus.cluster.common.ClusterController;
import com.eucalyptus.compute.ClientComputeException;
import com.eucalyptus.compute.ComputeException;
import com.eucalyptus.compute.common.BundleTask;
import com.eucalyptus.compute.common.internal.vm.VmBundleTask;
import com.eucalyptus.compute.common.internal.vm.VmInstance;
import com.eucalyptus.compute.common.internal.vm.VmRuntimeState;
import com.eucalyptus.context.IllegalContextAccessException;
import com.eucalyptus.context.ServiceStateException;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.objectstorage.ObjectStorage;
import com.eucalyptus.records.EventRecord;
import com.eucalyptus.records.EventType;
import com.eucalyptus.records.Logs;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.util.async.AsyncRequests;
import com.eucalyptus.util.async.MessageCallback;
import com.google.common.base.Function;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.eucalyptus.objectstorage.client.EucaS3Client;
import com.eucalyptus.objectstorage.client.EucaS3ClientFactory;
public class Bundles {
public static synchronized void putPreviousTask(VmBundleTask previousTask) {
if (previousTask != null) {
previousBundleTaskMap.put(previousTask.getBundleId(), VmBundleTask.copyOf(previousTask));
}
}
public static synchronized Map<String, VmBundleTask> getPreviousBundleTasks() {
return ImmutableMap.copyOf(previousBundleTaskMap);
}
private static final Map<String, VmBundleTask> previousBundleTaskMap = Maps.newConcurrentMap();
private static Logger LOG = Logger.getLogger( Bundles.class );
public static MessageCallback createCallback( ClusterBundleInstanceType request ) throws AuthException, IllegalContextAccessException, ServiceStateException {
final String objectStorageUrl = ServiceUris.remote( Topology.lookup( ObjectStorage.class ) ).toASCIIString( );
request.setUrl( objectStorageUrl );
return new BundleCallback( request );
}
public static MessageCallback cancelCallback( ClusterCancelBundleTaskType request ) {
return new CancelBundleCallback( request );
}
public static void updateBundleTaskState( final VmInstance vm, String state ) {
VmBundleTask.BundleState next = VmBundleTask.BundleState.mapper.apply( state );
updateBundleTaskState( vm, next, 0.0d );
}
public static void bundleRestartInstance( VmBundleTask bundleTask ) {
VmBundleTask.BundleState state = bundleTask.getState( );
if ( VmBundleTask.BundleState.complete.equals( state ) || VmBundleTask.BundleState.failed.equals( state ) || VmBundleTask.BundleState.cancelled.equals( state ) ) {
final ClusterBundleRestartInstanceType request = new ClusterBundleRestartInstanceType( );
try {
LOG.info( EventRecord.here( BundleCallback.class, EventType.BUNDLE_RESTART, bundleTask.getVmInstance().getOwner().getUserName(),
bundleTask.getBundleId(),
bundleTask.getVmInstance().getInstanceId() ) );
ServiceConfiguration ccConfig = Topology.lookup( ClusterController.class, bundleTask.getVmInstance( ).lookupPartition() );
request.setInstanceId( bundleTask.getVmInstance( ).getInstanceId() );
AsyncRequests.newRequest( bundleRestartInstanceCallback( request ) ).dispatch( ccConfig );
} catch ( final Exception e ) {
Logs.extreme( ).trace( "Failed to find bundle task: " + bundleTask.getBundleId( ) );
}
}
}
private static void setBundleTaskState( final VmBundleTask task, final VmBundleTask.BundleState state ) {
final VmBundleTask.BundleState previousState = task.getState( );
if ( previousState != null && previousState != state ) {
putPreviousTask( task );
}
task.setState( state );
}
public static void updateBundleTaskState( VmInstance vm, VmBundleTask.BundleState state, Double progress ) {
if ( vm.getRuntimeState( ).getBundleTask() != null ) {
final VmBundleTask.BundleState current = vm.getRuntimeState( ).getBundleTask().getState( );
VmBundleTask currentTask = vm.getRuntimeState( ).getBundleTask();
currentTask.setProgress((int) Math.round(progress * 100D));
if ( VmBundleTask.BundleState.complete.equals( state ) && !VmBundleTask.BundleState.complete.equals( current ) && !VmBundleTask.BundleState.none.equals( current ) ) {
setBundleTaskState( currentTask, state );
// set progress to 100% if complete is reached
currentTask.setProgress( 100 );
bundleRestartInstance( currentTask );
} else if ( VmBundleTask.BundleState.failed.equals( state ) && !VmBundleTask.BundleState.failed.equals( current ) && !VmBundleTask.BundleState.none.equals( current ) ) {
try{
deleteBucketContent(
vm.getOwnerAccountNumber( ),
currentTask.getBucket( ),
currentTask.getPrefix( ),
true );
}catch(final Exception ex){
LOG.error("After bundle failure, failed to delete the bucket", ex);
}
setBundleTaskState( currentTask, state );
bundleRestartInstance( currentTask );
} else if ( VmBundleTask.BundleState.cancelled.equals( state ) && !VmBundleTask.BundleState.cancelled.equals( current ) && !VmBundleTask.BundleState.none.equals( current ) ) {
try{
deleteBucketContent(
vm.getOwnerAccountNumber(),
vm.getRuntimeState( ).getBundleTask().getBucket( ),
vm.getRuntimeState( ).getBundleTask().getPrefix( ),
true );
}catch(final Exception ex){
LOG.error("After bundle cancellation, failed to delete the bucket", ex);
}
setBundleTaskState( currentTask, state );
bundleRestartInstance( currentTask );
} else if ( VmBundleTask.BundleState.canceling.equals( state ) || VmBundleTask.BundleState.canceling.equals( current ) ) {
//
} else if ( VmBundleTask.BundleState.pending.equals( current ) && !VmBundleTask.BundleState.none.equals( state ) ) {
setBundleTaskState( currentTask, state );
currentTask.setUpdateTime( new Date( ) );
EventRecord.here( VmRuntimeState.class, EventType.BUNDLE_TRANSITION, vm.getOwner( ).toString( ), "" + vm.getRuntimeState( ).getBundleTask() ).info( );
} else if ( VmBundleTask.BundleState.storing.equals( state ) ) {
setBundleTaskState( currentTask, state );
currentTask.setUpdateTime( new Date( ) );
EventRecord.here( VmRuntimeState.class, EventType.BUNDLE_TRANSITION, vm.getOwner( ).toString( ), "" + vm.getRuntimeState( ).getBundleTask() ).info( );
}
} else {
putPreviousTask( vm.getRuntimeState( ).getBundleTask( ) );
vm.getRuntimeState( ).setBundleTask( new VmBundleTask( vm, state.name(), new Date(), new Date(), 0, "unknown", "unknown", "unknown", "unknown" ) );
Logs.extreme( ).trace( "Unhandle bundle task state update: " + state );
}
}
public static Boolean cancelBundleTask( VmRuntimeState state ) {
if ( state.getBundleTask() != null ) {
setBundleTaskState( state.getBundleTask(), VmBundleTask.BundleState.canceling );
EventRecord.here( VmRuntimeState.class, EventType.BUNDLE_CANCELING, state.getVmInstance ().getOwner().toString( ), state.getBundleTask().getBundleId( ),
state.getVmInstance().getInstanceId( ),
"" + state.getBundleTask().getState( ) ).info( );
return true;
} else {
return false;
}
}
public static Boolean restartBundleTask( VmRuntimeState state ) {
if ( state.getBundleTask() != null ) {
setBundleTaskState( state.getBundleTask(), VmBundleTask.BundleState.none );
EventRecord.here( VmRuntimeState.class, EventType.BUNDLE_RESTART, state.getVmInstance().getOwner().toString( ), state.getBundleTask().getBundleId( ),
state.getVmInstance().getInstanceId( ),
"" + state.getBundleTask().getState( ) ).info( );
return true;
}
return false;
}
public static Boolean submittedBundleTask( VmRuntimeState state ) {
if ( state.getBundleTask() != null ) {
if ( VmBundleTask.BundleState.cancelled.equals( state.getBundleTaskState() ) ) {
EventRecord.here( VmRuntimeState.class, EventType.BUNDLE_CANCELLED, state.getVmInstance().getOwner().toString( ), state.getBundleTask().getBundleId( ),
state.getVmInstance().getInstanceId( ),
"" + state.getBundleTask().getState( ) ).info( );
resetBundleTask( state );
return true;
} else if ( state.getBundleTask().getState( ).ordinal( ) >= VmBundleTask.BundleState.storing.ordinal( ) ) {
setBundleTaskState( state.getBundleTask(), VmBundleTask.BundleState.storing );
EventRecord.here( VmRuntimeState.class, EventType.BUNDLE_STARTING,
state.getVmInstance().getOwner( ).toString( ),
state.getBundleTask( ).getBundleId(),
state.getVmInstance( ).getInstanceId( ),
"" + state.getBundleTask( ).getState( ) ).info();
return true;
}
}
return false;
}
public static Boolean startBundleTask( final VmBundleTask task ) {
final VmRuntimeState state = task.getVmInstance( ).getRuntimeState( );
if ( !state.isBundling( ) ) {
putPreviousTask( state.getBundleTask( ) );
state.setBundleTask( task );
return true;
} else {
if ( ( state.getBundleTask() != null )
&& ( VmBundleTask.BundleState.failed.equals( task.getState( ) ) || VmBundleTask.BundleState.canceling.equals( task.getState( ) ) || VmBundleTask.BundleState.cancelled.equals( task.getState( ) ) ) ) {
resetBundleTask( state );
state.setBundleTask( task );
return true;
} else {
return false;
}
}
}
private static VmBundleTask resetBundleTask( final VmRuntimeState state ) {
final VmBundleTask oldTask = state.getBundleTask( );
putPreviousTask(oldTask);
state.setBundleTask( null );
return oldTask;
}
public static class CancelBundleCallback extends MessageCallback<ClusterCancelBundleTaskType, ClusterCancelBundleTaskResponseType> {
private CancelBundleCallback( ClusterCancelBundleTaskType request ) {
super( request );
}
@Override
public void fire( ClusterCancelBundleTaskResponseType reply ) {
if ( !reply.get_return( ) ) {
LOG.info( "Attempt to CancelBundleTask for instance " + this.getRequest( ).getBundleId( ) + " has failed." );
} else {
EntityTransaction db = Entities.get( VmInstance.class );
try {
VmInstance vm = VmInstances.lookupByBundleId( this.getRequest( ).getBundleId( ) );
cancelBundleTask( vm.getRuntimeState() );
EventRecord.here( CancelBundleCallback.class, EventType.BUNDLE_CANCELLED, this.getRequest( ).toSimpleString( ), vm.getRuntimeState( ).getBundleTask( ).getBundleId( ),
vm.getInstanceId( ) ).info( );
db.commit( );
} catch ( Exception ex ) {
Logs.exhaust( ).error( ex, ex );
db.rollback( );
}
}
}
}
public static MessageCallback bundleRestartInstanceCallback( ClusterBundleRestartInstanceType request ) {
return new BundleRestartInstanceCallback( request );
}
public static class BundleRestartInstanceCallback extends MessageCallback<ClusterBundleRestartInstanceType, ClusterBundleRestartInstanceResponseType> {
private BundleRestartInstanceCallback( ClusterBundleRestartInstanceType request ) {
super( request );
}
@Override
public void fire( ClusterBundleRestartInstanceResponseType reply ) {
if ( !reply.get_return( ) ) {
LOG.info( "Attempt to restart bundle instance " + this.getRequest( ).getInstanceId( ) + " has failed." );
} else {
EntityTransaction db = Entities.get( VmInstance.class );
try {
VmInstance vm = VmInstances.lookup( this.getRequest( ).getInstanceId( ) );
restartBundleTask( vm.getRuntimeState() );
EventRecord.here( CancelBundleCallback.class, EventType.BUNDLE_RESTART, this.getRequest( ).toSimpleString( ), vm.getRuntimeState( ).getBundleTask( ).getBundleId( ),
vm.getInstanceId( ) ).info( );
db.commit( );
} catch ( Exception ex ) {
Logs.exhaust( ).error( ex, ex );
db.rollback( );
}
}
}
}
public static class BundleCallback extends MessageCallback<ClusterBundleInstanceType, ClusterBundleInstanceResponseType> {
private BundleCallback( ClusterBundleInstanceType request ) {
super( request );
}
@Override
public void fire( ClusterBundleInstanceResponseType reply ) {
EntityTransaction db = Entities.get( VmInstance.class );
try {
if ( !reply.get_return( ) ) {
LOG.info( "Attempt to bundle instance " + this.getRequest( ).getInstanceId( ) + " has failed." );
} else {
VmInstance vm = VmInstances.lookup( this.getRequest( ).getInstanceId( ) );
submittedBundleTask( vm.getRuntimeState() );
EventRecord.here( BundleCallback.class, EventType.BUNDLE_STARTED, this.getRequest( ).toSimpleString( ), "" + vm.getRuntimeState( ),
vm.getInstanceId( ) ).info( );
}
db.commit( );
} catch ( Exception ex ) {
Logs.exhaust( ).error( ex, ex );
db.rollback( );
}
}
}
public static Function<BundleTask, VmBundleTask> fromBundleTask( final VmInstance vm ) {
return new Function<BundleTask, VmBundleTask>( ) {
@Override
public VmBundleTask apply( final BundleTask input ) {
return new VmBundleTask( vm,
input.getState( ),
input.getStartTime( ),
input.getUpdateTime( ),
input.getProgress( ) != null
? Integer.parseInt( input.getProgress( ).replace( "%", "" ) )
: 0,
input.getBucket( ),
input.getPrefix( ),
input.getErrorMessage( ),
input.getErrorCode( ) );
}
};
}
public static Function<VmBundleTask, BundleTask> asBundleTask( ) {
return new Function<VmBundleTask, BundleTask>( ) {
@Override
public BundleTask apply( final VmBundleTask input ) {
return new BundleTask( input.getInstanceId( ),
input.getBundleId( ),
input.getState( ).name( ),
input.getStartTime( ),
input.getUpdateTime( ),
"" + input.getProgress( ),
input.getBucket( ),
input.getPrefix( ),
input.getErrorMessage( ),
input.getErrorCode( ) );
}
};
}
public static BundleTask transform( final VmBundleTask bundleTask ) {
return asBundleTask( ).apply( bundleTask );
}
public static VmBundleTask create( VmInstance v, String bucket, String prefix, String policy ) throws AuthException {
verifyPolicy( policy, bucket );
// TODO: this was removed to get bundle-instance to work we still need to resolve the
// permissions issue see EUCA-3665
//verifyBucket( bucket );
// verifyPrefix( prefix );
return VmBundleTask.create( v, bucket, prefix, policy );
}
private static void verifyPolicy( String policy, String bucketName ) {
/**
* GRZE:NOTE: why is there S3 specific stuff here? this is the ec2 implementation. policy check
* must happen in walrus not here.
**/
// check if the policy is not user-generated one
// "expiration": "2011-07-01T16:52:13","conditions": [{"bucket": "windowsbundle" },{"acl": "ec2-bundle-read" },["starts-with", "$key", "prefix"
int idxOpenBracket = policy.indexOf( "{" );
int idxClosingBracket = policy.lastIndexOf( "}" );
if ( idxOpenBracket < 0 || idxClosingBracket < 0 || idxOpenBracket >= idxClosingBracket )
throw new RuntimeException( "Custom policy is not acceptable for bundle instance" );
String bucketAndAcl = policy.substring( idxOpenBracket, idxClosingBracket - idxOpenBracket );
if ( !bucketAndAcl.contains( bucketName ) )
throw new RuntimeException( "Custom policy is not acceptable for bundle instance" );
if ( !bucketAndAcl.contains( "ec2-bundle-read" ) )
throw new RuntimeException( "Custom policy is not acceptable for bundle instance" );
}
static void checkAndCreateBucket( final User user, final String bucketName, final String prefix ) throws ComputeException {
try ( final EucaS3Client s3c =
EucaS3ClientFactory.getEucaS3Client( SecurityTokenAWSCredentialsProvider.forUserOrRole( user ) ) ) {
boolean foundBucket = false;
final List<Bucket> buckets = s3c.listBuckets( );
for( final Bucket bucket : buckets ) {
foundBucket = bucketName.equals( bucket.getName( ) );
if ( foundBucket ) {
if ( !Strings.isNullOrEmpty( prefix ) ) {
final ObjectListing objects = s3c.listObjects( bucketName, Strings.emptyToNull( prefix ) );
if ( !objects.getObjectSummaries( ).isEmpty( ) ) {
throw new ClientComputeException(
"InvalidParameterValue",
"Bucket prefix in use " + bucketName + "/" + prefix );
}
}
break;
}
}
if ( !foundBucket ) {
s3c.createBucket( bucketName );
}
} catch( final Exception ex ) {
Exceptions.findAndRethrow( ex, ComputeException.class );
if ( ex instanceof AmazonS3Exception && "BucketAlreadyExists".equals(((AmazonS3Exception)ex).getErrorCode( ) ) ) {
throw new ClientComputeException(
"InvalidParameterValue",
"Bucket already exists " + bucketName );
}
LOG.debug( "Unable to create bucket " + bucketName, ex);
throw new ComputeException( "InternalError", "Unable to create bucket " + bucketName );
}
}
static void deleteBucketContent( final String accountId,
final String bucketName,
final String prefix,
final boolean deleteEmptyBucket ) throws ComputeException {
try ( final EucaS3Client s3c =
EucaS3ClientFactory.getEucaS3Client( new SecurityTokenAWSCredentialsProvider( AccountFullName.getInstance( accountId ) ) ) ) {
final List<Bucket> buckets = s3c.listBuckets( );
boolean bucketFound = false;
for(final Bucket bucket : buckets){
if(bucketName.equals(bucket.getName())){
bucketFound=true;
break;
}
}
if( bucketFound ){
final ObjectListing objects = s3c.listObjects( bucketName, Strings.emptyToNull( prefix ) );
for( final S3ObjectSummary object : objects.getObjectSummaries( ) ){
s3c.deleteObject( bucketName, object.getKey( ) );
}
if ( deleteEmptyBucket ) {
final VersionListing versions = s3c.listVersions( bucketName, null );
if ( versions.getVersionSummaries( ).isEmpty( ) ) {
s3c.deleteBucket( bucketName );
}
}
}
} catch( final Exception ex ){
LOG.debug( "Error deleting bucket ("+bucketName+") " +
( Strings.isNullOrEmpty( prefix ) ? "" : "prefix ("+prefix+") " ) + "for account " + accountId, ex );
throw new ComputeException("InternalError", "Unable to delete the bucket");
}
}
}