elasticsearch-drain 0.0.6 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5636da7177de03b2cc46e1f5f9c3382c542c25b1
4
- data.tar.gz: ae9b51d1c2ace5e83497a6a92ba956523d713e01
3
+ metadata.gz: 59b1a35ac25472ad0caf43d4f30b5370bf59250f
4
+ data.tar.gz: 43dfb6c5d57c216ad4a947d86402c13ecd8148b4
5
5
  SHA512:
6
- metadata.gz: 7bbe3a3434ff69a13826251cb29561a6d95431294a7bc8590883480edbba598ff206c4d17d048a17a053e95fae4fb53f19ddf624599bf334215fa0b79ab34c1b
7
- data.tar.gz: dfec04ba5e72334f4d75a597b512230f93670374d22afcbfc92f4b6e3a0683a2cf191394dba4072b84c68fefdf25f1def1417984d5a44f1f4d6a2616142ec346
6
+ metadata.gz: 9844b71974cd6f7b838e7eb5a40de1f0d0440e0108c4dbe26dfa827329aeb95e7306624e22d2a2d5946bd9d3f8cd193bbb276e8a3bb9a5e4cffe60857a77223e
7
+ data.tar.gz: 3445323f27e4b549e27de00af09285cfec0ab9db3fa9a648c261a7b80d657a957d306008e754344432183359ca815f4938122e9ef2180eb7c5dc3b90367092e9
@@ -14,25 +14,25 @@ GEM
14
14
  ast (2.1.0)
15
15
  astrolabe (1.3.1)
16
16
  parser (~> 2.2)
17
- aws-sdk (2.4.4)
18
- aws-sdk-resources (= 2.4.4)
19
- aws-sdk-core (2.4.4)
17
+ aws-sdk (2.6.14)
18
+ aws-sdk-resources (= 2.6.14)
19
+ aws-sdk-core (2.6.14)
20
20
  jmespath (~> 1.0)
21
- aws-sdk-resources (2.4.4)
22
- aws-sdk-core (= 2.4.4)
21
+ aws-sdk-resources (2.6.14)
22
+ aws-sdk-core (= 2.6.14)
23
23
  coderay (1.1.0)
24
24
  crack (0.4.2)
25
25
  safe_yaml (~> 1.0.0)
26
26
  docile (1.1.5)
27
- elasticsearch (1.0.14)
28
- elasticsearch-api (= 1.0.14)
29
- elasticsearch-transport (= 1.0.14)
30
- elasticsearch-api (1.0.14)
27
+ elasticsearch (1.1.0)
28
+ elasticsearch-api (= 1.1.0)
29
+ elasticsearch-transport (= 1.1.0)
30
+ elasticsearch-api (1.1.0)
31
31
  multi_json
32
32
  elasticsearch-extensions (0.0.18)
33
33
  ansi
34
34
  ruby-prof
35
- elasticsearch-transport (1.0.14)
35
+ elasticsearch-transport (1.1.0)
36
36
  faraday
37
37
  multi_json
38
38
  faraday (0.9.2)
@@ -127,3 +127,6 @@ DEPENDENCIES
127
127
  vcr (~> 2.9)
128
128
  webmock (~> 1.21)
129
129
  yard (~> 0.8)
130
+
131
+ BUNDLED WITH
132
+ 1.13.2
@@ -38,12 +38,12 @@ module Elasticsearch
38
38
  @asg_client ||= AutoScaling.new(@asg_name, @region)
39
39
  end
40
40
 
41
- # Convience method to access {Elasticsearch::Drain::Nodes}
41
+ # Convenience method to access {Elasticsearch::Drain::Nodes}
42
42
  def nodes
43
43
  @nodes ||= Nodes.new(client, asg)
44
44
  end
45
45
 
46
- # Convience method to access {Elasticsearch::Drain::Cluster#cluster}
46
+ # Convenience method to access {Elasticsearch::Drain::Cluster#cluster}
47
47
  #
48
48
  # @return [Elasticsearch::API::Cluster] Elasticsearch cluster client
49
49
  def cluster
@@ -8,20 +8,27 @@ module Elasticsearch
8
8
  attr_reader :asg
9
9
 
10
10
  # @attribute [r]
