Skip to content

Commit 65938db

Browse files
committed
feature/EYPP-1211: simple, target and step policies support for asg
1 parent 6357a22 commit 65938db

File tree

13 files changed

+920
-133
lines changed

13 files changed

+920
-133
lines changed

lib/fog/aws.rb

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,113 @@ module AWS
8787
service(:sts, 'STS')
8888
service(:support, 'Support')
8989

90+
# Transforms hash keys according to the passed mapping or given block
91+
#
92+
# ==== Parameters
93+
# * object<~Hash> - A hash to apply transformation to
94+
# * mappings<~Hash> - A hash of mappings for keys. Keys of the mappings object should match desired keys of the
95+
# object which will be transformed. If object contains values that are hashes or arrays of hashes which should
96+
# be transformed as well mappings object's value should be configured with a specific form, for example:
97+
# {
98+
# :key => {
99+
# 'Key' => {
100+
# :nested_key => 'NestedKey'
101+
# }
102+
# }
103+
# }
104+
# This form will transform object's key :key to 'Key'
105+
# and transform corresponding value: if it's a hash then its key :nested_key will be transformed into 'NestedKey'
106+
# if it's an array of hashes, each hash element of the array will have it's key :nested_key transformed into 'NestedKey'
107+
# * block<~Proc> - block which is applied if mappings object does not contain key. Block receives key as it's argument
108+
# and should return new key as the one that will be used to replace the original one
109+
# ==== Returns
110+
# * object<~Hash> - hash containing transformed keys
111+
#
112+
def self.map_keys(object, mappings = nil, &block)
113+
case object
114+
when ::Hash
115+
object.reduce({}) do |acc, (key, val)|
116+
mapping = mappings[key] if mappings
117+
new_key, new_value = begin
118+
if mapping
119+
case mapping
120+
when ::Hash
121+
mapped_key = mapping.keys[0]
122+
[mapped_key, map_keys(val, mapping[mapped_key], &block)]
123+
else
124+
[mapping, map_keys(val, &block)]
125+
end
126+
else
127+
mapped_value = map_keys(val, &block)
128+
if block_given?
129+
[block.call(key), mapped_value]
130+
else
131+
[key, mapped_value]
132+
end
133+
end
134+
end
135+
acc[new_key] = new_value
136+
acc
137+
end
138+
when ::Array
139+
object.map { |item| map_keys(item, mappings, &block) }
140+
else
141+
object
142+
end
143+
end
144+
145+
# Maps object keys to aws compatible: underscore keys are transformed in camel case strings
146+
# with capitalized first word (aka :ab_cd transforms into AbCd). Already compatible keys remain the same
147+
#
148+
# ==== Parameters
149+
# * object<~Hash> - A hash to apply transformation to
150+
# * mappings<~Hash> - An optional hash of mappings for keys
151+
# ==== Returns
152+
# * object<~Hash> - hash containing transformed keys
153+
#
154+
def self.map_to_aws(object, mappings = nil)
155+
map_keys(object, mappings) do |key|
156+
words = key.to_s.split('_')
157+
if words.length > 1
158+
words.collect(&:capitalize).join
159+
else
160+
words[0].split(/(?=[A-Z])/).collect(&:capitalize).join
161+
end
162+
end
163+
end
164+
165+
# Maps object keys from aws to ruby compatible form aka snake case symbols
166+
#
167+
# ==== Parameters
168+
# * object<~Hash> - A hash to apply transformation to
169+
# * mappings<~Hash> - An optional hash of mappings for keys
170+
# ==== Returns
171+
# * object<~Hash> - hash containing transformed keys
172+
#
173+
def self.map_from_aws(object, mappings = nil)
174+
map_keys(object, mappings) { |key| key.to_s.split(/(?=[A-Z])/).join('_').downcase.to_sym }
175+
end
176+
177+
# Helper function to invert mappings: recursively replaces mapping keys and values.
178+
#
179+
# ==== Parameters
180+
# * mappings<~Hash> - mappings hash
181+
# ==== Returns
182+
# * object<~Hash> - inverted mappings
183+
#
184+
def self.invert_mappings(mappings)
185+
mappings.reduce({}) do |acc, (key, val)|
186+
mapped_key, mapped_value = case val
187+
when ::Hash
188+
[val.keys[0], val.values[0]]
189+
else
190+
[val, nil]
191+
end
192+
acc[mapped_key] = mapped_value ? { key => invert_mappings(mapped_value) } : key
193+
acc
194+
end
195+
end
196+
90197
def self.indexed_param(key, values)
91198
params = {}
92199
unless key.include?('%d')

