kitchen-ec2 3.6.0 → 3.7.0

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 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: []