aws-eni 0.3.1 → 0.4.0

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: 14eb7ab252066d43cf0f289c7d5de6e796fe6b95
4
- data.tar.gz: b96f9a46cc748d54bbef973a5af74fca467a7b22
3
+ metadata.gz: 9e3020c6ae20a0d6a7027f065560e00c5764dbfb
4
+ data.tar.gz: f4827152a5efad857d3a8818e7f882a5a25e15ca
5
5
  SHA512:
6
- metadata.gz: 48d1acfaaac7ca2ea0777305388fa1cac6f48c1b0a368dc9b08e493ff4a52274fc1813a34027b0c0858c27c4e3c91f3396df2090f1c9152f520133f0b0abbc75
7
- data.tar.gz: fb0c4f4a9b9309b8236c729c7ac438f62579a057accf41488c5e3822cb0cb509d52f30aa6dc393ea2f6253cf4c720f568a0ef45e3628ad368c2b2e659210dc7d
6
+ metadata.gz: bd1bb10082ba681b5452411d8f2e75a39cc9b6d7227e3e8b996de89b5d619072c3295a40c63db98e0e11bb2c938e8c01c7b736fd5122f6bd4da45d896a65b435
7
+ data.tar.gz: 9c0e454b7797af337b50829ee4231ee9ee2f3f558343980023f4bb5078f6fb0c17fe0d81e5954473b90e67669e25adbb710ffc808143bb9e7b9eeb2a47e52ed4
@@ -24,7 +24,7 @@ desc 'Display all system commands and warnings'
24
24
  switch [:V,:verbose], negatable: false
25
25
 
26
26
  pre do |opt|
27
- Aws::ENI.timeout 90
27
+ Aws::ENI.timeout 120
28
28
  Aws::ENI.verbose opt[:verbose]
29
29
  true
30
30
  end
@@ -398,11 +398,14 @@ command [:unassign] do |c|
398
398
  c.desc 'Release any associated public IP address'
399
399
  c.switch [:r,:release], negatable: false
400
400
 
401
+ c.desc 'Do not wait for new interface state to propogate'
402
+ c.switch [:n,:noblock], negatable: false
403
+
401
404
  c.action do |global,opts,args|
402
405
  params = parse_args args, :interface_id, :device_name, :private_ip
403
406
  help_now! "Missing argument" unless params[:private_ip]
404
- params[:release] = opts[:release]
405
407
 
408
+ params.merge! release: opts[:release], block: !opts[:noblock]
406
409
  unassign = Aws::ENI.unassign_secondary_ip(params[:private_ip], params)
407
410
  if unassign[:released]
408
411
  puts "EIP #{unassign[:public_ip]} (#{unassign[:allocation_id]}) dissociated from #{unassign[:private_ip]} and released"
@@ -463,11 +466,15 @@ command [:dissociate] do |c|
463
466
  c.desc 'Release the associated public IP address'
464
467
  c.switch [:r,:release], negatable: false
465
468
 
469
+ c.desc 'Do not wait for new interface state to propogate'
470
+ c.switch [:noblock], negatable: false
471
+
466
472
  c.action do |global,opts,args|
467
473
  params = parse_args args, :private_ip, :public_ip, :allocation_id, :association_id, :interface_id, :device_name
468
- params[:release] = opts[:release]
469
474
  address = params[:private_ip] || params[:public_ip] || params[:association_id] || params[:allocation_id]
470
475
  help_now! "Missing argument" unless address
476
+
477
+ params.merge! release: opts[:release], block: !opts[:noblock]
471
478
  dissoc = Aws::ENI.dissociate_elastic_ip(address, params)
472
479
  if dissoc[:released]
473
480
  puts "EIP #{dissoc[:public_ip]} (#{dissoc[:allocation_id]}) dissociated from #{dissoc[:private_ip]} on #{dissoc[:device_name]} (#{dissoc[:interface_id]}) and released"
@@ -9,16 +9,21 @@ module Aws
9
9
  module ENI
10
10
  extend self
11
11
 
