/************************************************************************* * Copyright 2009-2013 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; 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/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. ************************************************************************/ package com.eucalyptus.autoscaling.common.internal.groups; import static com.eucalyptus.autoscaling.common.AutoScalingMetadata.AutoScalingGroupMetadata; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import javax.persistence.CascadeType; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.MapKeyColumn; import javax.persistence.OneToMany; import javax.persistence.OrderColumn; import javax.persistence.PersistenceContext; import javax.persistence.PrePersist; import javax.persistence.PreUpdate; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; import org.apache.log4j.Logger; import com.eucalyptus.autoscaling.common.internal.activities.ScalingActivity; import com.eucalyptus.autoscaling.common.AutoScalingBackend; import com.eucalyptus.autoscaling.common.internal.configurations.LaunchConfiguration; import com.eucalyptus.autoscaling.common.internal.instances.AutoScalingInstance; import com.eucalyptus.autoscaling.common.internal.instances.AutoScalingInstance_; import com.eucalyptus.autoscaling.common.internal.policies.ScalingPolicy; import com.eucalyptus.autoscaling.common.internal.tags.AutoScalingGroupTag; import com.eucalyptus.entities.AbstractOwnedPersistent; import com.eucalyptus.auth.principal.OwnerFullName; import com.eucalyptus.entities.Entities; import com.eucalyptus.entities.TransactionResource; import com.eucalyptus.upgrade.Upgrades; import com.eucalyptus.util.Exceptions; import com.google.common.base.Joiner; import com.google.common.base.MoreObjects; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; /** * */ @Entity @PersistenceContext( name = "eucalyptus_autoscaling" ) @Table( name = "metadata_auto_scaling_groups" ) public class AutoScalingGroup extends AbstractOwnedPersistent implements AutoScalingGroupMetadata { private static final long serialVersionUID = 1L; @Column( name = "metadata_max_size", nullable = false ) private Integer maxSize; @Column( name = "metadata_min_size", nullable = false ) private Integer minSize; @ManyToOne( optional = false ) @JoinColumn( name = "metadata_launch_configuration_id" ) private LaunchConfiguration launchConfiguration; @Column(name = "metadata_capacity_timestamp", nullable = false ) @Temporal( TemporalType.TIMESTAMP) private Date capacityTimestamp; @Column( name = "metadata_default_cooldown", nullable = false ) private Integer defaultCooldown; @Column( name = "metadata_desired_capacity", nullable = false ) private Integer desiredCapacity; @Column( name = "metadata_capacity", nullable = false ) private Integer capacity; @Column( name = "metadata_scaling_required", nullable = false ) private Boolean scalingRequired; @Column( name = "metadata_health_check_grace_period" ) private Integer healthCheckGracePeriod; @Column( name = "metadata_health_check_type", nullable = false ) @Enumerated( EnumType.STRING ) private HealthCheckType healthCheckType; @Column( name = "metadata_new_instances_protected_from_scale_in" ) private Boolean newInstancesProtectedFromScaleIn; @ElementCollection @CollectionTable( name = "metadata_auto_scaling_group_availability_zones" ) @Column( name = "metadata_availability_zone" ) @JoinColumn( name = "metadata_auto_scaling_group_id" ) @OrderColumn( name = "metadata_availability_zone_index") private List<String> availabilityZones = Lists.newArrayList(); @ElementCollection @CollectionTable( name = "metadata_auto_scaling_group_zones_to_subnets" ) @MapKeyColumn( name = "metadata_availability_zone" ) @Column( name = "metadata_subnet_id" ) @JoinColumn( name = "metadata_auto_scaling_group_id" ) private Map<String,String> subnetIdByZone = Maps.newHashMap( ); @ElementCollection @CollectionTable( name = "metadata_auto_scaling_group_termination_policies" ) @Column( name = "metadata_termination_policy" ) @JoinColumn( name = "metadata_auto_scaling_group_id" ) @OrderColumn( name = "metadata_policy_index") @Enumerated( EnumType.STRING ) private List<TerminationPolicyType> terminationPolicies = Lists.newArrayList(); @ElementCollection @CollectionTable( name = "metadata_auto_scaling_group_load_balancers" ) @Column( name = "metadata_load_balancer_name" ) @JoinColumn( name = "metadata_auto_scaling_group_id" ) @OrderColumn( name = "metadata_load_balancer_index") private List<String> loadBalancerNames = Lists.newArrayList(); @ElementCollection @CollectionTable( name = "metadata_auto_scaling_group_suspended_processes" ) @JoinColumn( name = "metadata_auto_scaling_group_id" ) private Set<SuspendedProcess> suspendedProcesses = Sets.newHashSet(); @ElementCollection @CollectionTable( name = "metadata_auto_scaling_group_enabled_metrics" ) @Column( name = "metadata_metric" ) @JoinColumn( name = "metadata_auto_scaling_group_id" ) @Enumerated( EnumType.STRING ) private Set<MetricCollectionType> enabledMetrics = Sets.newHashSet(); @ElementCollection @CollectionTable( name = "metadata_auto_scaling_group_scaling_causes" ) @JoinColumn( name = "metadata_auto_scaling_group_id" ) @OrderColumn( name = "metadata_scaling_causes_index") private List<GroupScalingCause> scalingCauses = Lists.newArrayList(); @OneToMany( fetch = FetchType.LAZY, cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy = "group" ) private Collection<ScalingActivity> scalingActivity; @OneToMany( fetch = FetchType.LAZY, cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy = "group" ) private Collection<ScalingPolicy> scalingPolicies; @OneToMany( fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "group" ) private Collection<AutoScalingGroupTag> tags = Lists.newArrayList(); @OneToMany( fetch = FetchType.LAZY, cascade = CascadeType.REFRESH, mappedBy = "autoScalingGroup" ) private List<AutoScalingInstance> instances = Lists.newArrayList(); protected AutoScalingGroup() { } protected AutoScalingGroup( @Nullable final OwnerFullName owner ) { super( owner ); } protected AutoScalingGroup( final OwnerFullName owner, final String displayName ) { super( owner, displayName ); } public String getAutoScalingGroupName() { return getDisplayName(); } public Integer getMaxSize() { return maxSize; } public void setMaxSize( final Integer maxSize ) { this.maxSize = maxSize; } public Integer getMinSize() { return minSize; } public void setMinSize( final Integer minSize ) { this.minSize = minSize; } public LaunchConfiguration getLaunchConfiguration() { return launchConfiguration; } public void setLaunchConfiguration( final LaunchConfiguration launchConfiguration ) { this.launchConfiguration = launchConfiguration; } public Date getCapacityTimestamp() { return capacityTimestamp; } public void setCapacityTimestamp( final Date capacityTimestamp ) { this.capacityTimestamp = capacityTimestamp; } public Integer getDefaultCooldown() { return defaultCooldown; } public void setDefaultCooldown( final Integer defaultCooldown ) { this.defaultCooldown = defaultCooldown; } public Integer getDesiredCapacity() { return desiredCapacity; } public void setDesiredCapacity( final Integer desiredCapacity ) { this.desiredCapacity = desiredCapacity; } public Integer getCapacity() { return capacity; } public void setCapacity( final Integer capacity ) { this.capacity = capacity; } public Boolean getScalingRequired() { return scalingRequired; } public void setScalingRequired( final Boolean scalingRequired ) { this.scalingRequired = scalingRequired; } public Integer getHealthCheckGracePeriod() { return healthCheckGracePeriod; } public void setHealthCheckGracePeriod( final Integer healthCheckGracePeriod ) { this.healthCheckGracePeriod = healthCheckGracePeriod; } public HealthCheckType getHealthCheckType() { return healthCheckType; } public void setHealthCheckType( final HealthCheckType healthCheckType ) { this.healthCheckType = healthCheckType; } public Boolean getNewInstancesProtectedFromScaleIn() { return newInstancesProtectedFromScaleIn; } public void setNewInstancesProtectedFromScaleIn( final Boolean newInstancesProtectedFromScaleIn ) { this.newInstancesProtectedFromScaleIn = newInstancesProtectedFromScaleIn; } public List<String> getAvailabilityZones() { return availabilityZones; } public void setAvailabilityZones( final List<String> availabilityZones ) { this.availabilityZones = availabilityZones; } public Map<String, String> getSubnetIdByZone() { return subnetIdByZone; } public void setSubnetIdByZone( final Map<String, String> subnetIdByZone ) { this.subnetIdByZone = subnetIdByZone; } public List<TerminationPolicyType> getTerminationPolicies() { return terminationPolicies; } public void setTerminationPolicies( final List<TerminationPolicyType> terminationPolicies ) { this.terminationPolicies = terminationPolicies; } public List<String> getLoadBalancerNames() { return loadBalancerNames; } public void setLoadBalancerNames( final List<String> loadBalancerNames ) { this.loadBalancerNames = loadBalancerNames; } public Set<SuspendedProcess> getSuspendedProcesses() { return suspendedProcesses; } public void setSuspendedProcesses( final Set<SuspendedProcess> suspendedProcesses ) { this.suspendedProcesses = suspendedProcesses; } public Set<MetricCollectionType> getEnabledMetrics() { return enabledMetrics; } public void setEnabledMetrics( final Set<MetricCollectionType> enabledMetrics ) { this.enabledMetrics = enabledMetrics; } public List<GroupScalingCause> getScalingCauses() { return scalingCauses; } public void setScalingCauses( final List<GroupScalingCause> scalingCauses ) { this.scalingCauses = scalingCauses; } public List<AutoScalingInstance> getAutoScalingInstances() { return instances; } /** * Update the capacity of the group, flag scaling required if necessary. * * @param capacity The new capacity. */ public void updateCapacity( final int capacity ) { this.scalingRequired = scalingRequired || desiredCapacity == null || capacity != desiredCapacity; this.capacity = capacity; } public void updateDesiredCapacity( final int desiredCapacity, final String reason ) { if ( !this.desiredCapacity.equals( desiredCapacity ) ) { scalingCauses.add( new GroupScalingCause( reason ) ); while ( scalingCauses.size() > 100 ) { scalingCauses.remove( 0 ); } } this.scalingRequired = scalingRequired || capacity == null || !capacity.equals( desiredCapacity ); this.desiredCapacity = desiredCapacity; } public void updateAvailabilityZones( final List<String> availabilityZones ) { final Set<String> currentZones = Sets.newHashSet( this.availabilityZones ); final Set<String> newZones = Sets.newHashSet( availabilityZones ); if ( !currentZones.equals( newZones ) ) { final String removedZones = Joiner.on(",").join( Sets.difference( currentZones, newZones ) ); final String addedZones = Joiner.on(",").join( Sets.difference( newZones, currentZones ) ); scalingCauses.add( new GroupScalingCause( String.format( "a user request removed the zones %1$s from this AutoScalingGroup making them invalid", removedZones ) ) ); scalingCauses.add( new GroupScalingCause( String.format( "a user request added the zones %1$s to this AutoScalingGroup and the group may require rebalancing", addedZones ) ) ); } this.scalingRequired = scalingRequired || !currentZones.equals( newZones ); this.availabilityZones = availabilityZones; } @Override public String getArn() { return String.format( "arn:aws:autoscaling::%1s:autoScalingGroup:%2s:autoScalingGroupName/%3s", getOwnerAccountNumber(), getNaturalId(), getDisplayName() ); } /** * Create an example AutoScalingGroup for the given owner. * * @param ownerFullName The owner * @return The example */ public static AutoScalingGroup withOwner( @Nullable final OwnerFullName ownerFullName ) { return new AutoScalingGroup( ownerFullName ); } /** * Create an example AutoScalingGroup for the given owner and name. * * @param ownerFullName The owner * @param name The name * @return The example */ public static AutoScalingGroup named( final OwnerFullName ownerFullName, final String name ) { return new AutoScalingGroup( ownerFullName, name ); } public static AutoScalingGroup withId( final String id ) { final AutoScalingGroup example = new AutoScalingGroup(); example.setId( id); return example; } public static AutoScalingGroup withUuid( final String uuid ) { final AutoScalingGroup example = new AutoScalingGroup(); example.setNaturalId( uuid ); return example; } public static AutoScalingGroup requiringScaling( ) { final AutoScalingGroup example = new AutoScalingGroup(); example.setScalingRequired( true ); return example; } public static AutoScalingGroup create( final OwnerFullName ownerFullName, final String name, final LaunchConfiguration launchConfiguration, final Integer minSize, final Integer maxSize, final Boolean newInstancesProtectedFromScaleIn, final List<AutoScalingGroupTag> tags ) { final AutoScalingGroup autoScalingGroup = new AutoScalingGroup( ownerFullName, name ); autoScalingGroup.setLaunchConfiguration( launchConfiguration ); autoScalingGroup.setMinSize( minSize ); autoScalingGroup.setMaxSize( maxSize ); autoScalingGroup.setNewInstancesProtectedFromScaleIn( newInstancesProtectedFromScaleIn ); autoScalingGroup.setCapacity( 0 ); for ( final AutoScalingGroupTag tag : tags ) { tag.setGroup( autoScalingGroup ); tag.setOwner( autoScalingGroup.getOwner() ); autoScalingGroup.tags.add( tag ); } return autoScalingGroup; } @PrePersist @PreUpdate private void preUpdate() { if ( capacityTimestamp == null ) { capacityTimestamp = new Date(); } } protected static abstract class BaseBuilder<T extends BaseBuilder<T>> { private OwnerFullName ownerFullName; private String name; private Integer minSize; private Integer maxSize; private Integer defaultCooldown; private Integer desiredCapacity; private Integer healthCheckGracePeriod; private HealthCheckType healthCheckType; private Boolean newInstancesProtectedFromScaleIn; private LaunchConfiguration launchConfiguration; private Set<String> availabilityZones = Sets.newLinkedHashSet(); private Map<String,String> subnetsByZone = Maps.newHashMap(); private Set<TerminationPolicyType> terminationPolicies = Sets.newLinkedHashSet(); private Set<String> loadBalancerNames = Sets.newLinkedHashSet(); private List<AutoScalingGroupTag> tags = Lists.newArrayList(); BaseBuilder( final OwnerFullName ownerFullName, final String name, final LaunchConfiguration launchConfiguration, final Integer minSize, final Integer maxSize ) { this.ownerFullName = ownerFullName; this.name = name; this.launchConfiguration = launchConfiguration; this.minSize = minSize; this.maxSize = maxSize; } protected abstract T builder(); public T withDefaultCooldown( final Integer defaultCooldown ) { this.defaultCooldown = defaultCooldown; return builder(); } public T withDesiredCapacity( final Integer desiredCapacity ) { this.desiredCapacity = desiredCapacity; return builder(); } public T withHealthCheckGracePeriod( final Integer healthCheckGracePeriod ) { this.healthCheckGracePeriod = healthCheckGracePeriod; return builder(); } public T withHealthCheckType( final HealthCheckType healthCheckType ) { this.healthCheckType = healthCheckType; return builder(); } public T withNewInstancesProtectedFromScaleIn( final Boolean newInstancesProtectedFromScaleIn ) { this.newInstancesProtectedFromScaleIn = newInstancesProtectedFromScaleIn; return builder(); } public T withAvailabilityZones( final Iterable<String> availabilityZones ) { if ( availabilityZones != null ) { Iterables.addAll( this.availabilityZones, availabilityZones ); } return builder(); } public T withSubnetsByZone( final Map<String,String> subnetsByZone ) { if ( subnetsByZone != null ) { this.subnetsByZone.putAll( subnetsByZone ); } return builder(); } public T withTerminationPolicyTypes( final Iterable<TerminationPolicyType> terminationPolicies ) { if ( terminationPolicies != null ) { Iterables.addAll( this.terminationPolicies, terminationPolicies ); } return builder(); } public T withLoadBalancerNames( final Iterable<String> loadBalancerNames ) { if ( loadBalancerNames != null ) { Iterables.addAll( this.loadBalancerNames, loadBalancerNames ); } return builder(); } public T withTags( final Iterable<AutoScalingGroupTag> tags ) { if ( tags != null ) { Iterables.addAll( this.tags, tags ); } return builder(); } protected AutoScalingGroup build() { final AutoScalingGroup group = AutoScalingGroup.create( ownerFullName, name, launchConfiguration, minSize, maxSize, MoreObjects.firstNonNull( newInstancesProtectedFromScaleIn, false ), tags ); group.setDefaultCooldown( MoreObjects.firstNonNull( defaultCooldown, 300 ) ); group.setDesiredCapacity( MoreObjects.firstNonNull( desiredCapacity, minSize ) ); group.setHealthCheckGracePeriod( MoreObjects.firstNonNull( healthCheckGracePeriod, 0 ) ); group.setHealthCheckType( MoreObjects.firstNonNull( healthCheckType, HealthCheckType.EC2 ) ); group.setAvailabilityZones( Lists.newArrayList( availabilityZones ) ); group.setSubnetIdByZone( Maps.newHashMap( subnetsByZone ) ); group.setTerminationPolicies( terminationPolicies.isEmpty() ? Collections.singletonList(TerminationPolicyType.Default) : Lists.newArrayList( terminationPolicies ) ); group.setLoadBalancerNames( Lists.newArrayList( loadBalancerNames ) ); group.setScalingRequired( group.getDesiredCapacity() > 0 ); return group; } } @Upgrades.EntityUpgrade( entities = AutoScalingGroup.class, since = Upgrades.Version.v5_0_0, value = AutoScalingBackend.class) public enum AutoScalingGroupUpgrade500 implements Predicate<Class> { INSTANCE; private static Logger LOG = Logger.getLogger(AutoScalingGroupUpgrade500.class); @Override public boolean apply(@Nullable Class aClass) { try ( final TransactionResource tran = Entities.transactionFor( AutoScalingGroup.class ) ) { final List<AutoScalingGroup> groups = Entities.criteriaQuery( Entities.restriction( AutoScalingGroup.class ) .isNull( AutoScalingGroup_.newInstancesProtectedFromScaleIn ) ).list( ); for ( final AutoScalingGroup group : groups ) { if ( group.getNewInstancesProtectedFromScaleIn( ) == null ) { group.setNewInstancesProtectedFromScaleIn( false ); LOG.info( "Set default scale in protection for auto scaling grpup : " + group.getArn( ) ); } } tran.commit( ); } catch (Exception ex) { LOG.error("Exception during upgrade while attempting to initialize scaling protection for groups"); throw Exceptions.toUndeclared(ex); } return true; } } }