rollo 0.3.0 → 0.8.0.pre.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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)