kitchen-ec2 3.6.0 → 3.7.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
  SHA256:
3
- metadata.gz: ba67c03a470c6f9e73dc53b886a468a0003c24d9116cee97075ddc99da8a7529
4
- data.tar.gz: 8c77eba9c51421b234c0f5b2a7b75295eca8b5385a13cb9ca4213d4e65d5ddb9
3
+ metadata.gz: 0d6429e51c08d43c0e1084b9acc55b76feacae05bbe42b7cf3a1e35e0de70f87
4
+ data.tar.gz: 9f2a0adbe2866ad6a267ab8adc14d24a890b22b71b0abe272808a2a52def3933
5
5
  SHA512:
6
- metadata.gz: fdd64c2dd9a1b9e1a0811cc7a9bb34984f5a6bf9791b06d4ee2dcf05136d491a4024fd25b2c8cc996160970a155facad432c64270e2afebee9b6e4b3539ac30b
7
- data.tar.gz: 7597be672135a237bbfd11b5e40a0f1397d0f374253ac6eb1123834263c6d53b14509f2c7d662dae225bbd399acd494793c9d07fae225cf1480bbb986fc0b871
6
+ metadata.gz: aa308480b37d109ec173851aefe0098523d2a59c06481e88004bf76ccb8d87dfbd865300d2383157af13b38af47588ec2d1adf0541734f7279669a8dbcb0b673
7
+ data.tar.gz: 48f6f1a3dc22caf5d04079ef1579c19850e7e72558708e633537adb752969c3aac1b79702804864fac3fad9140e8d9053347c3ecde47e578e0545366d0b0b619
@@ -115,8 +115,22 @@ module Kitchen
115
115
  key_name: config[:aws_ssh_key_id],
116
116
  subnet_id: config[:subnet_id],
117
117
  private_ip_address: config[:private_ip_address],
118
+ min_count: 1,
119
+ max_count: 1,
118
120
  }
119
121
 
122
+ if config[:tags] && !config[:tags].empty?
123
+ tags = config[:tags].map do |k, v|
124
+ # we convert the value to a string because
125
+ # nils should be passed as an empty String
126
+ # and Integers need to be represented as Strings
127
+ { key: k, value: v.to_s }
128
+ end
129
+ instance_tag_spec = { resource_type: "instance", tags: tags }
130
+ volume_tag_spec = { resource_type: "volume", tags: tags }
131
+ i[:tag_specifications] = [instance_tag_spec, volume_tag_spec]
132
+ end
133
+
120
134
  availability_zone = config[:availability_zone]
121
135
  if availability_zone
122
136
  if availability_zone =~ /^[a-z]$/i
@@ -227,7 +227,7 @@ module Kitchen
227
227
 
228
228
  if config[:spot_price]
229
229
  # Spot instance when a price is set
230
- server = with_request_limit_backoff(state) { submit_spots(state) }
230
+ server = with_request_limit_backoff(state) { submit_spots }
231
231
  else
232
232
  # On-demand instance
233
233
  server = with_request_limit_backoff(state) { submit_server }
@@ -238,32 +238,16 @@ module Kitchen
238
238
  server.wait_until_exists(before_attempt: logging_proc)
239
239
  end
240
240
 
241
+ state[:server_id] = server.id
242
+ info("EC2 instance <#{state[:server_id]}> created.")
243
+
241
244
  # See https://github.com/aws/aws-sdk-ruby/issues/859
242
- # Tagging can fail with a NotFound error even though we waited until the server exists
243
- # Waiting can also fail, so we have to also retry on that. If it means we re-tag the
244
- # instance, so be it.
245
- # Tagging an instance is possible before volumes are attached. Tagging the volumes after
246
- # instance creation is consistent.
245
+ # Waiting can fail, so we have to retry on that.
247
246
  Retryable.retryable(
248
247
  tries: 10,
249
248
  sleep: lambda { |n| [2**n, 30].min },
250
249
  on: ::Aws::EC2::Errors::InvalidInstanceIDNotFound
251
250
  ) do |r, _|
252
- info("Attempting to tag the instance, #{r} retries")
253
- tag_server(server)
254
-
255
- # Get information about the AMI (image) used to create the image.
256
- image_data = ec2.client.describe_images({ image_ids: [server.image_id] })[0][0]
257
-
258
- state[:server_id] = server.id
259
- info("EC2 instance <#{state[:server_id]}> created.")
260
-
261
- # instance-store backed images do not have attached volumes, so only
262
- # wait for the volumes to be ready if the instance EBS-backed.
263
- if image_data.root_device_type == "ebs"
264
- wait_until_volumes_ready(server, state)
265
- tag_volumes(server)
266
- end
267
251
  wait_until_ready(server, state)
