Skip to content

Commit

Permalink
feature/EYPP-1211: simple, target and step policies support for asg
Browse files Browse the repository at this point in the history
  • Loading branch information
starbeast committed Dec 8, 2017
1 parent b0ed6cb commit 4564f32
Show file tree
Hide file tree
Showing 13 changed files with 893 additions and 134 deletions.
107 changes: 107 additions & 0 deletions lib/fog/aws.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,113 @@ module AWS
service(:sts, 'STS')
service(:support, 'Support')

# Transforms hash keys according to the passed mapping or given block
#
# ==== Parameters
# * object<~Hash> - A hash to apply transformation to
# * mappings<~Hash> - A hash of mappings for keys. Keys of the mappings object should match desired keys of the
# object which will be transformed. If object contains values that are hashes or arrays of hashes which should
# be transformed as well mappings object's value should be configured with a specific form, for example:
# {
# :key => {
# 'Key' => {
# :nested_key => 'NestedKey'
# }
# }
# }
# This form will transform object's key :key to 'Key'
# and transform corresponding value: if it's a hash then its key :nested_key will be transformed into 'NestedKey'
# if it's an array of hashes, each hash element of the array will have it's key :nested_key transformed into 'NestedKey'
# * block<~Proc> - block which is applied if mappings object does not contain key. Block receives key as it's argument
# and should return new key as the one that will be used to replace the original one
# ==== Returns
# * object<~Hash> - hash containing transformed keys
#
def self.map_keys(object, mappings = nil, &block)
case object
when ::Hash
object.reduce({}) do |acc, (key, val)|
mapping = mappings[key] if mappings
new_key, new_value = begin
if mapping
case mapping
when ::Hash
mapped_key = mapping.keys[0]
[mapped_key, map_keys(val, mapping[mapped_key], &block)]
else
[mapping, map_keys(val, &block)]
end
else
mapped_value = map_keys(val, &block)
if block_given?
[block.call(key), mapped_value]
else
[key, mapped_value]
end
end
end
acc[new_key] = new_value
acc
end
when ::Array
object.map { |item| map_keys(item, mappings, &block) }
else
object
end
end

# Maps object keys to aws compatible: underscore keys are transformed in camel case strings
# with capitalized first word (aka :ab_cd transforms into AbCd). Already compatible keys remain the same
#
# ==== Parameters
# * object<~Hash> - A hash to apply transformation to
# * mappings<~Hash> - An optional hash of mappings for keys
# ==== Returns
# * object<~Hash> - hash containing transformed keys
#
def self.map_to_aws(object, mappings = nil)
map_keys(object, mappings) do |key|
words = key.to_s.split('_')
if words.length > 1
words.collect(&:capitalize).join
else
words[0].split(/(?=[A-Z])/).collect(&:capitalize).join
end
end
end

# Maps object keys from aws to ruby compatible form aka snake case symbols
#
# ==== Parameters
# * object<~Hash> - A hash to apply transformation to
# * mappings<~Hash> - An optional hash of mappings for keys
# ==== Returns
# * object<~Hash> - hash containing transformed keys
#
def self.map_from_aws(object, mappings = nil)
map_keys(object, mappings) { |key| key.to_s.split(/(?=[A-Z])/).join('_').downcase.to_sym }
end

# Helper function to invert mappings: recursively replaces mapping keys and values.
#
# ==== Parameters
# * mappings<~Hash> - mappings hash
# ==== Returns
# * object<~Hash> - inverted mappings
#
def self.invert_mappings(mappings)
mappings.reduce({}) do |acc, (key, val)|
mapped_key, mapped_value = case val
when ::Hash
[val.keys[0], val.values[0]]
else
[val, nil]
end
acc[mapped_key] = mapped_value ? { key => invert_mappings(mapped_value) } : key
acc
end
end

def self.indexed_param(key, values)
params = {}
unless key.include?('%d')
Expand Down
4 changes: 4 additions & 0 deletions lib/fog/aws/models/auto_scaling/group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ def enable_metrics_collection(granularity = '1Minute', metrics = {})
reload
end

def policies
service.policies.all('AutoScalingGroupName' => id)
end

def instances
Fog::AWS::AutoScaling::Instances.new(:service => service).load(attributes[:instances])
end
Expand Down
138 changes: 121 additions & 17 deletions lib/fog/aws/models/auto_scaling/policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,103 @@ module Fog
module AWS
class AutoScaling
class Policy < Fog::Model
identity :id, :aliases => 'PolicyName'
attribute :arn, :aliases => 'PolicyARN'
attribute :adjustment_type, :aliases => 'AdjustmentType'
attribute :alarms, :aliases => 'Alarms'
attribute :auto_scaling_group_name, :aliases => 'AutoScalingGroupName'
attribute :cooldown, :aliases => 'Cooldown'
attribute :min_adjustment_step, :aliases => 'MinAdjustmentStep'
attribute :scaling_adjustment, :aliases => 'ScalingAdjustment'
identity :id, :aliases => 'PolicyName'
attribute :arn, :aliases => 'PolicyARN'
attribute :type, :aliases => 'PolicyType'
attribute :adjustment_type, :aliases => 'AdjustmentType'
attribute :scaling_adjustment, :aliases => 'ScalingAdjustment'
attribute :step_adjustments, :aliases => 'StepAdjustments'
attribute :target_tracking_configuration, :aliases => 'TargetTrackingConfiguration'
attribute :alarms, :aliases => 'Alarms'
attribute :auto_scaling_group_name, :aliases => 'AutoScalingGroupName'
attribute :cooldown, :aliases => 'Cooldown'
attribute :estimated_instance_warmup, :aliases => 'EstimatedInstanceWarmup'
attribute :metric_aggregation_type, :aliases => 'MetricAggregationType'
attribute :min_adjustment_magnitude, :aliases => 'MinAdjustmentMagnitude'
attribute :min_adjustment_step, :aliases => 'MinAdjustmentStep'

