rollo 0.3.0 → 0.8.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require_relative '../model'
5
+
6
+ module Rollo
7
+ module Commands
8
+ # rubocop:disable Metrics/ClassLength
9
+ class Services < Thor
10
+ namespace :services
11
+
12
+ def self.exit_on_failure?
13
+ true
14
+ end
15
+
16
+ desc(
17
+ 'expand REGION ASG_NAME ECS_CLUSTER_NAME',
18
+ 'Expands the service cluster by one batch.'
19
+ )
20
+ method_option(
21
+ :batch_size,
22
+ aliases: '-b',
23
+ type: :numeric,
24
+ default: 3,
25
+ desc: 'The number of service instances to add at a time.'
26
+ )
27
+ method_option(
28
+ :startup_time,
29
+ aliases: '-t',
30
+ type: :numeric,
31
+ default: 2,
32
+ desc: 'The number of minutes to wait for services to start up.'
33
+ )
34
+ method_option(
35
+ :maximum_instances,
36
+ aliases: '-mx',
37
+ type: :numeric,
38
+ desc: 'The maximum number of service instances to expand to.'
39
+ )
40
+ # rubocop:disable Metrics/AbcSize
41
+ # rubocop:disable Metrics/MethodLength
42
+ def expand(
43
+ region, _, ecs_cluster_name,
44
+ service_cluster = nil
45
+ )
46
+ batch_size = options[:batch_size]
47
+ maximum_instances = options[:maximum_instances]
48
+ service_start_wait_minutes = options[:startup_time]
49
+ service_start_wait_seconds = 60 * service_start_wait_minutes
50
+
51
+ service_cluster ||= Rollo::Model::ServiceCluster.new(ecs_cluster_name,
52
+ region)
53
+
54
+ say("Increasing service instance counts by #{batch_size}...")
55
+ # rubocop:disable Metrics/BlockLength
56
+ with_padding do
57
+ service_cluster.with_replica_services do |on|
58
+ on.start do |services|
59
+ say(
60
+ 'Service cluster contains services:' \
61
+ "\n\t\t[#{services.map(&:name).join(",\n\t\t ")}]"
62
+ )
63
+ end
64
+ on.each_service do |service|
65
+ say(
66
+ "Increasing instance count by #{batch_size} " \
67
+ "for #{service.name}"
68
+ )
69
+ # rubocop:disable Lint/ShadowingOuterLocalVariable
70
+ with_padding do
71
+ service.increase_instance_count_by(
72
+ batch_size, maximum_instances: maximum_instances
73
+ ) do |on|
74
+ on.prepare do |current, target|
75
+ say(
76
+ "Changing instance count from #{current} " \
77
+ "to #{target}..."
78
+ )
79
+ end
80
+ on.waiting_for_health do |attempt|
81
+ say(
82
+ 'Waiting for service to reach a steady state ' \
83
+ "(attempt #{attempt})..."
84
+ )
85
+ end
86
+ end
87
+ end
88
+ # rubocop:enable Lint/ShadowingOuterLocalVariable
89
+ end
90
+ end
91
+ end
92
+ # rubocop:enable Metrics/BlockLength
93
+ say(
94
+ "Waiting #{service_start_wait_minutes} minute(s) for " \
95
+ 'services to finish starting...'
96
+ )
97
+ with_padding do
98
+ sleep(service_start_wait_seconds)
99
+ say(
100
+ "Waited #{service_start_wait_minutes} minute(s). " \
101
+ 'Continuing...'
102
+ )
103
+ end
104
+ say('Service instance counts increased, continuing...')
105
+ end
106
+ # rubocop:enable Metrics/AbcSize
107
+ # rubocop:enable Metrics/MethodLength
108
+
109
+ desc(
110
+ 'contract REGION ASG_NAME ECS_CLUSTER_NAME',
111
+ 'Contracts the service cluster by one batch.'
112
+ )
113
+ method_option(
114
+ :batch_size,
115
+ aliases: '-b',
116
+ type: :numeric,
117
+ default: 3,
118
+ desc: 'The number of service instances to remove at a time.'
119
+ )
120
+ method_option(
121
+ :minimum_instances,
122
+ aliases: '-mn',
123
+ type: :numeric,
124
+ desc: 'The minimum number of service instances to contract to.'
125
+ )
126
+ # rubocop:disable Metrics/AbcSize
127
+ # rubocop:disable Metrics/MethodLength
128
+ def contract(
129
+ region, _, ecs_cluster_name,
130
+ service_cluster = nil
131
+ )
132
+ batch_size = options[:batch_size]
133
+
134
+ service_cluster ||= Rollo::Model::ServiceCluster.new(ecs_cluster_name,
135
+ region)
136
+
137
+ say("Decreasing service instance counts by #{batch_size}...")
138
+ with_padding do
139
+ service_cluster.with_replica_services do |on|
140
+ on.each_service do |service|
141
+ say(
142
+ "Decreasing instance count by #{batch_size} " \
143
+ "for #{service.name}"
144
+ )
145
+ # rubocop:disable Lint/ShadowingOuterLocalVariable
146
+ with_padding do
147
+ service.decrease_instance_count_by(batch_size) do |on|
148
+ on.prepare do |current, target|
149
+ say(
150
+ "Changing instance count from #{current} " \
151
+ "to #{target}..."
152
+ )
153
+ end
154
+ on.waiting_for_health do |attempt|
155
+ say(
156
+ 'Waiting for service to reach a steady state ' \
157
+ "(attempt #{attempt})..."
158
+ )
159
+ end
160
+ end
161
+ end
162
+ # rubocop:enable Lint/ShadowingOuterLocalVariable
163
+ end
164
+ end
165
+ end
166
+ say('Service instance counts decreased, continuing...')
167
+ end
168
+ # rubocop:enable Metrics/AbcSize
169
+ # rubocop:enable Metrics/MethodLength
170
+ end
171
+ # rubocop:enable Metrics/ClassLength
172
+ end
173
+ end
data/lib/rollo/model.rb CHANGED
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'model/host_cluster'
2
4
  require_relative 'model/service_cluster'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rollo