12
+ # class level thread-safe lock
13
+ @lock = Mutex.new
14
+
12
15
  def environment
13
- @environment ||= Meta.connection do
14
- hwaddr = Meta.instance('network/interfaces/macs/').lines.first.strip.chomp('/')
15
- {
16
- instance_id: Meta.instance('instance-id'),
17
- availability_zone: Meta.instance('placement/availability-zone'),
18
- region: Meta.instance('placement/availability-zone').sub(/^(.*)[a-z]$/,'\1'),
19
- vpc_id: Meta.interface(hwaddr, 'vpc-id'),
20
- vpc_cidr: Meta.interface(hwaddr, 'vpc-ipv4-cidr-block')
21
- }.freeze
16
+ @lock.synchronize do
17
+ @environment ||= Meta.connection do
18
+ hwaddr = Meta.instance('network/interfaces/macs/').lines.first.strip.chomp('/')
19
+ {
20
+ instance_id: Meta.instance('instance-id'),
21
+ availability_zone: Meta.instance('placement/availability-zone'),
22
+ region: Meta.instance('placement/availability-zone').sub(/^(.*)[a-z]$/,'\1'),
23
+ vpc_id: Meta.interface(hwaddr, 'vpc-id'),
24
+ vpc_cidr: Meta.interface(hwaddr, 'vpc-ipv4-cidr-block')
25
+ }.freeze
26
+ end
22
27
  end
23
28
  rescue Errors::MetaNotFound
24
29
  raise Errors::EnvironmentError, 'Unable to detect VPC, library incompatible with EC2-Classic'
@@ -33,7 +38,7 @@ module Aws
33
38
 
34
39
  def timeout(new_default = nil)
35
40
  @timeout = new_default.to_i if new_default
36
- @timeout ||= 30
41
+ @timeout ||= 120
37
42
  end
38
43
 
39
44
  def verbose(set_verbose = nil)
@@ -117,7 +122,7 @@ module Aws
117
122
  end
118
123
 
119
124
  if do_block || do_config || do_enable
120
- wait_for 'the interface to attach', rescue: Errors::MetaNotFound do
125
+ wait_for 'the interface to attach', rescue: Errors::InvalidInterface do
121
126
  device.exists? && Client.interface_attached(device.interface_id)
122
127
  end
123
128
  end
@@ -261,7 +266,7 @@ module Aws
261
266
  new_ip = options[:private_ip]
262
267
 
263
268
  if do_block && !device.enabled?
264
- raise Errors::InvalidParameter, "Interface #{device.name} is not enabled (cannot block)"
269
+ raise Errors::InvalidParameter, "Interface #{device.name} is not enabled (cannot test connection)"
265
270
  end
266
271
 
267
272
  if new_ip
@@ -273,29 +278,34 @@ module Aws
273
278
  private_ip_addresses: [new_ip],
274
279
  allow_reassignment: false
275
280
  )
276
- wait_for 'private ip address to be assigned' do
277
- Client.interface_private_ips(interface_id).include?(new_ip) ||
278
- device.local_ips.include?(new_ip)
279
- end
280
281
  else
281
282
  Client.assign_private_ip_addresses(
282
283
  network_interface_id: interface_id,
283
284
  secondary_private_ip_address_count: 1,
284
285
  allow_reassignment: false
285
286
  )
286
- wait_for 'new private ip address to be assigned' do
287
- new_ips = Client.interface_private_ips(interface_id) - current_ips
288
- new_ips = device.local_ips - current_ips if new_ips.empty?
289
- new_ip = new_ips.first if new_ips
287
+ wait_for 'new private IP address to be assigned' do
288
+ client_ips = Client.interface_private_ips(interface_id)
289
+ new_ip = (client_ips - current_ips).first || (device.meta_ips - current_ips).first
290
290
  end
291
291
  end
292
292
 
293
293
  if do_config
294
294
  device.add_alias(new_ip)
295
- if do_block && !Interface.test(new_ip, target: device.gateway)
295
+ if do_block && !Interface.test(new_ip, target: device.gateway, timeout: timeout)
296
296
  raise Errors::TimeoutError, 'Timed out waiting for IP address to become active'