STEP_ADJUSTMENTS_MAPPING = {
:metric_interval_lower_bound => 'MetricIntervalLowerBound',
:metric_interval_upper_bound => 'MetricIntervalUpperBound',
:scaling_adjustment => 'ScalingAdjustment'
}.freeze

TARGET_TRACKING_MAPPING = {
:customized_metric_specification => {
'CustomizedMetricSpecification' => {
:metric_name => 'MetricName',
:namespace => 'Namespace',
:statistics => 'Statistics',
:unit => 'Unit',
:dimensions => {
'Dimensions' => {
:name => 'Name',
:value => 'Value'
}
}
}
},
:disable_scale_in => 'DisableScaleIn',
:target_value => 'TargetValue',
:predefined_metric_specification => {
'PredefinedMetricSpecification' => {
:predefined_metric_type => 'PredefinedMetricType',
:resource_label => 'ResourceLabel'
}
}
}.freeze

# Returns attribute names specific for different policy types
#
# ==== Parameters
# * policy_type<~String> - type of the auto scaling policy
#
# ==== Returns
# * options<~Array> Array of string containing policy specific options
#
def self.preserve_options(policy_type)
case policy_type
when 'StepScaling'
%w(EstimatedInstanceWarmup PolicyType MinAdjustmentMagnitude MetricAggregationType AdjustmentType StepAdjustments)
when 'TargetTrackingScaling'
%w(EstimatedInstanceWarmup PolicyType TargetTrackingConfiguration)
else
%w(AdjustmentType ScalingAdjustment PolicyType Cooldown MinAdjustmentMagnitude MinAdjustmentStep)
end
end

def initialize(attributes)
attributes['AdjustmentType'] ||= 'ChangeInCapacity'
attributes['ScalingAdjustment'] ||= 1
super
case self.type
when 'StepScaling'
prepare_step_policy
when 'TargetTrackingScaling'
prepare_target_policy
else
prepare_simple_policy
end
end

# TODO: implement #alarms
# TODO: implement #auto_scaling_group

def auto_scaling_group
service.groups.get(self.auto_scaling_group_name)
end

def save
requires :id
requires :adjustment_type
requires :auto_scaling_group_name
requires :scaling_adjustment
type_requirements

options = Hash[self.class.aliases.map { |key, value| [key, send(value)] }]
options.delete_if { |key, value| value.nil? }
if options['TargetTrackingConfiguration']
options['TargetTrackingConfiguration'] = Fog::AWS.map_to_aws(options['TargetTrackingConfiguration'], TARGET_TRACKING_MAPPING)
end
if options['StepAdjustments']
options['StepAdjustments'] = Fog::AWS.map_to_aws(options['StepAdjustments'], STEP_ADJUSTMENTS_MAPPING)
end
options_keys = self.class.preserve_options(self.type)
options.delete_if { |key, value| value.nil? || !options_keys.include?(key) }

service.put_scaling_policy(adjustment_type, auto_scaling_group_name, id, scaling_adjustment, options)
service.put_scaling_policy(auto_scaling_group_name, id, options)
reload
end

Expand All @@ -38,6 +107,41 @@ def destroy
requires :auto_scaling_group_name
service.delete_policy(auto_scaling_group_name, id)
end

private

def prepare_simple_policy
self.adjustment_type ||= 'ChangeInCapacity'
self.scaling_adjustment ||= 1
end

def prepare_target_policy
# do we need default tracking configuration or should we just allow it to fail?
if target_tracking_configuration
self.target_tracking_configuration = Fog::AWS.map_from_aws(target_tracking_configuration, TARGET_TRACKING_MAPPING)
end
end

def prepare_step_policy
# do we need any default scaling steps or should we just allow it to fail?
self.adjustment_type ||= 'ChangeInCapacity'
if step_adjustments
self.step_adjustments = Fog::AWS.map_from_aws(step_adjustments, STEP_ADJUSTMENTS_MAPPING)
end
end

def type_requirements
requires :id
requires :auto_scaling_group_name
case self.type
when 'StepScaling'
requires :step_adjustments
when 'TargetTrackingScaling'
requires :target_tracking_configuration
else
requires :scaling_adjustment
end
end
end
end
end
Expand Down
Loading

0 comments on commit 4564f32

Please sign in to comment.