2
4
  module Model
3
5
  class Host
@@ -13,11 +15,11 @@ module Rollo
13
15
  @instance.terminate(should_decrement_desired_capacity: false)
14
16
  end
15
17
 
16
- def is_in_service?
18
+ def in_service?
17
19
  @instance.lifecycle_state == 'InService'
18
20
  end
19
21
 
20
- def is_healthy?
22
+ def healthy?
21
23
  @instance.health_status == 'Healthy'
22
24
  end
23
25
  end
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'aws-sdk'
2
4
  require 'hollerback'
5
+ require 'wait'
3
6
 
4
7
  require_relative './scaling_activity'
5
8
  require_relative './host'
@@ -13,11 +16,11 @@ module Rollo
13
16
  @region = region
14
17
  @asg_name = asg_name
15
18
  @asg_resource = asg_resource ||
16
- Aws::AutoScaling::Resource.new(region: region)
19
+ Aws::AutoScaling::Resource.new(region: region)
17
20
  @asg = @asg_resource.group(@asg_name)
18
21
  record_latest_scaling_activity
19
22
 
20
- @waiter = waiter || Wait.new(attempts: 300, timeout: 30, delay: 5)
23
+ @waiter = waiter || Wait.new(attempts: 720, timeout: 30, delay: 5)
21
24
  end
22
25
 
23
26
  def reload
@@ -33,30 +36,30 @@ module Rollo
33
36
  end
34
37
 
35
38
  def desired_capacity=(capacity)
36
- @asg.set_desired_capacity({desired_capacity: capacity})
39
+ @asg.set_desired_capacity({ desired_capacity: capacity })
37
40
  end
38
41
 
39
- def has_desired_capacity?
42
+ def desired_capacity?
40
43
  hosts.size == desired_capacity &&
41
- hosts.all? {|h| h.is_in_service? && h.is_healthy?}
44
+ hosts.all? { |h| h.in_service? && h.healthy? }
42
45
  end
43
46
 
44
47
  def scaling_activities
