/************************************************************************* * (c) Copyright 2017 Hewlett Packard Enterprise Development Company LP * * 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/. ************************************************************************/ package com.eucalyptus.reporting; import java.util.Collections; import java.util.Date; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import javax.annotation.Nonnull; import com.eucalyptus.crypto.util.Timestamps; import com.eucalyptus.event.EventListener; import com.eucalyptus.event.Listeners; import com.eucalyptus.reporting.event.ResourceAvailabilityEvent; import com.eucalyptus.util.Assert; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import javaslang.collection.Stream; /** * Track the latest capacity information from events preserving dimensions. */ public class Capacity { private static AtomicReference<CapacitySnapshot> capacitySnapshotRef = new AtomicReference<>( new CapacitySnapshot( Collections.emptyMap( ) ) ); /** * Get the latest capacity snapshot. * * @return The snapshot which may have no capacity information but is never null */ @Nonnull public static CapacitySnapshot snapshot( ) { return capacitySnapshotRef.get( ); } /** * The known capacity at a given instant */ public static class CapacitySnapshot { private final long timestamp; private final Map<String,Set<CapacityEntry>> capacityByType; public CapacitySnapshot( final long timestamp, final Map<String, Set<CapacityEntry>> capacityByType ) { this.timestamp = timestamp; this.capacityByType = capacityByType; } public CapacitySnapshot( final Map<String, Set<CapacityEntry>> capacityByType ) { this( System.currentTimeMillis( ), capacityByType ); } CapacitySnapshot update( final long timestamp, final String type, final Set<CapacityEntry> capacity ) { Assert.notNull( type, "type" ); capacity.forEach( capacityEntry -> { if ( !type.equals( capacityEntry.getType( ) ) ) throw new IllegalArgumentException( "type mismatch" ); } ); final Map<String,Set<CapacityEntry>> newCapacityByType = Maps.newHashMap( ); newCapacityByType.putAll( this.capacityByType ); newCapacityByType.put( type, ImmutableSet.copyOf( capacity ) ); return new CapacitySnapshot( timestamp, ImmutableMap.copyOf( newCapacityByType ) ); } public long getTimestamp( ) { return timestamp; } public Stream<CapacityEntry> getCapacities( ) { return Stream.ofAll( capacityByType.values( ) ).flatMap( Function.<Iterable<CapacityEntry>>identity( ) ); } @Override public String toString( ) { return MoreObjects.toStringHelper( this ) .add( "timestamp", Timestamps.formatIso8601Timestamp( new Date( timestamp ) ) ) .add( "capacities", capacityByType.values( ) ) .toString( ); } } /** * Capacity values by subtypes and dimensions */ public static class CapacityEntry { private final String type; private final Map<String,String> subtypes; private final Map<String,String> dimensions; private final long total; private final long available; public CapacityEntry( @Nonnull final String type, @Nonnull final Map<String, String> subtypes, @Nonnull final Map<String, String> dimensions, final long total, final long available ) { this.type = Assert.notNull( type, "type" ); this.subtypes = ImmutableMap.copyOf( subtypes ); this.dimensions = ImmutableMap.copyOf( dimensions ); this.total = total; this.available = available; } public String getType( ) { return type; } public Map<String, String> getSubtypes( ) { return subtypes; } public Map<String, String> getDimensions( ) { return dimensions; } public long getTotal( ) { return total; } public long getAvailable( ) { return available; } @Override public boolean equals( final Object o ) { if ( this == o ) return true; if ( o == null || getClass( ) != o.getClass( ) ) return false; final CapacityEntry that = (CapacityEntry) o; return Objects.equals( type, that.type ) && Objects.equals( subtypes, that.subtypes ) && Objects.equals( dimensions, that.dimensions ); } @Override public int hashCode() { return Objects.hash( type, subtypes, dimensions ); } @Override public String toString() { return MoreObjects.toStringHelper( this ) .add( "type", type ) .add( "subtypes", subtypes ) .add( "dimensions", dimensions ) .add( "available", available ) .add( "total", total ) .toString( ); } } public static class CapacityTrackingResourceAvailabilityEventListener implements EventListener<ResourceAvailabilityEvent> { public static void register( ) { Listeners.register( ResourceAvailabilityEvent.class, new CapacityTrackingResourceAvailabilityEventListener( ) ); } @Override public void fireEvent( final ResourceAvailabilityEvent event ) { final String type = event.getType( ).name( ); final Set<CapacityEntry> capacities = ImmutableSet.copyOf( Stream.ofAll( event.getAvailability( ) ).map( availability -> { final Map<String,String> types = Maps.newHashMap( ); final Map<String,String> dimensions = Maps.newHashMap( ); for ( final ResourceAvailabilityEvent.Tag tag : availability.getTags( ) ) { if ( tag instanceof ResourceAvailabilityEvent.Dimension ) { dimensions.put( tag.getType( ), tag.getValue( ) ); } else { types.put( tag.getType( ), tag.getValue( ) ); } } return new CapacityEntry( type, types, dimensions, availability.getTotal( ), availability.getAvailable( ) ); } ) ); for ( int i=0; i<10; i++ ) { final CapacitySnapshot currentSnapshot = capacitySnapshotRef.get( ); if ( capacitySnapshotRef.compareAndSet( currentSnapshot, currentSnapshot.update( System.currentTimeMillis( ), type, capacities ) ) ) { break; } } } } }