268
252
  end
269
253
 
@@ -297,13 +281,6 @@ module Kitchen
297
281
  warn("Received #{e}, instance was probably already destroyed. Ignoring")
298
282
  end
299
283
  end
300
- if state[:spot_request_id]
301
- debug("Deleting spot request <#{state[:server_id]}>")
302
- ec2.client.cancel_spot_instance_requests(
303
- spot_instance_request_ids: [state[:spot_request_id]]
304
- )
305
- state.delete(:spot_request_id)
306
- end
307
284
  # If we are going to clean up an automatic security group, we need
308
285
  # to wait for the instance to shut down. This slightly breaks the
309
286
  # subsystem encapsulation, sorry not sorry.
@@ -409,15 +386,14 @@ module Kitchen
409
386
  @instance_generator = Aws::InstanceGenerator.new(config, ec2, instance.logger)
410
387
  end
411
388
 
412
- # Fog AWS helper for creating the instance
389
+ # AWS helper for creating the instance
413
390
  def submit_server
414
391
  instance_data = instance_generator.ec2_instance_data
415
392
  debug("Creating EC2 instance in region #{config[:region]} with properties:")
416
393
  instance_data.each do |key, value|
417
394
  debug("- #{key} = #{value.inspect}")
418
395
  end
419
- instance_data[:min_count] = 1
420
- instance_data[:max_count] = 1
396
+
421
397
  ec2.create_instance(instance_data)
422
398
  end
423
399
 
@@ -445,7 +421,7 @@ module Kitchen
445
421
  configs
446
422
  end
447
423
 
448
- def submit_spots(state)
424
+ def submit_spots
449
425
  configs = [config]
450
426
  expanded = []
451
427
  keys = %i{instance_type subnet_id}
@@ -462,7 +438,7 @@ module Kitchen
462
438
  configs.each do |conf|
463
439
  begin
464
440
  @config = conf
465
- return submit_spot(state)
441
+ return submit_spot
466
442
  rescue => e
467
443
  errs.append(e)
468
444
  end
@@ -470,29 +446,10 @@ module Kitchen
470
446
  raise ["Could not create a spot instance:", errs].flatten.join("\n")
471
447
  end
472
448
 
473
- def submit_spot(state)
449
+ def submit_spot
474
450
  debug("Creating EC2 Spot Instance..")
451
+ instance_data = instance_generator.ec2_instance_data
475
452
 
476
- spot_request_id = create_spot_request
477
- # deleting the instance cancels the request, but deleting the request
478
- # does not affect the instance
479
- state[:spot_request_id] = spot_request_id
480
- ec2.client.wait_until(
481
- :spot_instance_request_fulfilled,
482
- spot_instance_request_ids: [spot_request_id]
483
- ) do |w|
484
- w.max_attempts = config[:spot_wait] / config[:retryable_sleep]
485
- w.delay = config[:retryable_sleep]
486
- w.before_attempt do |attempts|
487
- c = attempts * config[:retryable_sleep]
488
- t = config[:spot_wait]
489
- info "Waited #{c}/#{t}s for spot request <#{spot_request_id}> to become fulfilled."
490
- end
491
- end
492
- ec2.get_instance_from_spot_request(spot_request_id)
493
- end
494
-
495
- def create_spot_request
496
453
  request_duration = config[:spot_wait]
497
454
  config_spot_price = config[:spot_price].to_s
498
455
  if %w{ondemand on-demand}.include?(config_spot_price)
@@ -500,56 +457,36 @@ module Kitchen
500
457
  else
501
458
  spot_price = config_spot_price
502
459
  end