45
- @asg.activities.collect {|a| ScalingActivity.new(a)}
48
+ @asg.activities.collect { |a| ScalingActivity.new(a) }
46
49
  end
47
50
 
48
- def has_started_changing_capacity?
51
+ def started_changing_capacity?
49
52
  scaling_activities
50
- .select {|a| a.started_after_completion_of?(@last_scaling_activity)}
51
- .size > 0
53
+ .select { |a| a.started_after_completion_of?(@last_scaling_activity) }
54
+ .size.positive?
52
55
  end
53
56
 
54
- def has_completed_changing_capacity?
55
- scaling_activities.all?(&:is_complete?)
57
+ def completed_changing_capacity?
58
+ scaling_activities.all?(&:complete?)
56
59
  end
57
60
 
58
61
  def hosts
59
- @asg.instances.collect {|h| Host.new(h)}
62
+ @asg.instances.collect { |h| Host.new(h) }
60
63
  end
61
64
 
62
65
  def increase_capacity_by(capacity_delta, &block)
@@ -64,7 +67,8 @@ module Rollo
64
67
  increased = initial + capacity_delta
65
68
 
66
69
  callbacks_for(block).try_respond_with(
67
- :prepare, initial, increased)
70
+ :prepare, initial, increased
71
+ )
68
72
 
69
73
  ensure_capacity_changed_to(increased, &block)
70
74
  end
@@ -74,7 +78,8 @@ module Rollo
74
78
  decreased = initial - capacity_delta
75
79
 
76
80
  callbacks_for(block).try_respond_with(
77
- :prepare, initial, decreased)
81
+ :prepare, initial, decreased
82
+ )
78
83
 
79
84
  ensure_capacity_changed_to(decreased, &block)
80
85
  end
@@ -90,27 +95,33 @@ module Rollo
90
95
  def wait_for_capacity_change_start(&block)
91
96
  @waiter.until do |attempt|
92
97
  reload
93
- callbacks_for(block)
94
- .try_respond_with(:waiting_for_start, attempt) if block
95
- has_started_changing_capacity?
98
+ if block
99
+ callbacks_for(block)
100
+ .try_respond_with(:waiting_for_start, attempt)
101
+ end
102
+ started_changing_capacity?
96
103
  end
97
104
  end
98
105
 
99
106
  def wait_for_capacity_change_end(&block)
100
107
  @waiter.until do |attempt|
101
108
  reload
102
- callbacks_for(block)
103
- .try_respond_with(:waiting_for_end, attempt) if block
104
- has_completed_changing_capacity?
109
+ if block
110
+ callbacks_for(block)
111
+ .try_respond_with(:waiting_for_end, attempt)
112
+ end
113
+ completed_changing_capacity?
105
114
  end
106
115
  end
107
116
 
108
117
  def wait_for_capacity_health(&block)
109
118
  @waiter.until do |attempt|
110
119
  reload
111
- callbacks_for(block)
112
- .try_respond_with(:waiting_for_health, attempt) if block
113
- has_desired_capacity?
120
+ if block
121
+ callbacks_for(block)
122
+ .try_respond_with(:waiting_for_health, attempt)
123
+ end
124
+ desired_capacity?
114
125
  end
115
126
  end
116
127
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rollo
2
4
  module Model
3
5
  class ScalingActivity
@@ -18,14 +20,14 @@ module Rollo
18
20
  end
19
21
 
20
22
  def started_after_completion_of?(other)
21
- self.id != other.id &&
22
- !self.start_time.nil? &&
23
- !other.end_time.nil? &&
24
- self.start_time > other.end_time
23
+ id != other.id &&
24
+ !start_time.nil? &&
25
+ !other.end_time.nil? &&
26
+ start_time > other.end_time
25
27
  end
26
28
 
27
- def is_complete?
28
- %w(Successful Failed Cancelled).include?(@activity.status_code)
29
+ def complete?
30
+ %w[Successful Failed Cancelled].include?(@activity.status_code)
29
31
  end
30
32
  end
31
33
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'aws-sdk'
2
4
  require 'wait'
3
5
  require 'hollerback'
@@ -6,14 +8,15 @@ module Rollo
6
8
  module Model
