/*******************************************************************************
*Copyright (c) 2009 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, only version 3 of the License.
*
*
* This file 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., 130 Castilian
* Dr., Goleta, CA 93101 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.
*******************************************************************************/
/*
* Author: chris grzegorczyk <grze@eucalyptus.com>
*/
package com.eucalyptus.vm;
import java.io.ByteArrayInputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.log4j.Logger;
import org.bouncycastle.util.encoders.Base64;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import com.eucalyptus.auth.util.Hashes;
import com.eucalyptus.bootstrap.Component;
import com.eucalyptus.cluster.Clusters;
import com.eucalyptus.cluster.NetworkAlreadyExistsException;
import com.eucalyptus.cluster.Networks;
import com.eucalyptus.cluster.VmInstance;
import com.eucalyptus.cluster.VmInstances;
import com.eucalyptus.cluster.callback.TerminateCallback;
import com.eucalyptus.config.ClusterConfiguration;
import com.eucalyptus.configurable.ConfigurableClass;
import com.eucalyptus.configurable.ConfigurableField;
import com.eucalyptus.entities.EntityWrapper;
import com.eucalyptus.entities.SshKeyPair;
import com.eucalyptus.images.Image;
import com.eucalyptus.images.ImageInfo;
import com.eucalyptus.images.ProductCode;
import com.eucalyptus.network.NetworkGroupUtil;
import com.eucalyptus.util.EucalyptusCloudException;
import com.eucalyptus.ws.client.RemoteDispatcher;
import com.eucalyptus.ws.util.Messaging;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import edu.ucsb.eucalyptus.cloud.Network;
import edu.ucsb.eucalyptus.cloud.NetworkToken;
import edu.ucsb.eucalyptus.cloud.VmDescribeResponseType;
import edu.ucsb.eucalyptus.cloud.VmImageInfo;
import edu.ucsb.eucalyptus.cloud.VmInfo;
import edu.ucsb.eucalyptus.cloud.VmKeyInfo;
import edu.ucsb.eucalyptus.cloud.entities.SystemConfiguration;
import edu.ucsb.eucalyptus.msgs.GetObjectResponseType;
import edu.ucsb.eucalyptus.msgs.GetObjectType;
import edu.ucsb.eucalyptus.msgs.ReservationInfoType;
import edu.ucsb.eucalyptus.msgs.VmTypeInfo;
@ConfigurableClass( root = "vmstate", description = "Parameters controlling the lifecycle of virtual machines." )
public class SystemState {
public static Logger LOG = Logger.getLogger( SystemState.class );
@ConfigurableField( description = "Amount of time (in milliseconds) that a terminated VM will continue to be reported.", initial = "" + 60 * 60 * 1000 )
public static Integer BURY_TIME = -1;
@ConfigurableField( description = "Amount of time (in milliseconds) before a VM which is not reported by a cluster will be marked as terminated.", initial = "" + 10 * 60 * 1000 )
public static Integer SHUT_DOWN_TIME = -1;
public enum Reason {
NORMAL( "" ),
EXPIRED( "Instance expired after not being reported for %s ms.", SystemState.SHUT_DOWN_TIME ),
FAILED( "The instance failed to start on the NC." ),
USER_TERMINATED( "User initiated terminate." ),
BURIED( "Instance buried after timeout of %s ms.", SystemState.BURY_TIME ),
APPEND( "" );
private String message;
private Object[] args;
Reason( String message, Object... args ) {
this.message = message;
this.args = args;
}
@Override
public String toString( ) {
return String.format( this.message.toString( ), this.args );
}
}
public static void handle( VmDescribeResponseType request ) {
VmInstances.flushBuried( );
String originCluster = request.getOriginCluster( );
for ( VmInfo runVm : request.getVms( ) ) {
SystemState.updateVmInstance( originCluster, runVm );
}
final Set<String> unreportedVms = VmInstances.getInstance( ).getKeys( );
final List<String> runningVmIds = Lists.transform( request.getVms( ), new Function<VmInfo, String>( ) {
@Override
public String apply( VmInfo arg0 ) {
String vmId = arg0.getImageId( );
unreportedVms.remove( vmId );
return vmId;
}
} );
for ( String vmId : unreportedVms ) {
try {
VmInstance vm = VmInstances.getInstance( ).lookup( vmId );
if ( vm.getSplitTime( ) > SHUT_DOWN_TIME ) {
vm.setState( VmState.TERMINATED, Reason.EXPIRED );
}
} catch ( NoSuchElementException e ) {}
}
}
private static void updateVmInstance( final String originCluster, final VmInfo runVm ) {
VmState state = VmState.Mapper.get( runVm.getStateName( ) );
VmInstance vm = null;
try {
vm = VmInstances.getInstance( ).lookup( runVm.getInstanceId( ) );
} catch ( NoSuchElementException e ) {
try {
vm = VmInstances.getInstance( ).lookupDisabled( runVm.getInstanceId( ) );
if ( !VmState.BURIED.equals( vm.getState( ) ) && vm.getSplitTime( ) > BURY_TIME ) {
vm.setState( VmState.BURIED, Reason.BURIED );
}
return;
} catch ( NoSuchElementException e1 ) {
if ( VmState.PENDING.equals( state ) || VmState.RUNNING.equals( state ) ) {
SystemState.restoreInstance( originCluster, runVm );
}
return;
}
}
long splitTime = vm.getSplitTime( );
VmState oldState = vm.getState( );
vm.setServiceTag( runVm.getServiceTag( ) );
if ( VmState.SHUTTING_DOWN.equals( vm.getState( ) ) && splitTime > SHUT_DOWN_TIME ) {
vm.setState( VmState.TERMINATED, Reason.EXPIRED );
} else if ( VmState.SHUTTING_DOWN.equals( vm.getState( ) ) && VmState.SHUTTING_DOWN.equals( VmState.Mapper.get( runVm.getStateName( ) ) ) ) {
vm.setState( VmState.TERMINATED, Reason.APPEND, "DONE" );
} else if ( ( VmState.PENDING.equals( state ) || VmState.RUNNING.equals( state ) ) && ( VmState.PENDING.equals( vm.getState( ) ) || VmState.RUNNING.equals( vm.getState( ) ) ) ) {
if( !VmInstance.DEFAULT_IP.equals( runVm.getNetParams( ).getIpAddress( ) ) ) {
vm.updateAddresses( runVm.getNetParams( ).getIpAddress( ), runVm.getNetParams( ).getIgnoredPublicIp( ) );
}
vm.setState( VmState.Mapper.get( runVm.getStateName( ) ), Reason.APPEND, "UPDATE" );
vm.updateNetworkIndex( runVm.getNetParams( ).getNetworkIndex( ) );
vm.setVolumes( runVm.getVolumes( ) );
try {
Network network = Networks.getInstance( ).lookup( runVm.getOwnerId( ) + "-" + runVm.getGroupNames( ).get( 0 ) );
network.extantNetworkIndex( vm.getPlacement( ), vm.getNetworkIndex( ) );
} catch ( Exception e ) {}
}
}
@Deprecated /** TODO: HACK HACK **/
private static String getWalrusUrl( ) throws EucalyptusCloudException {
try {
return SystemConfiguration.getWalrusUrl( ) + "/";
} catch ( Exception e ) {
LOG.debug( e, e );
throw new EucalyptusCloudException( "Walrus has not been configured.", e );
}
}
@Deprecated /** TODO: HACK HACK **/
private static String getImageUrl( String walrusUrl, final Image diskInfo ) throws EucalyptusCloudException {
try {
URL url = new URL( getWalrusUrl( ) + diskInfo.getImageLocation( ) );
return url.toString( );
} catch ( MalformedURLException e ) {
throw new EucalyptusCloudException( "Failed to parse image location as URL.", e );
}
}
@Deprecated /** TODO: HACK HACK **/
private static VmImageInfo resolveImage( VmInfo vmInfo ) throws EucalyptusCloudException {
String walrusUrl = getWalrusUrl( );
ArrayList<String> productCodes = Lists.newArrayList( );
ImageInfo diskInfo = null, kernelInfo = null, ramdiskInfo = null;
String diskUrl = null, kernelUrl = null, ramdiskUrl = null;
EntityWrapper<ImageInfo> db = new EntityWrapper<ImageInfo>( );
try {
diskInfo = db.getUnique( new ImageInfo( vmInfo.getImageId( ) ) );
for ( ProductCode p : diskInfo.getProductCodes( ) ) {
productCodes.add( p.getValue( ) );
}
diskUrl = getImageUrl( walrusUrl, diskInfo );
db.commit( );
} catch ( EucalyptusCloudException e ) {
db.rollback( );
}
VmImageInfo vmImgInfo = new VmImageInfo( vmInfo.getImageId( ), vmInfo.getKernelId( ), vmInfo.getRamdiskId( ), diskUrl, null, null, productCodes );
if( Component.walrus.isLocal( ) ) {
ArrayList<String> ancestorIds = getAncestors( vmInfo.getOwnerId( ), diskInfo.getImageLocation( ) );
vmImgInfo.setAncestorIds( ancestorIds );
} else {//FIXME: handle populating these in a defered way for the remote case.
vmImgInfo.setAncestorIds( new ArrayList<String>() );
}
return vmImgInfo;
}
public static ArrayList<String> getAncestors( String userId, String manifestPath ) {
ArrayList<String> ancestorIds = Lists.newArrayList( );
try {
String[] imagePathParts = manifestPath.split( "/" );
String bucketName = imagePathParts[0];
String objectName = imagePathParts[1];
GetObjectResponseType reply = null;
try {
GetObjectType msg = new GetObjectType( bucketName, objectName, true, false, true );
msg.setUserId( userId );
reply = ( GetObjectResponseType ) RemoteDispatcher.lookupSingle( Component.walrus ).send( msg );
}
catch ( Exception e ) {
throw new EucalyptusCloudException( "Failed to read manifest file: " + bucketName + "/" + objectName, e );
}
Document inputSource = null;
try {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
inputSource = builder.parse( new ByteArrayInputStream( Hashes.base64decode(reply.getBase64Data() ).getBytes() ));
}
catch ( Exception e ) {
throw new EucalyptusCloudException( "Failed to read manifest file: " + bucketName + "/" + objectName, e );
}
XPath xpath = XPathFactory.newInstance( ).newXPath( );
NodeList ancestors = null;
try {
ancestors = ( NodeList ) xpath.evaluate( "/manifest/image/ancestry/ancestor_ami_id/text()", inputSource, XPathConstants.NODESET );
if ( ancestors == null ) return ancestorIds;
for ( int i = 0; i < ancestors.getLength( ); i++ ) {
for ( String ancestorId : ancestors.item( i ).getNodeValue( ).split( "," ) ) {
ancestorIds.add( ancestorId );
}
}
} catch ( XPathExpressionException e ) {
LOG.error( e, e );
}
} catch ( EucalyptusCloudException e ) {
LOG.error( e, e );
} catch ( DOMException e ) {
LOG.error( e, e );
}
return ancestorIds;
}
private static void restoreInstance( final String cluster, final VmInfo runVm ) {
try {
String instanceId = runVm.getInstanceId( );
String reservationId = runVm.getReservationId( );
String ownerId = runVm.getOwnerId( );
String placement = cluster;
byte[] userData = new byte[0];
if( runVm.getUserData( ) != null && runVm.getUserData( ).length( ) > 1 ) {
userData = Base64.decode( runVm.getUserData( ) );
}
Integer launchIndex = 0;
try {
launchIndex = Integer.parseInt( runVm.getLaunchIndex( ) );
} catch ( NumberFormatException e ) {}
VmImageInfo imgInfo = null;
//FIXME: really need to populate these asynchronously for multi-cluster/split component...
try {
imgInfo = resolveImage( runVm );
} catch ( EucalyptusCloudException e ) {
imgInfo = new VmImageInfo( runVm.getImageId( ), runVm.getKernelId( ), runVm.getRamdiskId( ), null, null, null, null );
}
VmKeyInfo keyInfo = null;
SshKeyPair key = null;
if ( runVm.getKeyValue( ) != null || !"".equals( runVm.getKeyValue( ) ) ) {
try {
EntityWrapper<SshKeyPair> db = EntityWrapper.get( SshKeyPair.class );
try {
SshKeyPair searchKey = new SshKeyPair( runVm.getOwnerId( ) ) {
{
setPublicKey( runVm.getKeyValue( ) );
}
};
key = db.getUnique( searchKey );
db.commit( );
} catch ( Throwable e ) {
db.rollback( );
throw new EucalyptusCloudException( "Failed to find key pair associated with public key " + runVm.getKeyValue( ), e );
}
} catch ( Throwable e ) {
key = SshKeyPair.NO_KEY;
}
} else {
key = SshKeyPair.NO_KEY;
}
keyInfo = new VmKeyInfo( key.getDisplayName( ), key.getPublicKey( ), key.getFingerPrint( ) );
VmTypeInfo vmType = runVm.getInstanceType( );
List<Network> networks = new ArrayList<Network>( );
for ( String netName : runVm.getGroupNames( ) ) {
Network notwork = null;
try {
notwork = Networks.getInstance( ).lookup( runVm.getOwnerId( ) + "-" + netName );
networks.add( notwork );
try {
NetworkToken netToken = Clusters.getInstance( ).lookup( runVm.getPlacement( ) ).getState( ).extantAllocation( runVm.getOwnerId( ), netName,
runVm.getNetParams( ).getVlan( ) );
notwork.addTokenIfAbsent( netToken );
} catch ( NetworkAlreadyExistsException e ) {
LOG.trace( e );
}
notwork.extantNetworkIndex( runVm.getPlacement( ), runVm.getNetParams( ).getNetworkIndex( ) );
} catch ( NoSuchElementException e1 ) {
try {
try {
notwork = SystemState.getUserNetwork( runVm.getOwnerId( ), netName );
} catch ( Exception ex ) {
LOG.error( ex );
notwork = SystemState.getUserNetwork( runVm.getOwnerId( ), "default" );
}
networks.add( notwork );
NetworkToken netToken = Clusters.getInstance( ).lookup( runVm.getPlacement( ) ).getState( ).extantAllocation( runVm.getOwnerId( ), netName,
runVm.getNetParams( ).getVlan( ) );
notwork.addTokenIfAbsent( netToken );
Networks.getInstance( ).registerIfAbsent( notwork, Networks.State.ACTIVE );
} catch ( EucalyptusCloudException e ) {
LOG.error( e );
ClusterConfiguration config = Clusters.getInstance( ).lookup( runVm.getPlacement( ) ).getConfiguration( );
new TerminateCallback( runVm.getInstanceId( ) ).dispatch( runVm.getPlacement( ) );
} catch ( NetworkAlreadyExistsException e ) {
LOG.trace( e );
}
}
}
VmInstance vm = new VmInstance( reservationId, launchIndex, instanceId, ownerId, placement, userData, imgInfo, keyInfo, vmType, networks,
Integer.toString( runVm.getNetParams( ).getNetworkIndex( ) ) );
vm.clearPending( );
vm.setLaunchTime( runVm.getLaunchTime( ) );
vm.updatePublicAddress( VmInstance.DEFAULT_IP );
vm.setKeyInfo( keyInfo );
vm.setImageInfo( imgInfo );
VmInstances.getInstance( ).register( vm );
} catch ( NoSuchElementException e ) {
ClusterConfiguration config = Clusters.getInstance( ).lookup( runVm.getPlacement( ) ).getConfiguration( );
new TerminateCallback( runVm.getInstanceId( ) ).dispatch( runVm.getPlacement( ) );
} catch ( Throwable t ) {
LOG.error( t, t );
}
}
private static String DESCRIBE_NO_DNS = "no-dns";
private static String ALT_PREFIX = "i-";
public static ArrayList<ReservationInfoType> handle( String userId, List<String> instancesSet, boolean isAdmin ) throws Exception {
Map<String, ReservationInfoType> rsvMap = new HashMap<String, ReservationInfoType>( );
boolean dns = Component.dns.isLocal( ) && !( instancesSet.remove( DESCRIBE_NO_DNS ) || instancesSet.remove( ALT_PREFIX + DESCRIBE_NO_DNS ) );
for ( VmInstance v : VmInstances.getInstance( ).listValues( ) ) {
if ( ( !isAdmin && !userId.equals( v.getOwnerId( ) ) || ( !instancesSet.isEmpty( ) && !instancesSet.contains( v.getInstanceId( ) ) ) ) ) continue;
if ( rsvMap.get( v.getReservationId( ) ) == null ) {
ReservationInfoType reservation = new ReservationInfoType( v.getReservationId( ), v.getOwnerId( ), v.getNetworkNames( ) );
rsvMap.put( reservation.getReservationId( ), reservation );
}
rsvMap.get( v.getReservationId( ) ).getInstancesSet( ).add( v.getAsRunningInstanceItemType( dns ) );
}
if ( isAdmin ) {
for ( VmInstance v : VmInstances.getInstance( ).listDisabledValues( ) ) {
if ( VmState.BURIED.equals( v.getState( ) ) ) continue;
if ( !instancesSet.isEmpty( ) && !instancesSet.contains( v.getInstanceId( ) ) ) continue;
if ( rsvMap.get( v.getReservationId( ) ) == null ) {
ReservationInfoType reservation = new ReservationInfoType( v.getReservationId( ), v.getOwnerId( ), v.getNetworkNames( ) );
rsvMap.put( reservation.getReservationId( ), reservation );
}
rsvMap.get( v.getReservationId( ) ).getInstancesSet( ).add( v.getAsRunningInstanceItemType( dns ) );
}
}
return new ArrayList<ReservationInfoType>( rsvMap.values( ) );
}
public static Network getUserNetwork( String userId, String networkName ) throws EucalyptusCloudException {
try {
return NetworkGroupUtil.getUserNetworkRulesGroup( userId, networkName ).getVmNetwork( );
} catch ( Exception e ) {
throw new EucalyptusCloudException( "Failed to find network: " + userId + "-" + networkName );
}
}
}