297
297
  end
298
298
  end
299
+
300
+ if do_block
301
+ # ensure new state has propagated to avoid race conditions
302
+ wait_for 'new private IP address to appear in metadata' do
303
+ device.meta_ips.include?(new_ip)
304
+ end
305
+ wait_for 'new private IP address to appear in EC2 resource' do
306
+ Client.interface_private_ips(interface_id).include?(new_ip)
307
+ end
308
+ end
299
309
  {
300
310
  private_ip: new_ip,
301
311
  interface_id: interface_id,
@@ -308,6 +318,7 @@ module Aws
308
318
  # remove a private ip using the AWS api and remove it from local config
309
319
  def unassign_secondary_ip(private_ip, options = {})
310
320
  do_release = !!options[:release]
321
+ do_block = options[:block] != false
311
322
 
312
323
  find = options[:device_name] || options[:device_number] || options[:interface_id] || private_ip
313
324
  device = Interface[find].assert(
@@ -335,6 +346,16 @@ module Aws
335
346
  network_interface_id: interface[:network_interface_id],
336
347
  private_ip_addresses: [private_ip]
337
348
  )
349
+
350
+ if do_block
351
+ # ensure new state has propagated to avoid race conditions
352
+ wait_for 'private IP address to be removed from metadata' do
353
+ !device.meta_ips.include?(private_ip)
354
+ end
355
+ wait_for 'private IP address to be removed from EC2 resource' do
356
+ !Client.interface_private_ips(device.interface_id).include?(private_ip)
357
+ end
358
+ end
338
359
  {
339
360
  private_ip: private_ip,
340
361
  device_name: device.name,
@@ -402,8 +423,14 @@ module Aws
402
423
  allow_reassociation: false
403
424
  )
404
425
 
405
- if do_block && !Interface.test(private_ip)
406
- raise Errors::TimeoutError, 'Timed out waiting for ip address to become active'
426
+ if do_block
427
+ # ensure new state has propagated to avoid race conditions
428
+ wait_for 'public IP address to appear in metadata' do
429
+ device.public_ips.has_value?(eip[:public_ip])
430
+ end
431
+ if !Interface.test(private_ip, timeout: timeout)
432
+ raise Errors::TimeoutError, 'Timed out waiting for IP address to become active'
433
+ end
407
434
  end
408
435
  {
409
436
  private_ip: private_ip,
@@ -420,6 +447,7 @@ module Aws
420
447
  # optionally release the public ip
421
448
  def dissociate_elastic_ip(address, options = {})
422
449
  do_release = !!options[:release]
450
+ do_block = options[:block] != false
423
451
 
424
452
  # assert device attributes if we've specified a device
425
453
  if find = options[:device_name] || options[:device_number]
@@ -463,6 +491,13 @@ module Aws
463
491
 
464
492
  Client.disassociate_address(association_id: eip[:association_id])
465
493
  Client.release_address(allocation_id: eip[:allocation_id]) if do_release
494
+
495
+ if device && do_block
496
+ # ensure new state has propagated to avoid race conditions
497
+ wait_for 'public IP address to be removed from metadata' do
498
+ !device.public_ips.has_value?(eip[:public_ip])
499
+ end
500
+ end
466
501
  {
467
502
  private_ip: eip[:private_ip_address],
468
503
  device_name: device && device.name,
@@ -578,7 +613,7 @@ module Aws
578
613
  def wait_for(task, options = {}, &block)
579
614
  errors = [*options[:rescue]]
580
615
  timeout = options[:timeout] || self.timeout
581
- interval = options[:interval] || 0.1
616
+ interval = options[:interval] || 0.3
582
617
 
583
618
  until timeout < 0
584
619
  begin
@@ -6,6 +6,9 @@ module Aws
6
6
  module ENI
7
7
  class Interface
8
8
 
9
+ # Class level thread-safe lock
10
+ @lock = Mutex.new
11
+
9
12
  class << self
10
13
  include Enumerable
11
14
 
@@ -15,8 +18,10 @@ module Aws
15
18
  def [](index)
16
19
  case index
17
20
  when Integer
18
- @instance_cache ||= []
19
- @instance_cache[index] ||= new("eth#{index}", false)
21
+ @lock.synchronize do
22
+ @instance_cache ||= []
23
+ @instance_cache[index] ||= new("eth#{index}", false)
24
+ end
20
25
  when nil
21
26
  self[next_available_index]
22
27
  when /^(?:eth)?([0-9]+)$/
@@ -144,6 +149,7 @@ module Aws
144
149
  @name = name
145
150
  @device_number = $1.to_i
146
151
  @route_table = @device_number + 10000
152
+ @lock = Mutex.new
147
153
  configure if auto_config
148
154
  end
149
155
 
@@ -166,19 +172,21 @@ module Aws
166
172
 
167
173
  # Validate and return basic interface metadata
168
174
  def info
169
- hwaddr = self.hwaddr
170
- unless @meta_cache && @meta_cache[:hwaddr] == hwaddr
171
- @meta_cache = Meta.connection do
172
- raise Errors::MetaBadResponse unless Meta.interface(hwaddr, '', not_found: nil)
173
- {
174
- hwaddr: hwaddr,
175
- interface_id: Meta.interface(hwaddr, 'interface-id'),
176
- subnet_id: Meta.interface(hwaddr, 'subnet-id'),
177
- subnet_cidr: Meta.interface(hwaddr, 'subnet-ipv4-cidr-block')
178
- }.freeze
175
+ @lock.synchronize do
176
+ hwaddr = self.hwaddr
177
+ unless @meta_cache && @meta_cache[:hwaddr] == hwaddr
178
+ @meta_cache = Meta.connection do
179
+ raise Errors::MetaBadResponse unless Meta.interface(hwaddr, '', not_found: nil)
180
+ {
181
+ hwaddr: hwaddr,
182
+ interface_id: Meta.interface(hwaddr, 'interface-id'),
183
+ subnet_id: Meta.interface(hwaddr, 'subnet-id'),
184
+ subnet_cidr: Meta.interface(hwaddr, 'subnet-ipv4-cidr-block')
185
+ }.freeze
186
+ end
179
187
  end
188
+ @meta_cache
180
189
  end
181
- @meta_cache
182
190
  rescue Errors::MetaConnectionFailed
183
191
  raise Errors::InvalidInterface, "Interface #{name} could not be found in the EC2 instance meta-data"
184
192
  end
@@ -210,13 +218,25 @@ module Aws
210
218
  list.lines.grep(/inet ([0-9\.]+)\/.* #{name}/i){ $1 }
211
219
  end
212
220
 
221
+ # Return an array of ip addresses found in our instance metadata
222
+ def meta_ips
223
+ # hack to use cached hwaddr when available since this is often polled
224
+ # continuously for changes
225
+ hwaddr = (@meta_cache && @meta_cache[:hwaddr]) || hwaddr
226
+ Meta.interface(hwaddr, 'local-ipv4s', cache: false).lines.map(&:strip)
227
+ end
228
+
229
+ # Return a hash of local/public ip associations found in instance metadata
213
230
  def public_ips
214
231
  hwaddr = self.hwaddr
215
232
  Hash[
216
233
  Meta.connection do
217
234
  Meta.interface(hwaddr, 'ipv4-associations/', not_found: '', cache: false).lines.map do |public_ip|
218
235
  public_ip.strip!
219
- [ Meta.interface(hwaddr, "ipv4-associations/#{public_ip}", cache: false), public_ip ]
236
+ unless private_ip = Meta.interface(hwaddr, "ipv4-associations/#{public_ip}", not_found: nil, cache: false)
237
+ raise Errors::MetaBadResponse
238
+ end
239
+ [ private_ip, public_ip ]
220
240
  end
221
241
  end
222
242
  ]
@@ -244,7 +264,6 @@ module Aws
244
264
  changes = 0
245
265
  prefix = self.prefix # prevent exists? check on each use
246
266
 
247
- meta_ips = Meta.interface(hwaddr, 'local-ipv4s', cache: false).lines.map(&:strip)
248
267
  local_primary, *local_aliases = local_ips
249
268
  meta_primary, *meta_aliases = meta_ips
250
269
 
@@ -5,6 +5,10 @@ require 'aws-eni/errors'
5
5
  module Aws
6
6
  module ENI
7
7
  module Meta
8
+ extend self
9
+
10
+ # Per-thread open connection storage
11
+ @connections = {}
8
12
 
9
13
  # These are the errors we trap when attempting to talk to the instance
10
14
  # metadata service. Any of these imply the service is not present, not
@@ -21,7 +25,7 @@ module Aws
21
25
 
22
26
  # Perform a GET request on an open HTTP connection to the EC2 instance
23
27
  # meta-data and return the body of any 200 response.
24
- def self.get(path, options = {})
28
+ def get(path, options = {})
25
29
  @cache ||= {}
26
30
  if @cache[path] && options[:cache] != false
27
31
  @cache[path]
@@ -43,39 +47,46 @@ module Aws
43
47
 
44
48
  # Perform a GET request on the instance metadata and return the body of
45
49
  # any 200 response.
46
- def self.instance(path, options = {})
50
+ def instance(path, options = {})
47
51
  get("/latest/meta-data/#{path}", options)
48
52
  end
49
53
 
50
54
  # Perform a GET request on the interface metadata and return the body of
51
55
  # any 200 response.
52
- def self.interface(hwaddr, path, options = {})
56
+ def interface(hwaddr, path, options = {})
53
57
  instance("network/interfaces/macs/#{hwaddr}/#{path}", options)
54
58
  end
55
59
 
56
60
  # Open a connection and attempt to execute the block `retries` times.
57
61
  # Can specify open and read timeouts in addition to the number of retries.
58
- def self.connection(options = {})
59
- return yield(@open_connection) if @open_connection
62
+ def connection(options = {})
63
+ raise ArgumentError, 'Must be called with a block' unless block_given?
64
+ attempts = 0
60
65
  retries = options[:retries] || 5
61
- failed_attempts = 0
62
- begin
63
- http = Net::HTTP.new('169.254.169.254', '80', nil)
64
- http.open_timeout = options[:open_timeout] || 5
65
- http.read_timeout = options[:read_timeout] || 5
66
- @open_connection = http.start
67
- yield(http).tap { http.finish }
68
- rescue *FAILURES => e
69
- if failed_attempts < retries
70
- # retry after an ever increasing cooldown time with each failure
71
- Kernel.sleep(1.2 ** failed_attempts)
72
- failed_attempts += 1
73
- retry
74
- else
75
- raise Errors::MetaConnectionFailed, "EC2 Metadata request failed after #{retries} retries."
66
+
67
+ if (http = @connections[Thread.current]) && http.active?
68
+ yield(http)
69
+ else
70
+ begin
71
+ http = Net::HTTP.new('169.254.169.254', '80', nil)
72
+ http.open_timeout = options[:open_timeout] || 5
73
+ http.read_timeout = options[:read_timeout] || 5
74
+
75
+ @connections[Thread.current] = http.start
76
+ yield(http)
77
+ rescue *FAILURES => e
78
+ if attempts < retries
79
+ # increasing cooldown time on each attempt
80
+ Kernel.sleep(1.2 ** attempts)
81
+ attempts += 1
82
+ retry
83
+ else
84
+ raise Errors::MetaConnectionFailed, "EC2 Metadata request failed after #{retries} retries."
85
+ end
86
+ ensure
87
+ http = @connections.delete(Thread.current)
88
+ http.finish if http && http.active?
76
89
  end
77
- ensure
78
- @open_connection = nil
79
90
  end
80
91
  end
81
92
  end
@@ -1,5 +1,5 @@
1
1
  module Aws
2
2
  module ENI
3
- VERSION = "0.3.1"
3
+ VERSION = "0.4.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aws-eni
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Greiling
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-07 00:00:00.000000000 Z
11
+ date: 2015-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: gli