7
9
  class Service
8
10
  def initialize(
9
- ecs_cluster_name, ecs_service_arn, region,
10
- ecs_resource = nil, waiter = nil)
11
+ ecs_cluster_name, ecs_service_arn, region,
12
+ ecs_resource = nil, waiter = nil
13
+ )
11
14
  @ecs_cluster_name = ecs_cluster_name
12
15
  @ecs_service_arn = ecs_service_arn
13
16
  @ecs_resource = ecs_resource || Aws::ECS::Resource.new(region: region)
14
17
  reload
15
18
 
16
- @waiter = waiter || Wait.new(attempts: 300, timeout: 30, delay: 5)
19
+ @waiter = waiter || Wait.new(attempts: 720, timeout: 30, delay: 5)
17
20
  end
18
21
 
19
22
  def name
@@ -25,10 +28,10 @@ module Rollo
25
28
  end
26
29
 
27
30
  def reload
28
- @ecs_service = get_ecs_service
31
+ @ecs_service = ecs_service
29
32
  end
30
33
 
31
- def is_replica?
34
+ def replica?
32
35
  @ecs_service.scheduling_strategy == 'REPLICA'
33
36
  end
34
37
 
@@ -42,34 +45,41 @@ module Rollo
42
45
 
43
46
  def desired_count=(count)
44
47
  @ecs_resource.client
45
- .update_service(
46
- cluster: @ecs_cluster_name,
47
- service: @ecs_service_arn,
48
- desired_count: count)
48
+ .update_service(
49
+ cluster: @ecs_cluster_name,
50
+ service: @ecs_service_arn,
51
+ desired_count: count
52
+ )
49
53
  end
50
54
 
51
- def has_desired_count?
55
+ def desired_count_met?
52
56
  running_count == desired_count
53
57
  end
54
58
 
55
- def increase_instance_count_by(count_delta, &block)
59
+ def increase_instance_count_by(count_delta, options = {}, &block)
60
+ maximum = options[:maximum_instance_count] || Float::INFINITY
56
61
  initial = desired_count
57
62
  increased = initial + count_delta
63
+ target = [increased, maximum].min
58
64
 
59
65
  callbacks_for(block).try_respond_with(
60
- :prepare, initial, increased)
66
+ :prepare, initial, target
67
+ )
61
68
 
62
- ensure_instance_count(increased, &block)
69
+ ensure_instance_count(target, &block)
63
70
  end
64
71
 
65
- def decrease_instance_count_by(count_delta, &block)
72
+ def decrease_instance_count_by(count_delta, options = {}, &block)
73
+ minimum = options[:minimum_instance_count] || 0
66
74
  initial = desired_count
67
75
  decreased = initial - count_delta
76
+ target = [decreased, minimum].max
68
77
 
69
78
  callbacks_for(block).try_respond_with(
70
- :prepare, initial, decreased)
79
+ :prepare, initial, target
80
+ )
71
81
 
72
- ensure_instance_count(decreased, &block)
82
+ ensure_instance_count(target, &block)
73
83
  end
74
84
 
75
85
  def ensure_instance_count(count, &block)
@@ -80,20 +90,23 @@ module Rollo
80
90
  def wait_for_service_health(&block)
81
91
  @waiter.until do |attempt|
82
92
  reload
83
- callbacks_for(block)
84
- .try_respond_with(:waiting_for_health, attempt) if block
85
- has_desired_count?
93
+ if block
94
+ callbacks_for(block)
95
+ .try_respond_with(:waiting_for_health, attempt)
96
+ end
97
+ desired_count_met?
86
98
  end
87
99
  end
88
100
 
89
101
  private
90
102
 
91
- def get_ecs_service
103
+ def ecs_service
92
104
  @ecs_resource.client
93
- .describe_services(
94
- cluster: @ecs_cluster_name,
95
- services: [@ecs_service_arn])
96
- .services[0]
105
+ .describe_services(
106
+ cluster: @ecs_cluster_name,
107
+ services: [@ecs_service_arn]
108
+ )
109
+ .services[0]
97
110
  end
98
111
 
99
112
  def callbacks_for(block)