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.
- checksums.yaml +5 -5
- data/CODE_OF_CONDUCT.md +1 -1
- data/Gemfile +4 -2
- data/Gemfile.lock +1128 -524
- data/LICENSE.txt +1 -1
- data/Rakefile +134 -10
- data/bin/console +4 -3
- data/lib/rollo.rb +3 -1
- data/lib/rollo/commands.rb +2 -0
- data/lib/rollo/commands/hosts.rb +232 -0
- data/lib/rollo/commands/main.rb +78 -48
- data/lib/rollo/commands/services.rb +173 -0
- data/lib/rollo/model.rb +2 -0
- data/lib/rollo/model/host.rb +4 -2
- data/lib/rollo/model/host_cluster.rb +34 -23
- data/lib/rollo/model/scaling_activity.rb +8 -6
- data/lib/rollo/model/service.rb +37 -24
- data/lib/rollo/model/service_cluster.rb +19 -14
- data/lib/rollo/version.rb +3 -1
- metadata +145 -43
- data/.envrc +0 -5
- data/.gitignore +0 -31
- data/.rspec +0 -3
- data/.ruby-version +0 -1
- data/.travis.yml +0 -5
- data/exe/rollo +0 -5
- data/go +0 -61
- data/lib/rollo/commands/host_cluster.rb +0 -184
- data/lib/rollo/commands/service_cluster.rb +0 -124
- data/rollo.gemspec +0 -37
@@ -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
data/lib/rollo/model/host.rb
CHANGED
@@ -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
|
18
|
+
def in_service?
|
17
19
|
@instance.lifecycle_state == 'InService'
|
18
20
|
end
|
19
21
|
|
20
|
-
def
|
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
|
-
|
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:
|
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
|
42
|
+
def desired_capacity?
|
40
43
|
hosts.size == desired_capacity &&
|
41
|
-
|
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
|
51
|
+
def started_changing_capacity?
|
49
52
|
scaling_activities
|
50
|
-
|
51
|
-
|
53
|
+
.select { |a| a.started_after_completion_of?(@last_scaling_activity) }
|
54
|
+
.size.positive?
|
52
55
|
end
|
53
56
|
|
54
|
-
def
|
55
|
-
scaling_activities.all?(&:
|
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
|
-
|
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
|
-
|
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
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
112
|
-
|
113
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
28
|
-
%w
|
29
|
+
def complete?
|
30
|
+
%w[Successful Failed Cancelled].include?(@activity.status_code)
|
29
31
|
end
|
30
32
|
end
|
31
33
|
end
|
data/lib/rollo/model/service.rb
CHANGED
@@ -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
|
-
|
10
|
-
|
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:
|
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 =
|
31
|
+
@ecs_service = ecs_service
|
29
32
|
end
|
30
33
|
|
31
|
-
def
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
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
|
-
|
66
|
+
:prepare, initial, target
|
67
|
+
)
|
61
68
|
|
62
|
-
ensure_instance_count(
|
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
|
-
|
79
|
+
:prepare, initial, target
|
80
|
+
)
|
71
81
|
|
72
|
-
ensure_instance_count(
|
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
|
-
|
84
|
-
|
85
|
-
|
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
|
103
|
+
def ecs_service
|
92
104
|
@ecs_resource.client
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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)
|