Class: CfnManage::StartStopHandler::Asg

Inherits:
Object
  • Object
show all
Defined in:
lib/cfn_manage/handlers/asg.rb

Instance Method Summary collapse

Constructor Details

#initialize(asg_id, options = {}) ⇒ Asg

Returns a new instance of Asg.



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/cfn_manage/handlers/asg.rb', line 11

def initialize(asg_id, options = {})
  @asg_name = asg_id
  @wait_state = options.has_key?(:wait_state) ? options[:wait_state] : CfnManage.asg_wait_state
  @skip_wait = options.has_key?(:skip_wait) ? CfnManage.true?(options[:skip_wait]) : CfnManage.skip_wait? 
  @suspend_termination = options.has_key?(:suspend_termination) ? CfnManage.true?(options[:suspend_termination]) : CfnManage.asg_suspend_termination?
  
  credentials = CfnManage::AWSCredentials.get_session_credentials("stopasg_#{@asg_name}")
  @asg_client = Aws::AutoScaling::Client.new(retry_limit: 20)
  @ec2_client = Aws::EC2::Client.new(retry_limit: 20)
  @elb_client = Aws::ElasticLoadBalancingV2::Client.new(retry_limit: 20)
  if credentials != nil
    @asg_client = Aws::AutoScaling::Client.new(credentials: credentials, retry_limit: 20)
    @ec2_client = Aws::EC2::Client.new(credentials: credentials, retry_limit: 20)
    @elb_client = Aws::ElasticLoadBalancingV2::Client.new(credentials: credentials, retry_limit: 20)
  end

  asg_details = @asg_client.describe_auto_scaling_groups(
      auto_scaling_group_names: [@asg_name]
  )
  if asg_details.auto_scaling_groups.size() == 0
    raise "Couldn't find ASG #{@asg_name}"
  end
  @asg = asg_details.auto_scaling_groups[0]
end

Instance Method Details

#start(configuration) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/cfn_manage/handlers/asg.rb', line 99

def start(configuration)
  if configuration.nil?
    $log.warn("No configuration found for #{@asg_name}, skipping..")
    return
  end
  $log.info("Starting ASG #{@asg_name} with following configuration\n#{configuration}")

  unless @suspend_termination
    # restore asg sizes
    @asg_client.update_auto_scaling_group({
      auto_scaling_group_name: @asg_name,
      min_size: configuration['min_size'],
      max_size: configuration['max_size'],
      desired_capacity: configuration['desired_capacity']
    })
    
  else

    $log.info("Starting instances for ASG #{@asg_name}...")

    @asg.instances.each do |instance|
      @instance_id = instance.instance_id
      @instance = Aws::EC2::Resource.new(client: @ec2_client, retry_limit: 20).instance(@instance_id)

      if %w(running).include?(@instance.state.name)
        $log.info("Instance #{@instance_id} already running")
        return
      end
      $log.info("Starting instance #{@instance_id}")
      @instance.start()
    end
    
  end
  
  if configuration['desired_capacity'] == 0
    # if ASG desired count is purposfully set to 0 and we want to wait for other ASG's
    # int the stack, then we need to skip wait for this ASG.
    $log.info("Desired capacity is 0, skipping wait for asg #{@asg_name}")
  elsif @skip_wait && @suspend_termination
    # If wait is skipped we still need to wait until the instances are healthy in the asg
    # before resuming the processes. This will avoid the asg terminating the instances.
    wait('HealthyInASG')
  elsif !@skip_wait
    # if we are waiting for the instances to reach a desired state
    $log.info("Waiting for ASG instances wait state #{@wait_state}")
    wait(@wait_state)
  end
  
  if @suspend_termination
    # resume the asg processes after we've waited for them to become healthy
    $log.info("Resuming all processes for ASG #{@asg_name}")

    @asg_client.resume_processes({
      auto_scaling_group_name: "#{@asg.auto_scaling_group_name}",
    })

    if configuration.key?(:suspended_processes)

      $log.info("Suspending processes stored in configuration for ASG #{@asg_name}")

      @asg_client.suspend_processes({
        auto_scaling_group_name: "#{@asg.auto_scaling_group_name}",
        scaling_processes: configuration['suspended_processes'],
      })
    end

  end

end

#stopObject



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/cfn_manage/handlers/asg.rb', line 36

def stop
  # check if already stopped
  if @asg.min_size == @asg.max_size and @asg.max_size == @asg.desired_capacity and @asg.min_size == 0
    $log.info("ASG #{@asg_name} already stopped")
    # nil and false configurations are not saved
    return nil
  else

    unless @suspend_termination
      # store asg configuration to S3
      configuration = {
          desired_capacity: @asg.desired_capacity,
          min_size: @asg.min_size,
          max_size: @asg.max_size
      }

      $log.info("Setting desired capacity to 0/0/0 for ASG #{@asg.auto_scaling_group_name}A")

      @asg_client.update_auto_scaling_group({
          auto_scaling_group_name: "#{@asg.auto_scaling_group_name}",
          min_size: 0,
          max_size: 0,
          desired_capacity: 0
      })
      return configuration
    else

      configuration = {
        desired_capacity: @asg.desired_capacity,
        min_size: @asg.min_size,
        max_size: @asg.max_size,
        suspended_processes: @asg.suspended_processes
      }

      $log.info("Suspending processes for ASG #{@asg.auto_scaling_group_name}A")

      @asg_client.suspend_processes({
        auto_scaling_group_name: "#{@asg.auto_scaling_group_name}",
      })

      $log.info("Stopping all instances in ASG #{@asg.auto_scaling_group_name}A")

      @asg.instances.each do |instance|
        @instance_id = instance.instance_id
        @instance = Aws::EC2::Resource.new(client: @ec2_client, retry_limit: 20).instance(@instance_id)

        if %w(stopped stopping).include?(@instance.state.name)
          $log.info("Instance #{@instance_id} already stopping or stopped")
          return
        end

        $log.info("Stopping instance #{@instance_id}")
        @instance.stop()
      end

      return configuration

    end

  end