lib/fog/aws/models/auto_scaling/group.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ def enable_metrics_collection(granularity = '1Minute', metrics = {})
9999
reload
100100
end
101101

102+
def policies
103+
service.policies.all('AutoScalingGroupName' => id)
104+
end
105+
102106
def instances
103107
Fog::AWS::AutoScaling::Instances.new(:service => service).load(attributes[:instances])
104108
end

lib/fog/aws/models/auto_scaling/policy.rb

Lines changed: 121 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,103 @@ module Fog
22
module AWS
33
class AutoScaling
44
class Policy < Fog::Model
5-
identity :id, :aliases => 'PolicyName'
6-
attribute :arn, :aliases => 'PolicyARN'
7-
attribute :adjustment_type, :aliases => 'AdjustmentType'
8-
attribute :alarms, :aliases => 'Alarms'
9-
attribute :auto_scaling_group_name, :aliases => 'AutoScalingGroupName'
10-
attribute :cooldown, :aliases => 'Cooldown'
11-
attribute :min_adjustment_step, :aliases => 'MinAdjustmentStep'
12-
attribute :scaling_adjustment, :aliases => 'ScalingAdjustment'
5+
identity :id, :aliases => 'PolicyName'
6+
attribute :arn, :aliases => 'PolicyARN'
7+
attribute :type, :aliases => 'PolicyType'
8+
attribute :adjustment_type, :aliases => 'AdjustmentType'
9+
attribute :scaling_adjustment, :aliases => 'ScalingAdjustment'
10+
attribute :step_adjustments, :aliases => 'StepAdjustments'
11+
attribute :target_tracking_configuration, :aliases => 'TargetTrackingConfiguration'
12+
attribute :alarms, :aliases => 'Alarms'
13+
attribute :auto_scaling_group_name, :aliases => 'AutoScalingGroupName'
14+
attribute :cooldown, :aliases => 'Cooldown'
15+
attribute :estimated_instance_warmup, :aliases => 'EstimatedInstanceWarmup'
16+
attribute :metric_aggregation_type, :aliases => 'MetricAggregationType'
17+
attribute :min_adjustment_magnitude, :aliases => 'MinAdjustmentMagnitude'
18+
attribute :min_adjustment_step, :aliases => 'MinAdjustmentStep'
19+
20+
STEP_ADJUSTMENTS_MAPPING = {
21+
:metric_interval_lower_bound => 'MetricIntervalLowerBound',
22+
:metric_interval_upper_bound => 'MetricIntervalUpperBound',
23+
:scaling_adjustment => 'ScalingAdjustment'
24+
}.freeze
25+
26+
TARGET_TRACKING_MAPPING = {
27+
:customized_metric_specification => {
28+
'CustomizedMetricSpecification' => {
29+
:metric_name => 'MetricName',
30+
:namespace => 'Namespace',
31+
:statistics => 'Statistics',
32+
:unit => 'Unit',
33+
:dimensions => {
34+
'Dimensions' => {
35+
:name => 'Name',
36+
:value => 'Value'
37+
}
38+
}
39+
}
40+
},
41+
:disable_scale_in => 'DisableScaleIn',
42+
:target_value => 'TargetValue',
43+
:predefined_metric_specification => {
44+
'PredefinedMetricSpecification' => {
45+
:predefined_metric_type => 'PredefinedMetricType',
46+
:resource_label => 'ResourceLabel'
47+
}
48+
}
49+
}.freeze
50+
51+
# Returns attribute names specific for different policy types
52+
#
53+
# ==== Parameters
54+
# * policy_type<~String> - type of the auto scaling policy
55+
#
56+
# ==== Returns
57+
# * options<~Array> Array of string containing policy specific options
58+
#
59+
def self.preserve_options(policy_type)
60+
case policy_type
61+
when 'StepScaling'
62+
%w(EstimatedInstanceWarmup PolicyType MinAdjustmentMagnitude MetricAggregationType AdjustmentType StepAdjustments)
63+
when 'TargetTrackingScaling'
64+
%w(EstimatedInstanceWarmup PolicyType TargetTrackingConfiguration)
65+
else
66+
%w(AdjustmentType ScalingAdjustment PolicyType Cooldown MinAdjustmentMagnitude MinAdjustmentStep)
67+
end
68+
end
1369

