/*******************************************************************************
*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.cluster;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.atomic.AtomicMarkableReference;
import org.apache.commons.lang.time.StopWatch;
import org.apache.log4j.Logger;
import org.bouncycastle.util.encoders.Base64;
import com.eucalyptus.bootstrap.Component;
import com.eucalyptus.component.Configurations;
import com.eucalyptus.records.EventClass;
import com.eucalyptus.records.EventRecord;
import com.eucalyptus.records.EventType;
import com.eucalyptus.util.HasName;
import com.eucalyptus.util.LogUtil;
import com.eucalyptus.vm.SystemState;
import com.eucalyptus.vm.VmState;
import com.eucalyptus.vm.SystemState.Reason;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import edu.ucsb.eucalyptus.cloud.Network;
import edu.ucsb.eucalyptus.cloud.VmImageInfo;
import edu.ucsb.eucalyptus.cloud.VmKeyInfo;
import edu.ucsb.eucalyptus.msgs.AttachedVolume;
import edu.ucsb.eucalyptus.msgs.NetworkConfigType;
import edu.ucsb.eucalyptus.msgs.RunningInstancesItemType;
import edu.ucsb.eucalyptus.msgs.VmTypeInfo;
public class VmInstance implements HasName<VmInstance> {
private static Logger LOG = Logger.getLogger( VmInstance.class );
public static String DEFAULT_IP = "0.0.0.0";
public static String DEFAULT_TYPE = "m1.small";
private final String reservationId;
private final int launchIndex;
private final String instanceId;
private final String ownerId;
private final String placement;
private final byte[] userData;
private final List<Network> networks = Lists.newArrayList( );
private final NetworkConfigType networkConfig = new NetworkConfigType( );
private VmImageInfo imageInfo;
private VmKeyInfo keyInfo;
private VmTypeInfo vmTypeInfo;
private final AtomicMarkableReference<VmState> state = new AtomicMarkableReference<VmState>( VmState.PENDING, false );
private final ConcurrentSkipListSet<AttachedVolume> volumes = new ConcurrentSkipListSet<AttachedVolume>( );
private final StopWatch stopWatch = new StopWatch( );
private Date launchTime = new Date( );
private String serviceTag;
private SystemState.Reason reason;
private final List<String> reasonDetails = Lists.newArrayList( );
private StringBuffer consoleOutput = new StringBuffer( );
private String passwordData;
private Boolean privateNetwork;
public VmInstance( final String reservationId, final int launchIndex, final String instanceId, final String ownerId, final String placement,
final byte[] userData, final VmImageInfo imageInfo, final VmKeyInfo keyInfo, final VmTypeInfo vmTypeInfo, final List<Network> networks,
final String networkIndex ) {
this.reservationId = reservationId;
this.launchIndex = launchIndex;
this.instanceId = instanceId;
this.ownerId = ownerId;
this.placement = placement;
this.userData = userData;
this.imageInfo = imageInfo;
this.keyInfo = keyInfo;
this.vmTypeInfo = vmTypeInfo;
this.networks.addAll( networks );
this.networkConfig.setMacAddress( "d0:0d:" + VmInstances.asMacAddress( this.instanceId ) );
this.networkConfig.setIpAddress( DEFAULT_IP );
this.networkConfig.setIgnoredPublicIp( DEFAULT_IP );
this.networkConfig.setNetworkIndex( Integer.parseInt( networkIndex ) );
this.stopWatch.start( );
this.updateDns( );
}
public void updateNetworkIndex( Integer newIndex ) {
if ( this.getNetworkConfig( ).getNetworkIndex( ) > 0 && newIndex > 0
&& ( VmState.RUNNING.equals( this.getState( ) ) || VmState.PENDING.equals( this.getState( ) ) ) ) {
this.getNetworkConfig( ).setNetworkIndex( newIndex );
}
}
public void updateAddresses( String privateAddr, String publicAddr ) {
this.updatePrivateAddress( privateAddr );
this.updatePublicAddress( publicAddr );
}
public void updatePublicAddress( String publicAddr ) {
if ( !VmInstance.DEFAULT_IP.equals( publicAddr ) && !"".equals( publicAddr )
&& publicAddr != null ) {
this.getNetworkConfig( ).setIgnoredPublicIp( publicAddr );
}
}
private void updateDns( ) {
String dnsDomain = "dns-disabled";
try {
dnsDomain = edu.ucsb.eucalyptus.cloud.entities.SystemConfiguration.getSystemConfiguration( ).getDnsDomain( );
} catch ( Exception e ) {}
this.getNetworkConfig( ).updateDns( dnsDomain );
}
public void updatePrivateAddress( String privateAddr ) {
if ( !VmInstance.DEFAULT_IP.equals( privateAddr ) && !"".equals( privateAddr ) && privateAddr != null ) {
this.getNetworkConfig( ).setIpAddress( privateAddr );
}
updateDns( );
}
public boolean clearPending( ) {
if ( this.state.isMarked( ) && this.getState( ).ordinal( ) > VmState.RUNNING.ordinal( ) ) {
this.state.set( this.getState( ), false );
VmInstances.cleanUp( this );
return true;
} else {
this.state.set( this.getState( ), false );
return false;
}
}
public VmState getState( ) {
return this.state.getReference( );
}
public void setState( final VmState state ) {
this.setState( state, SystemState.Reason.NORMAL );
}
public String getReason( ) {
if ( this.reason == null ) {
this.reason = Reason.NORMAL;
}
return this.reason.name( ) + ": " + this.reason + ( this.reasonDetails != null ? " -- " + this.reasonDetails : "" );
}
private int stateCounter = 0;
private static String SEND_USER_TERMINATE = "SIGTERM";
private void addReasonDetail( String... extra ) {
for ( String s : extra ) {
this.reasonDetails.add( s );
}
}
public void setState( final VmState newState, SystemState.Reason reason, String... extra ) {
this.resetStopWatch( );
VmState oldState = this.state.getReference( );
if ( VmState.SHUTTING_DOWN.equals( newState ) && VmState.SHUTTING_DOWN.equals( oldState ) && Reason.USER_TERMINATED.equals( reason ) ) {
VmInstances.cleanUp( this );
if ( !this.reasonDetails.contains( SEND_USER_TERMINATE ) ) {
this.addReasonDetail( SEND_USER_TERMINATE );
}
} else if ( VmState.TERMINATED.equals( newState ) && VmState.TERMINATED.equals( oldState ) ) {
VmInstances.getInstance( ).deregister( this.getName( ) );
} else if ( !this.getState( ).equals( newState ) ) {
if ( Reason.APPEND.equals( reason ) ) {
reason = this.reason;
}
this.addReasonDetail( extra );
LOG.info( String.format( "%s state change: %s -> %s", this.getInstanceId( ), this.getState( ), newState ) );
this.reason = reason;
if ( this.state.isMarked( ) && VmState.PENDING.equals( this.getState( ) ) ) {
if ( VmState.SHUTTING_DOWN.equals( newState ) || VmState.PENDING.equals( newState ) ) {
this.state.set( newState, true );
} else {
this.state.set( newState, false );
}
} else if ( this.state.isMarked( ) && VmState.SHUTTING_DOWN.equals( this.getState( ) ) ) {
LOG.debug( "Ignoring events for state transition because the instance is marked as pending: " + oldState + " to " + this.getState( ) );
} else if ( !this.state.isMarked( ) ) {
if ( oldState.ordinal( ) <= VmState.RUNNING.ordinal( ) && newState.ordinal( ) > VmState.RUNNING.ordinal( ) ) {
this.state.set( newState, false );
VmInstances.cleanUp( this );
} else if ( VmState.PENDING.equals( oldState ) && VmState.RUNNING.equals( newState ) ) {
this.state.set( newState, false );
} else if ( VmState.TERMINATED.equals( newState ) && oldState.ordinal( ) <= VmState.RUNNING.ordinal( ) ) {
this.state.set( newState, false );
VmInstances.getInstance( ).disable( this.getName( ) );
VmInstances.cleanUp( this );
} else if ( VmState.TERMINATED.equals( newState ) && oldState.ordinal( ) > VmState.RUNNING.ordinal( ) ) {
this.state.set( newState, false );
VmInstances.getInstance( ).disable( this.getName( ) );
} else if ( oldState.ordinal( ) > VmState.RUNNING.ordinal( ) && newState.ordinal( ) <= VmState.RUNNING.ordinal( ) ) {
this.state.set( oldState, false );
VmInstances.cleanUp( this );
} else if ( newState.ordinal( ) > oldState.ordinal( ) ) {
this.state.set( newState, false );
}
EventRecord.here( VmInstance.class, EventClass.VM, EventType.VM_STATE, "user="+this.getOwnerId( ), "instance="+this.getInstanceId( ), "type="+this.getVmTypeInfo( ).getName( ), "state="+ this.state.getReference( ).name( ), "details="+this.reasonDetails.toString( ) ).info();
} else {
LOG.debug( "Ignoring events for state transition because the instance is marked as pending: " + oldState + " to " + this.getState( ) );
}
if ( !this.getState( ).equals( oldState ) ) {
EventRecord.caller( VmInstance.class, EventType.VM_STATE, this.instanceId, this.ownerId, this.state.getReference( ).name( ), this.launchTime );
}
}
}
public String getByKey( String path ) {
Map<String, String> m = getMetadataMap( );
if ( path == null ) path = "";
LOG.debug( "Servicing metadata request:" + path + " -> " + m.get( path ) );
if ( m.containsKey( path + "/" ) ) path += "/";
return m.get( path ).replaceAll( "\n*\\z", "" );
}
private Map<String, String> getMetadataMap( ) {
Map<String, String> m = new HashMap<String, String>( );
m.put( "ami-id", this.getImageInfo( ).getImageId( ) );
m.put( "product-codes", this.getImageInfo( ).getProductCodes( ).toString( ).replaceAll( "[\\Q[]\\E]", "" ).replaceAll( ", ", "\n" ) );
m.put( "ami-launch-index", "" + this.getLaunchIndex( ) );
m.put( "ancestor-ami-ids", this.getImageInfo( ).getAncestorIds( ).toString( ).replaceAll( "[\\Q[]\\E]", "" ).replaceAll( ", ", "\n" ) );
m.put( "ami-manifest-path", this.getImageInfo( ).getImageLocation( ) );
m.put( "hostname", this.getPublicAddress( ) );
m.put( "instance-id", this.getInstanceId( ) );
m.put( "instance-type", this.getVmTypeInfo( ).getName( ) );
if ( Component.dns.isLocal( ) ) {
m.put( "local-hostname", this.getNetworkConfig( ).getPrivateDnsName( ) );
} else {
m.put( "local-hostname", this.getNetworkConfig( ).getIpAddress( ) );
}
m.put( "local-ipv4", this.getNetworkConfig( ).getIpAddress( ) );
if ( Component.dns.isLocal( ) ) {
m.put( "public-hostname", this.getNetworkConfig( ).getPublicDnsName( ) );
} else {
m.put( "public-hostname", this.getPublicAddress( ) );
}
m.put( "public-ipv4", this.getPublicAddress( ) );
m.put( "reservation-id", this.getReservationId( ) );
m.put( "kernel-id", this.getImageInfo( ).getKernelId( ) );
if ( this.getImageInfo( ).getRamdiskId( ) != null ) {
m.put( "ramdisk-id", this.getImageInfo( ).getRamdiskId( ) );
}
m.put( "security-groups", this.getNetworkNames( ).toString( ).replaceAll( "[\\Q[]\\E]", "" ).replaceAll( ", ", "\n" ) );
m.put( "block-device-mapping/", "emi\nephemeral\nephemeral0\nroot\nswap" );
m.put( "block-device-mapping/emi", "sda1" );
m.put( "block-device-mapping/ami", "sda1" );
m.put( "block-device-mapping/ephemeral", "sda2" );
m.put( "block-device-mapping/ephemeral0", "sda2" );
m.put( "block-device-mapping/swap", "sda3" );
m.put( "block-device-mapping/root", "/dev/sda1" );
m.put( "public-keys/", "0=" + this.getKeyInfo( ).getName( ) );
m.put( "public-keys/0", "openssh-key" );
m.put( "public-keys/0/", "openssh-key" );
m.put( "public-keys/0/openssh-key", this.getKeyInfo( ).getValue( ) );
m.put( "placement/", "availability-zone" );
m.put( "placement/availability-zone", this.getPlacement( ) );
String dir = "";
for ( String entry : m.keySet( ) ) {
if ( ( entry.contains( "/" ) && !entry.endsWith( "/" ) )
|| ( "ramdisk-id".equals(entry) && this.getImageInfo( ).getRamdiskId( ) == null ) ) {
continue;
}
dir += entry + "\n";
}
m.put( "", dir );
return m;
}
@Override
public int compareTo( final VmInstance that ) {
return this.getName( ).compareTo( that.getName( ) );
}
public synchronized long resetStopWatch( ) {
this.stopWatch.stop( );
long ret = this.stopWatch.getTime( );
this.stopWatch.reset( );
this.stopWatch.start( );
return ret;
}
public synchronized long getSplitTime( ) {
this.stopWatch.split( );
long ret = this.stopWatch.getSplitTime( );
this.stopWatch.unsplit( );
return ret;
}
public RunningInstancesItemType getAsRunningInstanceItemType( boolean dns ) {
RunningInstancesItemType runningInstance = new RunningInstancesItemType( );
runningInstance.setAmiLaunchIndex( Integer.toString( this.launchIndex ) );
runningInstance.setStateCode( Integer.toString( this.state.getReference( ).getCode( ) ) );
runningInstance.setStateName( this.state.getReference( ).getName( ) );
runningInstance.setInstanceId( this.instanceId );
runningInstance.setImageId( this.imageInfo.getImageId( ) );
runningInstance.setKernel( this.imageInfo.getKernelId( ) );
runningInstance.setRamdisk( this.imageInfo.getRamdiskId( ) );
runningInstance.setProductCodes( this.imageInfo.getProductCodes( ) );
if ( dns ) {
runningInstance.setDnsName( this.getNetworkConfig( ).getPublicDnsName( ) );
runningInstance.setPrivateDnsName( this.getNetworkConfig( ).getPrivateDnsName( ) );
} else {
runningInstance.setPrivateDnsName( this.getNetworkConfig( ).getIpAddress( ) );
if ( !VmInstance.DEFAULT_IP.equals( this.getPublicAddress( ) ) ) {
runningInstance.setDnsName( this.getPublicAddress( ) );
} else {
runningInstance.setDnsName( this.getNetworkConfig( ).getIpAddress( ) );
}
}
runningInstance.setReason( this.getReason( ) );
if ( this.getKeyInfo( ) != null )
runningInstance.setKeyName( this.getKeyInfo( ).getName( ) );
else runningInstance.setKeyName( "" );
runningInstance.setInstanceType( this.getVmTypeInfo( ).getName( ) );
runningInstance.setPlacement( this.placement );
runningInstance.setLaunchTime( this.launchTime );
return runningInstance;
}
public void setServiceTag( String serviceTag ) {
this.serviceTag = serviceTag;
}
public String getServiceTag( ) {
return serviceTag;
}
public boolean hasPublicAddress( ) {
NetworkConfigType conf = getNetworkConfig( );
return conf != null && !( DEFAULT_IP.equals( conf.getIgnoredPublicIp( ) ) || conf.getIpAddress( ).equals( conf.getIgnoredPublicIp( ) ) );
}
public String getName( ) {
return this.instanceId;
}
public void setLaunchTime( final Date launchTime ) {
this.launchTime = launchTime;
}
public String getReservationId( ) {
return reservationId;
}
public String getInstanceId( ) {
return instanceId;
}
public String getOwnerId( ) {
return ownerId;
}
public int getLaunchIndex( ) {
return launchIndex;
}
public String getPlacement( ) {
return placement;
}
public Date getLaunchTime( ) {
return launchTime;
}
public byte[] getUserData( ) {
return userData;
}
public VmKeyInfo getKeyInfo( ) {
return keyInfo;
}
public String getConsoleOutputString( ) {
return new String( Base64.encode( this.consoleOutput.toString( ).getBytes( ) ) );
}
public StringBuffer getConsoleOutput( ) {
return this.consoleOutput;
}
public void setConsoleOutput( final StringBuffer consoleOutput ) {
this.consoleOutput = consoleOutput;
if ( this.passwordData == null ) {
String tempCo = consoleOutput.toString( ).replaceAll( "[\r\n]*", "" );
if ( tempCo.matches( ".*<Password>[\\w=+/]*</Password>.*" ) ) {
this.passwordData = tempCo.replaceAll( ".*<Password>", "" ).replaceAll( "</Password>.*", "" );
}
}
}
public void setKeyInfo( final VmKeyInfo keyInfo ) {
this.keyInfo = keyInfo;
}
public VmTypeInfo getVmTypeInfo( ) {
return vmTypeInfo;
}
public void setVmTypeInfo( final VmTypeInfo vmTypeInfo ) {
this.vmTypeInfo = vmTypeInfo;
}
public List<Network> getNetworks( ) {
return networks;
}
public List<String> getNetworkNames( ) {
List<String> nets = new ArrayList<String>( );
for ( Network net : this.getNetworks( ) )
nets.add( net.getNetworkName( ) );
return nets;
}
public String getPrivateAddress( ) {
return networkConfig.getIpAddress( );
}
public String getPublicAddress( ) {
return networkConfig.getIgnoredPublicIp( );
}
public NetworkConfigType getNetworkConfig( ) {
return networkConfig;
}
public VmImageInfo getImageInfo( ) {
return imageInfo;
}
public void setImageInfo( final VmImageInfo imageInfo ) {
this.imageInfo = imageInfo;
}
public void updateVolumeState( final String volumeId, String state ) {
AttachedVolume v = Iterables.find( this.volumes, new Predicate<AttachedVolume>( ) {
@Override
public boolean apply( AttachedVolume arg0 ) {
return arg0.getVolumeId( ).equals( volumeId );
}
} );
v.setStatus( state );
}
public NavigableSet<AttachedVolume> getVolumes( ) {
return this.volumes;
}
public void setVolumes( final List<AttachedVolume> newVolumes ) {
for ( AttachedVolume vol : newVolumes ) {
vol.setInstanceId( this.getInstanceId( ) );
vol.setStatus( "attached" );
}
Set<AttachedVolume> oldVolumes = Sets.newHashSet( this.getVolumes( ) );
this.volumes.retainAll( volumes );
this.volumes.addAll( volumes );
for ( AttachedVolume v : oldVolumes ) {
if ( "attaching".equals( v.getStatus( ) ) && !this.volumes.contains( v ) ) {
this.volumes.add( v );
}
}
}
public String getPasswordData( ) {
return this.passwordData;
}
public void setPasswordData( String passwordData ) {
this.passwordData = passwordData;
}
@Override
public boolean equals( Object o ) {
if ( this == o ) return true;
if ( o == null || getClass( ) != o.getClass( ) ) return false;
VmInstance vmInstance = ( VmInstance ) o;
if ( !instanceId.equals( vmInstance.instanceId ) ) return false;
return true;
}
@Override
public int hashCode( ) {
return instanceId.hashCode( );
}
@Override
public String toString( ) {
return String
.format(
"VmInstance [imageInfo=%s, instanceId=%s, keyInfo=%s, launchIndex=%s, launchTime=%s, networkConfig=%s, networks=%s, ownerId=%s, placement=%s, privateNetwork=%s, reason=%s, reservationId=%s, state=%s, stopWatch=%s, userData=%s, vmTypeInfo=%s, volumes=%s]",
this.imageInfo, this.instanceId, this.keyInfo, this.launchIndex, this.launchTime, this.networkConfig, this.networks, this.ownerId,
this.placement, this.privateNetwork, this.reason, this.reservationId, this.state, this.stopWatch, this.userData, this.vmTypeInfo,
this.volumes );
}
public int getNetworkIndex( ) {
return this.getNetworkConfig( ).getNetworkIndex( );
}
}