/*******************************************************************************
*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.Comparator;
import java.util.List;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ConcurrentSkipListSet;
import org.apache.log4j.Logger;
import com.eucalyptus.entities.VmType;
import com.eucalyptus.records.EventType;
import com.eucalyptus.util.LogUtil;
import com.eucalyptus.util.NotEnoughResourcesAvailable;
import com.google.common.collect.Lists;
import edu.ucsb.eucalyptus.cloud.NetworkToken;
import edu.ucsb.eucalyptus.cloud.ResourceToken;
import com.eucalyptus.records.EventRecord;
import edu.ucsb.eucalyptus.msgs.ResourceType;
import edu.ucsb.eucalyptus.msgs.VmTypeInfo;
public class ClusterNodeState {
private static Logger LOG = Logger.getLogger( ClusterNodeState.class );
private ConcurrentNavigableMap<String, VmTypeAvailability> typeMap;
private NavigableSet<ResourceToken> pendingTokens;
private NavigableSet<ResourceToken> submittedTokens;
private NavigableSet<ResourceToken> redeemedTokens;
private int virtualTimer;
private String clusterName;
public ClusterNodeState( String clusterName ) {
this.clusterName = clusterName;
this.typeMap = new ConcurrentSkipListMap<String, VmTypeAvailability>();
for ( VmType v : VmTypes.list() )
this.typeMap.putIfAbsent( v.getName(), new VmTypeAvailability( v, 0, 0 ) );
this.pendingTokens = new ConcurrentSkipListSet<ResourceToken>();
this.submittedTokens = new ConcurrentSkipListSet<ResourceToken>();
this.redeemedTokens = new ConcurrentSkipListSet<ResourceToken>();
}
public synchronized ResourceToken getResourceAllocation( String requestId, String userName, String vmTypeName, Integer quantity ) throws NotEnoughResourcesAvailable {
VmTypeAvailability vmType = this.typeMap.get( vmTypeName );
NavigableSet<VmTypeAvailability> sorted = this.sorted();
LOG.debug( LogUtil.header("BEFORE ALLOCATE") );
LOG.debug( sorted );
//:: if not enough, then bail out :://
if ( vmType.getAvailable() < quantity ) throw new NotEnoughResourcesAvailable("Not enough resources available: vm resources");
Set<VmTypeAvailability> tailSet = sorted.tailSet( vmType );
Set<VmTypeAvailability> headSet = sorted.headSet( vmType );
LOG.debug( LogUtil.header("DURING ALLOCATE") );
LOG.debug(LogUtil.subheader( "TAILSET: \n" + tailSet) );
LOG.debug(LogUtil.subheader( "HEADSET: \n" + headSet) );
//:: decrement available resources across the "active" partition :://
for ( VmTypeAvailability v : tailSet )
v.decrement( quantity );
for ( VmTypeAvailability v : headSet )
v.setAvailable( vmType.getAvailable() );
LOG.debug( LogUtil.header("AFTER ALLOCATE") );
LOG.debug( sorted );
ResourceToken token = new ResourceToken( this.clusterName, requestId, userName, quantity, this.virtualTimer++, vmTypeName );
EventRecord.caller( ResourceToken.class, EventType.TOKEN_RESERVED, token.toString( ) ).info( );
this.pendingTokens.add( token );
return token;
}
public synchronized List<ResourceToken> splitToken( ResourceToken token ) throws NoSuchTokenException {
EventRecord.caller( ResourceToken.class, EventType.TOKEN_SPLIT, token.toString( ) ).info( );
if( !this.pendingTokens.contains( token ) ) {
throw new NoSuchTokenException( "Splitting the requested token is not possible since it is not pending: " + token );
}
List<ResourceToken> childTokens = Lists.newArrayList( );
for( int index = 0; index < token.getAmount( ); index++ ) {
NetworkToken primaryNet = token.getPrimaryNetwork( );
ResourceToken childToken = new ResourceToken( token.getCluster( ), token.getCorrelationId( )+index, token.getUserName( ), 1, this.virtualTimer++, token.getVmType( ) );
if( token.getAddresses( ).size( ) > index ) {
childToken.getAddresses( ).add( token.getAddresses( ).get( index ) );
}
if( token.getInstanceIds( ).size( ) > index ) {
childToken.getInstanceIds( ).add( token.getInstanceIds( ).get( index ) );
}
if( primaryNet != null ) {
NetworkToken childNet = new NetworkToken( primaryNet.getCluster( ), primaryNet.getUserName( ), primaryNet.getNetworkName( ), primaryNet.getVlan( ) );
childNet.getIndexes( ).add( primaryNet.getIndexes( ).pollFirst( ) );
childToken.getNetworkTokens( ).add( childNet );
}
EventRecord.caller( ResourceToken.class, EventType.TOKEN_CHILD, childToken.toString( ) ).info( );
childTokens.add( childToken );
}
this.pendingTokens.remove( token );
this.pendingTokens.addAll( childTokens );
return childTokens;
}
public synchronized void releaseToken( ResourceToken token ) {
EventRecord.caller( ResourceToken.class, EventType.TOKEN_RETURNED, token.toString( ) ).info( );
this.pendingTokens.remove( token );
this.submittedTokens.remove( token );
this.redeemedTokens.remove( token );
}
public synchronized void submitToken( ResourceToken token ) throws NoSuchTokenException {
EventRecord.caller( ResourceToken.class, EventType.TOKEN_SUBMITTED, token.toString( ) ).info( );
if ( this.pendingTokens.remove( token ) )
this.submittedTokens.add( token );
else
throw new NoSuchTokenException();
}
public synchronized void redeemToken( ResourceToken token ) throws NoSuchTokenException {
EventRecord.caller( ResourceToken.class, EventType.TOKEN_REDEEMED, token.toString( ) ).info( );
if ( this.submittedTokens.remove( token ) || this.pendingTokens.remove( token ) )
this.redeemedTokens.add( token );
else
throw new NoSuchTokenException();
}
public synchronized void update( List<ResourceType> rscUpdate ) {
int outstandingCount = 0;
int pending = 0, submitted = 0, redeemed = 0;
for( ResourceToken t : this.pendingTokens )
pending += t.getAmount();
for( ResourceToken t : this.submittedTokens )
submitted += t.getAmount();
for( ResourceToken t : this.redeemedTokens )
redeemed += t.getAmount();
outstandingCount = pending + submitted;
EventRecord.here( ClusterNodeState.class, EventType.CLUSTER_STATE_UPDATE, this.clusterName, String.format( "outstanding=%d:pending=%d:submitted=%d:redeemed=%d", outstandingCount, pending, submitted, redeemed ) ).info( );
this.redeemedTokens.clear();
StringBuffer before = new StringBuffer();
StringBuffer after = new StringBuffer();
for ( ResourceType rsc : rscUpdate ) {
VmTypeAvailability vmAvailable = this.typeMap.get( rsc.getInstanceType().getName() );
if ( vmAvailable == null ) continue;
before.append( String.format( ":%s:%d/%d", vmAvailable.getType( ).getName( ), vmAvailable.getAvailable( ), vmAvailable.getMax( ) ) );
vmAvailable.setAvailable( rsc.getAvailableInstances() );
vmAvailable.decrement( outstandingCount );
vmAvailable.setMax( rsc.getMaxInstances() );
after.append( String.format( ":%s:%d/%d", vmAvailable.getType( ).getName( ), vmAvailable.getAvailable( ), vmAvailable.getMax( ) ) );
}
EventRecord.here( ClusterNodeState.class, EventType.CLUSTER_STATE_UPDATE, this.clusterName, "ANTE" + before.toString( ) ).info( );
EventRecord.here( ClusterNodeState.class, EventType.CLUSTER_STATE_UPDATE, this.clusterName, "POST" + after.toString( ) ).info( );
}
private NavigableSet<VmTypeAvailability> sorted() {
NavigableSet<VmTypeAvailability> available = new TreeSet<VmTypeAvailability>();
for ( String typeName : this.typeMap.keySet() )
available.add( this.typeMap.get( typeName ) );
available.add( VmTypeAvailability.ZERO );
LOG.debug("Resource information for " + this.clusterName );
return available;
}
public VmTypeAvailability getAvailability( String vmTypeName ) {
return this.typeMap.get( vmTypeName );
}
public static ResourceComparator getComparator( VmTypeInfo vmTypeInfo ) {
return new ResourceComparator( vmTypeInfo );
}
public static class ResourceComparator implements Comparator<ClusterNodeState> {
private VmTypeInfo vmTypeInfo;
ResourceComparator( final VmTypeInfo vmTypeInfo ) {
this.vmTypeInfo = vmTypeInfo;
}
public int compare( final ClusterNodeState o1, final ClusterNodeState o2 ) {
return o1.getAvailability( this.vmTypeInfo.getName() ).getAvailable() - o2.getAvailability( this.vmTypeInfo.getName() ).getAvailable();
}
}
@Override
public String toString( ) {
return String.format(
"ClusterNodeState [clusterName=%s, pendingTokens=%s, redeemedTokens=%s, submittedTokens=%s, typeMap=%s]",
this.clusterName, this.pendingTokens, this.redeemedTokens, this.submittedTokens, this.typeMap );
}
}