1470
def initialize(attributes)
15-
attributes['AdjustmentType'] ||= 'ChangeInCapacity'
16-
attributes['ScalingAdjustment'] ||= 1
1771
super
72+
case self.type
73+
when 'StepScaling'
74+
prepare_step_policy
75+
when 'TargetTrackingScaling'
76+
prepare_target_policy
77+
else
78+
prepare_simple_policy
79+
end
1880
end
1981

2082
# TODO: implement #alarms
21-
# TODO: implement #auto_scaling_group
83+
84+
def auto_scaling_group
85+
service.groups.get(self.auto_scaling_group_name)
86+
end
2287

2388
def save
24-
requires :id
25-
requires :adjustment_type
26-
requires :auto_scaling_group_name
27-
requires :scaling_adjustment
89+
type_requirements
2890

2991
options = Hash[self.class.aliases.map { |key, value| [key, send(value)] }]
30-
options.delete_if { |key, value| value.nil? }
92+
if options['TargetTrackingConfiguration']
93+
options['TargetTrackingConfiguration'] = Fog::AWS.map_to_aws(options['TargetTrackingConfiguration'], TARGET_TRACKING_MAPPING)
94+
end
95+
if options['StepAdjustments']
96+
options['StepAdjustments'] = Fog::AWS.map_to_aws(options['StepAdjustments'], STEP_ADJUSTMENTS_MAPPING)
97+
end
98+
options_keys = self.class.preserve_options(self.type)
99+
options.delete_if { |key, value| value.nil? || !options_keys.include?(key) }
31100

32-
service.put_scaling_policy(adjustment_type, auto_scaling_group_name, id, scaling_adjustment, options)
101+
service.put_scaling_policy(auto_scaling_group_name, id, options)
33102
reload
34103
end
35104

@@ -38,6 +107,41 @@ def destroy
38107
requires :auto_scaling_group_name
39108
service.delete_policy(auto_scaling_group_name, id)
40109
end
110+
111+
private
112+
113+
def prepare_simple_policy
114+
self.adjustment_type ||= 'ChangeInCapacity'
115+
self.scaling_adjustment ||= 1
116+
end
117+
118+
def prepare_target_policy
119+
# do we need default tracking configuration or should we just allow it to fail?
120+
if target_tracking_configuration
121+
self.target_tracking_configuration = Fog::AWS.map_from_aws(target_tracking_configuration, TARGET_TRACKING_MAPPING)
122+
end
123+
end
124+
125+
def prepare_step_policy
126+
# do we need any default scaling steps or should we just allow it to fail?
127+
self.adjustment_type ||= 'ChangeInCapacity'
128+
if step_adjustments
129+
self.step_adjustments = Fog::AWS.map_from_aws(step_adjustments, STEP_ADJUSTMENTS_MAPPING)
130+
end
131+
end
132+
133+
def type_requirements
134+
requires :id
135+
requires :auto_scaling_group_name
136+
case self.type
137+
when 'StepScaling'
138+
requires :step_adjustments
139+
when 'TargetTrackingScaling'
140+
requires :target_tracking_configuration
141+
else
142+
requires :scaling_adjustment
143+
end
144+
end
41145
end
42146
end
43147
end

0 commit comments

Comments
 (0)