end

#wait(type) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/cfn_manage/handlers/asg.rb', line 169

def wait(type)
  
  attempts = 0
  
  until attempts == (max_attempts = 60*6) do
    
    case type
    when 'HealthyInASG'
      success = wait_till_healthy_in_asg()
    when 'Running'
      success = wait_till_running()
    when 'HealthyInTargetGroup'
      success = wait_till_healthy_in_target_group()
    else
      $log.warn("unknown asg wait type #{type}. skipping...")
      break
    end
    
    if success
      break
    end
    
    attempts = attempts + 1
    sleep(15)
  end

  if attempts == max_attempts
    $log.error("Failed to wait for asg with wait type #{type}")
  end
end

#wait_till_healthy_in_asgObject



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/cfn_manage/handlers/asg.rb', line 200

def wait_till_healthy_in_asg

  asg_curr_details = @asg_client.describe_auto_scaling_groups(
    auto_scaling_group_names: [@asg_name]
  )
  
  asg_status = asg_curr_details.auto_scaling_groups.first
  health_status = asg_status.instances.collect { |inst| inst.health_status }
  $log.info("ASG #{@asg_name} health status is currently #{health_status}")

  if health_status.empty?
    $log.info("ASG #{@asg_name} has not started any instances yet")
    return false
  end
  
  if health_status.all? "Healthy"
    $log.info("All instances healthy in ASG #{@asg_name}")
    return true
  end
    
  unhealthy = asg_status.instances.select {|inst| inst.health_status == "Unhealthy" }.collect {|inst| inst.instance_id }
  $log.info("waiting for instances #{unhealthy} to become healthy in asg #{@asg_name}")
  return false
  
end

#wait_till_healthy_in_target_groupObject



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/cfn_manage/handlers/asg.rb', line 259

def wait_till_healthy_in_target_group
    
  asg_curr_details = @asg_client.describe_auto_scaling_groups(
    auto_scaling_group_names: [@asg_name]
  )
  asg_status = asg_curr_details.auto_scaling_groups.first
  asg_instances = asg_status.instances.collect { |inst| inst.instance_id }
  target_groups = asg_status.target_group_arns
  
  if asg_instances.empty?
    $log.info("ASG #{@asg_name} has not started any instances yet")
    return false
  end
  
  if target_groups.empty?
    # we want to skip here if the asg is not associated with any target groups
    $log.info("ASG #{@asg_name} is not associated with any target groups")
    return true
  end
  
  target_health = []
  target_groups.each do |tg| 
    resp = @elb_client.describe_target_health({
      target_group_arn: tg, 
    })
    if resp.target_health_descriptions.length != asg_instances.length
      # we need to wait until all asg insatnces have been placed into the target group 
      # before we can check they're healthy
      $log.info("All ASG instances haven't been placed into target group #{tg.split('/')[1]} yet")
      return false
    end
    target_health.push(*resp.target_health_descriptions)
  end
        
  state = target_health.collect {|tg| tg.target_health.state}
  
  if state.all? 'healthy'
    $log.info("All instances are in a healthy state in target groups #{target_groups.map {|tg| tg.split('/')[1] }}")
    return true
  end
  
  unhealthy = target_health.select {|tg| tg.target_health.state != 'healthy'}
  unhealthy.each do |tg|
    $log.info("waiting for instances #{tg.target.id} to be healthy in target group. Current state is #{tg.target_health.state}")
  end
  
  return false
  
end

#wait_till_runningObject



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/cfn_manage/handlers/asg.rb', line 226

def wait_till_running
    
  asg_curr_details = @asg_client.describe_auto_scaling_groups(
    auto_scaling_group_names: [@asg_name]
  )
  asg_status = asg_curr_details.auto_scaling_groups.first
  instances = asg_status.instances.collect { |inst| inst.instance_id }
  
  if instances.empty?
    $log.info("ASG #{@asg_name} has not started any instances yet")
    return false
  end
  
  status = @ec2_client.describe_instance_status({
    instance_ids: instances
  })
  
  state = status.instance_statuses.collect {|inst| inst.instance_state.name}
  
  if state.all? "running"
    $log.info("All instances in a running state from ASG #{@asg_name}")
    return true
  end
  
  not_running = @status.instance_statuses.select {|inst| inst.instance_state.name != "running" }
  not_running.each do |inst|
    $log.info("waiting for instances #{inst.instance_id} to be running. Current state is #{inst.instance_state.name}")
  end
  
  return false
  
end