11
- # EC2 Client
12
- attr_reader :ec2_client
11
+ # AWS region
12
+ attr_reader :region
13
13
 
14
14
  def initialize(asg, region)
15
15
  @asg = asg
16
- @asg_client = Aws::AutoScaling::Client.new(region: region)
17
- @ec2_client = Aws::EC2::Client.new(region: region)
16
+ @region = region
18
17
  @instances = nil
19
18
  @instance_ids = nil
20
19
  end
21
20
 
21
+ def asg_client
22
+ Aws::AutoScaling::Client.new(region: region)
23
+ end
24
+
25
+ def ec2_client
26
+ Aws::EC2::Client.new(region: region)
27
+ end
28
+
22
29
  def find_instances_in_asg
23
30
  instances = []
24
- @asg_client.describe_auto_scaling_instances.each do |page|
31
+ asg_client.describe_auto_scaling_instances.each do |page|
25
32
  instances << page.auto_scaling_instances.map do |i|
26
33
  i.instance_id if i.auto_scaling_group_name == asg
27
34
  end
@@ -38,7 +45,7 @@ module Elasticsearch
38
45
  instances = []
39
46
  find_instances_in_asg if @instance_ids.nil?
40
47
  return [] if @instance_ids.empty?
41
- @ec2_client.describe_instances(instance_ids: @instance_ids).each do |page|
48
+ ec2_client.describe_instances(instance_ids: @instance_ids).each do |page|
42
49
  instances << page.reservations.map(&:instances)
43
50
  end
44
51
  instances.flatten!
@@ -49,7 +56,7 @@ module Elasticsearch
49
56
  #
50
57
  # @return [Struct] AutoScaling Group
51
58
  def describe_autoscaling_group
