/*******************************************************************************
*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.sla;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import org.apache.log4j.Logger;
import com.eucalyptus.address.Address;
import com.eucalyptus.address.AddressCategory;
import com.eucalyptus.address.Addresses;
import com.eucalyptus.cluster.Cluster;
import com.eucalyptus.cluster.ClusterThreadFactory;
import com.eucalyptus.cluster.Clusters;
import com.eucalyptus.cluster.Networks;
import com.eucalyptus.cluster.NoSuchTokenException;
import com.eucalyptus.cluster.StatefulMessageSet;
import com.eucalyptus.cluster.SuccessCallback;
import com.eucalyptus.cluster.VmInstance;
import com.eucalyptus.cluster.VmInstances;
import com.eucalyptus.cluster.callback.ConfigureNetworkCallback;
import com.eucalyptus.cluster.callback.QueuedEventCallback;
import com.eucalyptus.cluster.callback.StartNetworkCallback;
import com.eucalyptus.cluster.callback.VmRunCallback;
import com.eucalyptus.records.EventType;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.vm.SystemState;
import com.eucalyptus.vm.VmState;
import com.eucalyptus.vm.SystemState.Reason;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import edu.ucsb.eucalyptus.cloud.Network;
import edu.ucsb.eucalyptus.cloud.NetworkToken;
import edu.ucsb.eucalyptus.cloud.ResourceToken;
import edu.ucsb.eucalyptus.cloud.VmAllocationInfo;
import edu.ucsb.eucalyptus.cloud.VmImageInfo;
import edu.ucsb.eucalyptus.cloud.VmInfo;
import edu.ucsb.eucalyptus.cloud.VmKeyInfo;
import edu.ucsb.eucalyptus.cloud.VmRunResponseType;
import edu.ucsb.eucalyptus.cloud.VmRunType;
import com.eucalyptus.records.EventRecord;
import edu.ucsb.eucalyptus.msgs.BaseMessage;
import edu.ucsb.eucalyptus.msgs.RunInstancesType;
import edu.ucsb.eucalyptus.msgs.VmTypeInfo;
public class ClusterAllocator extends Thread {
private static Logger LOG = Logger.getLogger( ClusterAllocator.class );
enum State {
START, CREATE_NETWORK, CREATE_NETWORK_RULES, CREATE_VMS, ASSIGN_ADDRESSES, FINISHED, ROLLBACK;
}
public static Boolean SPLIT_REQUESTS = true;
private StatefulMessageSet<State> messages;
private Cluster cluster;
private VmAllocationInfo vmAllocInfo;
public static void create( ResourceToken t, VmAllocationInfo vmAllocInfo ) {
ClusterThreadFactory.getThreadFactory( t.getCluster( ) ).newThread( new ClusterAllocator( t, vmAllocInfo ) ).start( );
}
private ClusterAllocator( ResourceToken vmToken, VmAllocationInfo vmAllocInfo ) {
this.vmAllocInfo = vmAllocInfo;
if ( vmToken != null ) {
try {
this.cluster = Clusters.getInstance( ).lookup( vmToken.getCluster( ) );
this.messages = new StatefulMessageSet<State>( cluster, State.values( ) );
for ( NetworkToken networkToken : vmToken.getNetworkTokens( ) )
this.setupNetworkMessages( networkToken );
this.setupVmMessages( vmToken );
} catch ( Throwable e ) {
LOG.debug( e, e );
try {
Clusters.getInstance( ).lookup( vmToken.getCluster( ) ).getNodeState( ).releaseToken( vmToken );
} catch ( Throwable e1 ) {
LOG.debug( e1 );
LOG.trace( e1, e1 );
}
for ( String addr : vmToken.getAddresses( ) ) {
try {
Addresses.release( Addresses.getInstance( ).lookup( addr ) );
} catch ( Throwable e1 ) {
LOG.debug( e1 );
LOG.trace( e1, e1 );
}
}
try {
if ( vmToken.getPrimaryNetwork( ) != null ) {
Network net = Networks.getInstance( ).lookup( vmToken.getPrimaryNetwork( ).getName( ) );
for ( Integer i : vmToken.getPrimaryNetwork( ).getIndexes( ) ) {
net.returnNetworkIndex( i );
}
}
} catch ( Throwable e1 ) {
LOG.debug( e1 );
LOG.trace( e1, e1 );
}
for ( String vmId : vmToken.getInstanceIds( ) ) {
try {
VmInstance vm = VmInstances.getInstance( ).lookup( vmId );
vm.setState( VmState.TERMINATED, Reason.FAILED, e.getMessage( ) );
VmInstances.getInstance( ).disable( vmId );
} catch ( Exception e1 ) {
LOG.debug( e1, e1 );
}
}
}
}
}
@SuppressWarnings( "unchecked" )
private void setupNetworkMessages( NetworkToken networkToken ) {
if ( networkToken != null ) {
QueuedEventCallback callback = new StartNetworkCallback( networkToken ).regardingUserRequest( vmAllocInfo.getRequest( ) );
this.messages.addRequest( State.CREATE_NETWORK, callback );
EventRecord.here( ClusterAllocator.class, EventType.VM_PREPARE, callback.getClass( ).getSimpleName( ), networkToken.toString( ) ).debug( );
}
try {
RunInstancesType request = this.vmAllocInfo.getRequest( );
if ( networkToken != null ) {
Network network = Networks.getInstance( ).lookup( networkToken.getName( ) );
EventRecord.here( ClusterAllocator.class, EventType.VM_PREPARE, ConfigureNetworkCallback.class.getSimpleName( ), network.getRules( ).toString( ) ).debug( );
if ( !network.getRules( ).isEmpty( ) ) {
this.messages.addRequest( State.CREATE_NETWORK_RULES, new ConfigureNetworkCallback( this.vmAllocInfo.getRequest( ).getUserId( ), network.getRules( ) ) );
}
//:: need to refresh the rules on the backend for all active networks which point to this network :://
for ( Network otherNetwork : Networks.getInstance( ).listValues( ) ) {
if ( otherNetwork.isPeer( network.getUserName( ), network.getNetworkName( ) ) ) {
LOG.warn( "Need to refresh rules for incoming named network ingress on: " + otherNetwork.getName( ) );
LOG.debug( otherNetwork );
if ( !otherNetwork.getRules( ).isEmpty( ) ) {
this.messages.addRequest( State.CREATE_NETWORK_RULES, new ConfigureNetworkCallback( otherNetwork.getUserName( ), otherNetwork.getRules( ) ) );
}
}
}
}
} catch ( NoSuchElementException e ) {}/* just added this network, shouldn't happen, if so just smile and nod */
}
private void setupVmMessages( final ResourceToken token ) {
Integer vlan = null;
List<String> networkNames = null;
ArrayList<String> networkIndexes = Lists.newArrayList( );
if ( token.getPrimaryNetwork( ) != null ) {
vlan = token.getPrimaryNetwork( ).getVlan( );
if ( vlan < 0 ) vlan = 9;//FIXME: general vlan, should be min-1?
networkNames = Lists.newArrayList( token.getPrimaryNetwork( ).getNetworkName( ) );
for ( Integer index : token.getPrimaryNetwork( ).getIndexes( ) ) {
networkIndexes.add( index.toString( ) );
}
} else {
vlan = -1;
networkNames = Lists.newArrayList( Collections.nCopies( token.getAmount( ), "default" ) );
networkIndexes = Lists.newArrayList( Collections.nCopies( token.getAmount( ), "-1" ) );
}
final List<String> addresses = Lists.newArrayList( token.getAddresses( ) );
RunInstancesType request = this.vmAllocInfo.getRequest( );
String rsvId = this.vmAllocInfo.getReservationId( );
VmImageInfo imgInfo = this.vmAllocInfo.getImageInfo( );
VmKeyInfo keyInfo = this.vmAllocInfo.getKeyInfo( );
VmTypeInfo vmInfo = this.vmAllocInfo.getVmTypeInfo( );
byte[] userData = this.vmAllocInfo.getUserData( );
QueuedEventCallback cb = null;
try {
int index = 0;
for ( ResourceToken childToken : this.cluster.getNodeState( ).splitToken( token ) ) {
cb = makeRunRequest( request, childToken, rsvId, imgInfo, keyInfo, vmInfo, userData );
this.messages.addRequest( State.CREATE_VMS, cb );
index++;
}
} catch ( NoSuchTokenException e ) {
cb = makeRunRequest( request, token, rsvId, token.getInstanceIds( ), imgInfo, keyInfo, vmInfo, vlan, networkNames, networkIndexes, addresses, userData );
}
if ( cb != null ) {
this.messages.addRequest( State.CREATE_VMS, cb );
} else {
Exceptions.eat( "Failed to create VM run callback: " + token );
}
}
private QueuedEventCallback makeRunRequest( RunInstancesType request, ResourceToken childToken, String rsvId, List<String> instanceIds, VmImageInfo imgInfo, VmKeyInfo keyInfo, VmTypeInfo vmInfo, Integer vlan, List<String> networkNames, List<String> netIndexes, final List<String> addrList, byte[] userData ) {
List<String> macs = Lists.transform( instanceIds, new Function<String, String>( ) {
@Override
public String apply( String instanceId ) {
return VmInstances.getAsMAC( instanceId );
}
} );
VmRunType run = new VmRunType( rsvId, request.getUserData( ), childToken.getAmount( ), imgInfo, vmInfo, keyInfo, instanceIds, macs, vlan, networkNames,
netIndexes ).regardingUserRequest( request );
VmRunCallback cb = new VmRunCallback( run, childToken );
if ( !addrList.isEmpty( ) ) {
cb.then( new SuccessCallback<VmRunResponseType>( ) {
@Override
public void apply( VmRunResponseType response ) {
Iterator<String> addrs = addrList.iterator( );
for ( VmInfo vmInfo : response.getVms( ) ) {//TODO: this will have some funny failure characteristics
final Address addr = Addresses.getInstance( ).lookup( addrs.next( ) );
final VmInstance vm = VmInstances.getInstance( ).lookup( vmInfo.getInstanceId( ) );
addr.assign( vm.getInstanceId( ), vm.getPrivateAddress( ) ).getCallback( ).dispatch( addr.getCluster( ) );
}
}
} );
}
return cb;
}
private QueuedEventCallback makeRunRequest( RunInstancesType request, final ResourceToken childToken, String rsvId, VmImageInfo imgInfo, VmKeyInfo keyInfo, VmTypeInfo vmInfo, byte[] userData ) {
List<String> macs = Lists.transform( childToken.getInstanceIds( ), new Function<String, String>( ) {
@Override
public String apply( String instanceId ) {
return VmInstances.getAsMAC( instanceId );
}
} );
NetworkToken primaryNet = childToken.getPrimaryNetwork( );
int vlan;
List<String> netIndexes;
List<String> networkNames;
if ( primaryNet != null ) {
vlan = primaryNet.getVlan( );
networkNames = Lists.newArrayList( primaryNet.getNetworkName( ) );
netIndexes = Lists.newArrayList( Iterables.transform( primaryNet.getIndexes( ), Functions.TO_STRING ) );
} else {
vlan = -1;
networkNames = Lists.newArrayList( "default" );
netIndexes = Lists.newArrayList( "-1" );
}
VmRunType run = new VmRunType( rsvId, request.getUserData( ), childToken.getAmount( ), imgInfo, vmInfo, keyInfo, childToken.getInstanceIds( ), macs, vlan,
networkNames, netIndexes ).regardingUserRequest( request );
VmRunCallback cb = new VmRunCallback( run, childToken );
if ( !childToken.getAddresses( ).isEmpty( ) ) {
final String address = childToken.getAddresses( ).get( 0 );
cb.then( new SuccessCallback<VmRunResponseType>( ) {
@Override
public void apply( VmRunResponseType response ) {
for ( VmInfo vmInfo : response.getVms( ) ) {//TODO: this will have some funny failure characteristics
try {
final Address addr = Addresses.getInstance( ).lookup( address );
final VmInstance vm = VmInstances.getInstance( ).lookup( childToken.getInstanceIds( ).get( 0 ) );
addr.assign( vmInfo.getInstanceId( ), vmInfo.getNetParams( ).getIpAddress( ) ).getCallback( ).dispatch( addr.getCluster( ) );
} catch ( NoSuchElementException ex ) {
LOG.debug( ex , ex );
}
}
}
}
);
}
return cb;
}
public void run( ) {
this.messages.run( );
}
}