503
- request_data = {
504
- spot_price: spot_price,
505
- launch_specification: instance_generator.ec2_instance_data,
460
+ spot_options = {
461
+ spot_instance_type: "persistent", # Cannot use one-time with valid_until
506
462
  valid_until: Time.now + request_duration,
463
+ instance_interruption_behavior: "stop",
507
464
  }
508
465
  if config[:block_duration_minutes]
509
- request_data[:block_duration_minutes] = config[:block_duration_minutes]
466
+ spot_options[:block_duration_minutes] = config[:block_duration_minutes]
510
467
  end
511
-
512
- response = ec2.client.request_spot_instances(request_data)
513
- response[:spot_instance_requests][0][:spot_instance_request_id]
514
- end
515
-
516
- def tag_server(server)
517
- if config[:tags] && !config[:tags].empty?
518
- tags = config[:tags].map do |k, v|
519
- # we convert the value to a string because
520
- # nils should be passed as an empty String
521
- # and Integers need to be represented as Strings
522
- { key: k.to_s, value: v.to_s }
523
- end
524
- server.create_tags(tags: tags)
468
+ unless spot_price == "" # i.e. on-demand
469
+ spot_options[:max_price] = spot_price
525
470
  end
526
- end
527
471
 
528
- def tag_volumes(server)
529
- if config[:tags] && !config[:tags].empty?
530
- tags = config[:tags].map do |k, v|
531
- { key: k.to_s, value: v.to_s }
532
- end
533
- server.volumes.each do |volume|
534
- volume.create_tags(tags: tags)
535
- end
536
- end
537
- end
472
+ instance_data[:instance_market_options] = {
473
+ market_type: "spot",
474
+ spot_options: spot_options,
475
+ }
538
476
 
539
- # Compares the requested volume count vs what has actually been set to be
540
- # attached to the instance. The information requested through
541
- # ec2.client.described_volumes is updated before the instance volume
542
- # information.
543
- def wait_until_volumes_ready(server, state)
544
- wait_with_destroy(server, state, "volumes to be ready") do |aws_instance|
545
- described_volume_count = 0
546
- ready_volume_count = 0
547
- if aws_instance.exists?
548
- described_volume_count = ec2.client.describe_volumes(filters: [
549
- { name: "attachment.instance-id", values: ["#{state[:server_id]}"] }]).volumes.length
550
- aws_instance.volumes.each { ready_volume_count += 1 }
551
- end
552
- (described_volume_count > 0) && (described_volume_count == ready_volume_count)
477
+ # The preferred way to create a spot instance is via request_spot_instances()
478
+ # However, it does not allow for tagging to occur at creation time.
479
+ # create_instances() allows creation of tagged spot instances, but does
480
+ # not retry if the price could not be satisfied immediately.
481
+ Retryable.retryable(
482
+ tries: config[:spot_wait] / config[:retryable_sleep],
483
+ sleep: lambda { |_n| config[:retryable_sleep] },
484
+ on: ::Aws::EC2::Errors::SpotMaxPriceTooLow
485
+ ) do |retries|
486
+ c = retries * config[:retryable_sleep]
487
+ t = config[:spot_wait]
488
+ info "Waited #{c}/#{t}s for spot request to become fulfilled."
489
+ ec2.create_instance(instance_data)
553
490
  end
554
491
  end
555
492
 
@@ -22,6 +22,6 @@ module Kitchen
22
22
  module Driver
23
23
 
24
24
  # Version string for EC2 Test Kitchen driver
25
- EC2_VERSION = "3.6.0".freeze
25
+ EC2_VERSION = "3.7.0".freeze
26
26
  end
27
27
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kitchen-ec2
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.6.0
4
+ version: 3.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fletcher Nichol
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-17 00:00:00.000000000 Z
11
+ date: 2020-07-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: test-kitchen
@@ -168,14 +168,14 @@ dependencies:
168
168
  requirements:
169
169
  - - '='
170
170
  - !ruby/object:Gem::Version
171
- version: 1.0.5
171
+ version: 1.1.2
172
172
  type: :development
173
173
  prerelease: false
174
174
  version_requirements: !ruby/object:Gem::Requirement
175
175
  requirements:
176
176
  - - '='
177
177
  - !ruby/object:Gem::Version
178
- version: 1.0.5
178
+ version: 1.1.2
179
179
  - !ruby/object:Gem::Dependency
180
180
  name: climate_control
181
181
  requirement: !ruby/object:Gem::Requirement
@@ -216,7 +216,7 @@ homepage: https://github.com/test-kitchen/kitchen-ec2
216
216
  licenses:
217
217
  - Apache-2.0
218
218
  metadata: {}
219
- post_install_message:
219
+ post_install_message:
220
220
  rdoc_options: []
221
221
  require_paths:
222
222
  - lib
@@ -232,7 +232,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
232
232
  version: '0'
233
233
  requirements: []
234
234
  rubygems_version: 3.1.2
235
- signing_key:
235
+ signing_key:
236
236
  specification_version: 4
237
237
  summary: A Test Kitchen Driver for Amazon EC2
238
238
  test_files: []