52
- groups = @asg_client.describe_auto_scaling_groups(
59
+ groups = asg_client.describe_auto_scaling_groups(
53
60
  auto_scaling_group_names: [asg]
54
61
  )
55
62
  groups.auto_scaling_groups.first
@@ -81,11 +88,11 @@ module Elasticsearch
81
88
  # @option [FixNum] count (0) The new MinSize of the AutoScalingGroup
82
89
  # @return [Struct] Empty response from the sdk
83
90
  def min_size=(count = 0)
84
- @asg_client.update_auto_scaling_group(
91
+ asg_client.update_auto_scaling_group(
85
92
  auto_scaling_group_name: asg,
86
93
  min_size: count
87
94
  )
88
- wait_until(0) do
95
+ wait_until(count) do
89
96
  min_size
90
97
  end
91
98
  end
@@ -108,7 +115,7 @@ module Elasticsearch
108
115
 
109
116
  def detach_instance(instance_id)
110
117
  current_desired_capacity = desired_capacity
111
- @asg_client.detach_instances(
118
+ asg_client.detach_instances(
112
119
  instance_ids: [instance_id],
113
120
  auto_scaling_group_name: asg,
114
121
  should_decrement_desired_capacity: true
@@ -2,35 +2,93 @@ require 'thor'
2
2
 
3
3
  module Elasticsearch
4
4
  class Drain
5
- class CLI < ::Thor
5
+ class CLI < ::Thor # rubocop:disable Metrics/ClassLength
6
6
  package_name :elasticsearch
7
7
 
8
8
  attr_reader :drainer
9
9
  attr_accessor :active_nodes
10
10
 
11
+ # rubocop:disable Metrics/LineLength
11
12
  desc 'asg', 'Drain all documents from all nodes in an EC2 AutoScaling Group'
12
13
  option :host, default: 'localhost:9200'
13
14
  option :asg, required: true
14
15
  option :region, required: true
15
- option :nodes
16
+ option :nodes, type: :array, desc: 'A comma separated list of node IDs to drain. If specified, the --number option has no effect'
17
+ option :number, type: :numeric, desc: 'The number of nodes to drain'
18
+ option :continue, type: :boolean, default: true, desc: 'Whether to continue draining nodes once the first iteration of --number is complete'
19
+ # rubocop:enable Metrics/LineLength
16
20
  def asg # rubocop:disable Metrics/MethodLength
17
21
  @drainer = Elasticsearch::Drain.new(options[:host],
18
22
  options[:asg],
19
23
  options[:region])
24
+
20
25
  ensure_cluster_healthy
21
26
  @active_nodes = drainer.active_nodes_in_asg
22
27
 
28
+ # If :nodes are specified, :number has no effect
29
+ if options[:nodes]
30
+ say "Nodes #{options[:nodes].join(', ')} have been specified, the --number option has no effect"
31
+ number_to_drain = nil
32
+ currently_draining_nodes = nil
33
+ else
34
+ number_to_drain = options[:number]
35
+ currently_draining_nodes = drainer.cluster.currently_draining('_id')
36
+ end
37
+
23
38
  # If a node or nodes are specified, only drain the requested node(s)
24
39
  @active_nodes = active_nodes.find_all do |n|
25
40
  instance_id = drainer.asg.instance(n.ipaddress).instance_id
26
- options[:nodes].split(',').include?(instance_id)
41
+ options[:nodes].include?(instance_id)
27
42
  end if options[:nodes]
28
43
 
29
44
  do_exit { say_status 'Complete', 'Nothing to do', :green } if active_nodes.empty?
30
- say_status 'Found Nodes', "AutoScalingGroup: #{instances}", :magenta
31
- ensure_cluster_healthy
32
- drain_nodes
33
- remove_nodes
45
+ say_status 'Found Nodes', "AutoScalingGroup: #{instances(active_nodes)}", :magenta
46
+
47
+ until active_nodes.empty?
48
+ ensure_cluster_healthy
49
+
50
+ nodes = active_nodes
51
+
52
+ # If there are nodes in cluster settings "transient.cluster.routing.allocation.exclude"
53
+ # test if those nodes are still in the ASG. If so, work on them first unless nodes are
54
+ # specified.
55
+ if currently_draining_nodes
56
+ nodes_to_drain = active_nodes.find_all { |n| currently_draining_nodes.split(',').include?(n.id) }
57
+
58
+ # If the list of nodes_to_drain isn't empty, we want to set nodes to the list of nodes
59
+ # we've already been working on.
60
+ unless nodes_to_drain.empty?
61
+ nodes = nodes_to_drain
62
+
63
+ say_status 'Active Nodes', "Resuming drain process on #{instances(nodes)}", :magenta
64
+ end
65
+
66
+ # We should only process currently_draining_nodes once
67
+ currently_draining_nodes = nil
68
+ end
69
+
70
+ # If we specify a number but DON'T specify nodes, sample the active_nodes.
71
+ if number_to_drain
72
+ nodes = nodes.sample(number_to_drain.to_i)
73
+ say_status 'Active Nodes', "Sampled #{number_to_drain} nodes and got #{instances(nodes)}", :magenta
74
+ end
75
+
76
+ @active_nodes = nodes unless options[:continue]
77
+
78
+ drain_nodes(nodes)
79
+ remove_nodes(nodes)
80
+
81
+ # Remove the drained nodes from the list of active_nodes
82
+ @active_nodes -= nodes
83
+
84
+ unless active_nodes.empty?
85
+ say_status 'Drain Nodes', "#{active_nodes.length} nodes remaining", :green
86
+
87
+ sleep_time = wait_sleep_time
88
+ say_status 'Waiting', "Sleeping for #{sleep_time} seconds before the next iteration", :green
89
+ sleep sleep_time
90
+ end
91
+ end
34
92
  say_status 'Complete', 'Draining nodes complete!', :green
35
93
  end
36
94
 
@@ -48,42 +106,53 @@ module Elasticsearch
48
106
  exit code
49
107
  end
50
108
 
51
- def instances
52
- instances = active_nodes.map(&:ipaddress)
109
+ def instances(nodes)
110
+ instances = nodes.map(&:ipaddress)
53
111
  instances.join(' ')
54
112
  end
55
113
 
56
- def adjusted_min_size
114
+ def adjusted_min_size(nodes)
57
115
  min_size = drainer.asg.min_size
58
116
  desired_capacity = drainer.asg.desired_capacity
59
- if (desired_capacity - active_nodes.length) >= min_size # Removing the active_nodes won't violate the min_size
60
- # Reduce the asg min_size proportionally
61
- desired_min_size = (min_size - active_nodes.length) <= 0 ? 0 : (min_size - active_nodes.length)
62
- else
63
- # Removing the active_nodes will result in the min_size being violated
64
- desired_min_size = desired_capacity - active_nodes.length
65
- end
117
+ desired_min_size = if (desired_capacity - nodes.length) >= min_size # Removing the nodes won't violate the min_size
118
+ # Reduce the asg min_size proportionally
119
+ (min_size - nodes.length) <= 0 ? 0 : (min_size - nodes.length)
120
+ else
121
+ # Removing the nodes will result in the min_size being violated
122
+ (desired_capacity - nodes.length) <= 0 ? 0 : (desired_capacity - nodes.length)
123
+ end
66
124
  desired_min_size
67
125
  end
68
126
 
69
- def drain_nodes
70
- drainer.asg.min_size = adjusted_min_size
71
- nodes_to_drain = active_nodes.map(&:id).join(',')
127
+ def drain_nodes(nodes)
128
+ drainer.asg.min_size = adjusted_min_size(nodes)
129
+ nodes_to_drain = nodes.map(&:id).join(',')
72
130
  say_status 'Drain Nodes', "Draining nodes: #{nodes_to_drain}", :magenta
73
131
  drainer.cluster.drain_nodes(nodes_to_drain, '_id')
74
132
  end
75
133
 
76
- def remove_nodes # rubocop:disable Metrics/MethodLength
77
- while active_nodes.length > 0
78
- active_nodes.each do |instance|
79
- instance = drainer.nodes.filter_nodes([instance], true).first
134
+ def wait_sleep_time
135
+ ips = active_nodes.map(&:ipaddress)
136
+ bytes = drainer.nodes.filter_nodes(ips).map(&:bytes_stored)
137
+ sleep_time = 10
138
+ sleep_time = 30 if bytes.any? { |b| b >= 100_000 }
139
+ sleep_time = 60 if bytes.any? { |b| b >= 1_000_000 }
140
+ sleep_time = 120 if bytes.any? { |b| b >= 10_000_000_000 }
141
+ sleep_time
142
+ end
143
+
144
+ def remove_nodes(nodes) # rubocop:disable Metrics/MethodLength
145
+ while nodes.length > 0
146
+ sleep_time = wait_sleep_time
147
+ nodes.each do |instance|
148
+ instance = drainer.nodes.filter_nodes([instance.ipaddress], true).first
80
149
  if instance.bytes_stored > 0
81
150
  say_status 'Drain Status', "Node #{instance.ipaddress} has #{instance.bytes_stored} bytes to move", :blue
82
- sleep 2
151
+ sleep sleep_time
83
152
  else
84
153
  next unless remove_node(instance)
85
- active_nodes.delete_if { |n| n.ipaddress == instance.ipaddress }
86
- break if active_nodes.length < 1
154
+ nodes.delete_if { |n| n.ipaddress == instance.ipaddress }
155
+ break if nodes.length < 1
87
156
  say_status 'Waiting', 'Sleeping for 1 minute before removing the next node', :green
88
157
  sleep 60
89
158
  end
@@ -97,7 +166,8 @@ module Elasticsearch
97
166
  say_status(
98
167
  'Removing Node',
99
168
  "Removing #{instance.ipaddress} from Elasticsearch cluster and #{drainer.asg.asg} AutoScalingGroup",
100
- :magenta)
169
+ :magenta
170
+ )
101
171
  sleep 5 unless instance.in_recovery?
102
172
  node = "#{instance.instance_id}(#{instance.ipaddress})"
103
173
  ensure_cluster_healthy
@@ -34,6 +34,11 @@ module Elasticsearch
34
34
  }
35
35
  )
36
36
  end
37
+
38
+ def currently_draining(exclude_by = '_ip')
39
+ settings = cluster.get_settings(:flat_settings => true)
40
+ settings.fetch('transient', {}).fetch("cluster.routing.allocation.exclude.#{exclude_by}", nil)
41
+ end
37
42
  end
38
43
  end
39
44
  end
@@ -99,7 +99,7 @@ module Elasticsearch
99
99
  @asg.ec2_client.wait_until(:instance_terminated,
100
100
  instance_ids: [instance_id]) do |w|
101
101
  w.max_attempts = 10
102
- w.delay = 30
102
+ w.delay = 60
103
103
  end
104
104
  end
105
105
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elasticsearch-drain
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Thompson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-08-09 00:00:00.000000000 Z
11
+ date: 2020-08-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler