Class: Beaker::AwsSdk

Inherits:
Hypervisor
  • Object
show all
Defined in:
lib/beaker/hypervisor/aws_sdk.rb

Overview

This is an alternate EC2 driver that implements direct API access using Amazon’s AWS-SDK library: / SDK For Ruby

It is built for full control, to reduce any other layers beyond the pure vendor API.

Direct Known Subclasses

Ec2

Constant Summary collapse

ZOMBIE =

anything older than 3 hours is considered a zombie

3
PING_SECURITY_GROUP_NAME =
'beaker-ping'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(hosts, options) ⇒ AwsSdk

Initialize AwsSdk hypervisor driver

Parameters:

  • hosts (Array<Beaker::Host>)

    Array of Beaker::Host objects

  • options (Hash<String, String>)

    Options hash



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 28

def initialize(hosts, options)
  @hosts = hosts
  @options = options
  @logger = options[:logger]
  @default_region = ENV['AWS_REGION'] || 'us-west-2'

  # Get AWS credentials
  creds = options[:use_fog_credentials] ? load_credentials() : nil

  config = {
    :credentials   => creds,
    :logger        => Logger.new($stdout),
    :log_level     => :debug,
    :log_formatter => Aws::Log::Formatter.colored,
    :retry_limit   => 12,
    :region        => ENV['AWS_REGION'] || 'us-west-2'
  }.delete_if{ |k,v| v.nil? }
  Aws.config.update(config)

  @client = {}
  @client.default_proc = proc do |hash, key|
    hash[key] = Aws::EC2::Client.new(:region => key)
  end

  test_split_install()
end

Instance Attribute Details

#default_regionObject (readonly)

Returns the value of attribute default_region.



22
23
24
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 22

def default_region
  @default_region
end

Instance Method Details

#add_ingress_rule(cl, sg_group, cidr_ip, from_port, to_port, protocol = 'tcp') ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Authorizes connections from certain CIDR to a range of ports

Parameters:

  • cl (Aws::EC2::Client)
  • sg_group (Aws::EC2::SecurityGroup)

    the AWS security group

  • cidr_ip (String)

    CIDR used for outbound security group rule

  • from_port (String)

    Starting Port number in the range

  • to_port (String)

    Ending Port number in the range



1105
1106
1107
1108
1109
1110
1111
1112
1113
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 1105

def add_ingress_rule(cl, sg_group, cidr_ip, from_port, to_port, protocol = 'tcp')
  cl.authorize_security_group_ingress(
    :cidr_ip     => cidr_ip,
    :ip_protocol => protocol,
    :from_port   => from_port,
    :to_port     => to_port,
    :group_id    => sg_group.group_id,
  )
end

#add_tagsvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Add metadata tags to all instances



559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 559

def add_tags
  @hosts.each do |host|
    instance = host['instance']

    # Define tags for the instance
    @logger.notify("aws-sdk: Add tags for #{host.name}")

    tags = [
      {
        :key   => 'jenkins_build_url',
        :value => @options[:jenkins_build_url],
      },
      {
        :key   => 'Name',
        :value => host.name,
      },
      {
        :key   => 'department',
        :value => @options[:department],
      },
      {
        :key   => 'project',
        :value => @options[:project],
      },
      {
        :key   => 'created_by',
        :value => @options[:created_by],
      },
    ]

    host[:host_tags].each do |name, val|
      tags << { :key => name.to_s, :value => val }
    end

    client.create_tags(
      :resources => [instance.instance_id],
      :tags      => tags.reject { |r| r[:value].nil? },
    )
  end

  nil
end

#backoff_sleep(tries) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Calculates and waits a back-off period based on the number of tries

Logs each backupoff time and retry value to the console.

Parameters:

  • tries (Number)

    number of tries to calculate back-off period



815
816
817
818
819
820
821
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 815

def backoff_sleep(tries)
  # Exponential with some randomization
  sleep_time = 2 ** tries
  @logger.notify("aws-sdk: Sleeping #{sleep_time} seconds for attempt #{tries}.")
  sleep sleep_time
  nil
end

#cleanupvoid

This method returns an undefined value.

Cleanup all earlier provisioned hosts on EC2 using the Aws::EC2 library

It goes without saying, but a #cleanup does nothing without a #provision method call first.



126
127
128
129
130
131
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 126

def cleanup
  # Provisioning should have set the host 'instance' values.
  kill_instances(@hosts.map{ |h| h['instance'] }.select{ |x| !x.nil? })
  delete_key_pair_all_regions()
  nil
end

#client(region = default_region) ⇒ Object



55
56
57
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 55

def client(region = default_region)
  @client[region]
end

#configure_hostsvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

f5 hosts are skipped since this isn’t a valid step there

This method returns an undefined value.

Configure /etc/hosts for each node



666
667
668
669
670
671
672
673
674
675
676
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 666

def configure_hosts
  non_netdev_windows_hosts = @hosts.select{ |h| !(h['platform'] =~ /f5-|netscaler|windows/) }
  non_netdev_windows_hosts.each do |host|
    host_entries = non_netdev_windows_hosts.map do |h|
      h == host ? etc_hosts_entry(h, :private_ip) : etc_hosts_entry(h)
    end
    host_entries.unshift "127.0.0.1\tlocalhost localhost.localdomain\n"
    set_etc_hosts(host, host_entries.join(''))
  end
  nil
end

#create_group(region_or_vpc, ports, sg_cidr_ips = ['0.0.0.0/0']) ⇒ Aws::EC2::SecurityGroup

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Create a new security group

Accepts a region or VPC for group creation.

Parameters:

  • region_or_vpc (Aws::EC2::Region, Aws::EC2::VPC)

    the AWS region or vpc control object

  • ports (Array<Number>)

    an array of port numbers

  • sg_cidr_ips (Array<String>) (defaults to: ['0.0.0.0/0'])

    CIDRs used for outbound security group rule

Returns:

  • (Aws::EC2::SecurityGroup)

    created security group



1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 1068

def create_group(region_or_vpc, ports, sg_cidr_ips = ['0.0.0.0/0'])
  name = group_id(ports)
  @logger.notify("aws-sdk: Creating group #{name} for ports #{ports.to_s}")
  @logger.notify("aws-sdk: Creating group #{name} with CIDR IPs #{sg_cidr_ips.to_s}")
  cl = region_or_vpc.is_a?(String) ? client(region_or_vpc) : client

  params = {
    :description => "Custom Beaker security group for #{ports.to_a}",
    :group_name  => name,
  }

  params[:vpc_id] = region_or_vpc.vpc_id if region_or_vpc.is_a?(Aws::EC2::Types::Vpc)

  group = cl.create_security_group(params)

  unless ports.is_a? Set
    ports = Set.new(ports)
  end

  sg_cidr_ips.each do |cidr_ip|
    ports.each do |port|
      add_ingress_rule(cl, group, cidr_ip, port, port)
    end
  end

  group
end

#create_instance(host, ami_spec, subnet_id) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Create an EC2 instance for host, tag it, and return it.

Raises:

  • (RuntimeError)


272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 272

def create_instance(host, ami_spec, subnet_id)
  amitype = host['vmname'] || host['platform']
  amisize = host['amisize'] || 'm1.small'
  vpc_id = host['vpc_id'] || @options['vpc_id'] || nil
  host['sg_cidr_ips'] = host['sg_cidr_ips'] || '0.0.0.0/0';
  sg_cidr_ips = host['sg_cidr_ips'].split(',')
  assoc_pub_ip_addr = host['associate_public_ip_address']

  if vpc_id && !subnet_id
    raise RuntimeError, "A subnet_id must be provided with a vpc_id"
  end
  
  if assoc_pub_ip_addr && !subnet_id
    raise RuntimeError, "A subnet_id must be provided when configuring assoc_pub_ip_addr"
  end

  # Use snapshot provided for this host
  image_type = host['snapshot']
  raise RuntimeError, "No snapshot/image_type provided for EC2 provisioning" unless image_type

  ami = ami_spec[amitype]
  ami_region = ami[:region]

  # Main region object for ec2 operations
  region = ami_region

  # If we haven't defined a vpc_id then we use the default vpc for the provided region
  unless vpc_id
    @logger.notify("aws-sdk: filtering available vpcs in region by 'isDefault'")

    default_vpcs = client(region).describe_vpcs(:filters => [{:name => 'isDefault', :values => ['true']}])
    vpc_id = if default_vpcs.vpcs.empty?
               nil
             else
               default_vpcs.vpcs.first.vpc_id
             end
  end

  # Grab the vpc object based upon provided id
  vpc = vpc_id ? client(region).describe_vpcs(:vpc_ids => [vpc_id]).vpcs.first : nil

  # Grab image object
  image_id = ami[:image][image_type.to_sym]
  @logger.notify("aws-sdk: Checking image #{image_id} exists and getting its root device")
  image = client(region).describe_images(:image_ids => [image_id]).images.first
  raise RuntimeError, "Image not found: #{image_id}" if image.nil?

  @logger.notify("Image Storage Type: #{image.root_device_type}")

  # Transform the images block_device_mappings output into a format
  # ready for a create.
  block_device_mappings = []
  if image.root_device_type == :ebs
    orig_bdm = image.block_device_mappings
    @logger.notify("aws-sdk: Image block_device_mappings: #{orig_bdm}")
    orig_bdm.each do |block_device|
      block_device_mappings << {
        :device_name => block_device.device_name,
        :ebs => {
          # Change the default size of the root volume.
          :volume_size => host['volume_size'] || block_device.ebs.volume_size,
          # This is required to override the images default for
          # delete_on_termination, forcing all volumes to be deleted once the
          # instance is terminated.
          :delete_on_termination => true,
        }
      }
    end
  end

  security_group = ensure_group(vpc || region, Beaker::EC2Helper.amiports(host), sg_cidr_ips)
  #check if ping is enabled
  ping_security_group = ensure_ping_group(vpc || region, sg_cidr_ips)

  msg = "aws-sdk: launching %p on %p using %p/%p%s" %
        [host.name, amitype, amisize, image_type,
         subnet_id ? ("in %p" % subnet_id) : '']
  @logger.notify(msg)
  config = {
    :max_count  => 1,
    :min_count  => 1,
    :image_id   => image_id,
    :monitoring => {
      :enabled => true,
    },
    :key_name => ensure_key_pair(region).key_pairs.first.key_name,
    :instance_type => amisize,
    :disable_api_termination => false,
    :instance_initiated_shutdown_behavior => "terminate",
  }
  if assoc_pub_ip_addr
    # this never gets created, so they end up with
    # default security group which only allows for
    # ssh access from outside world which
    # doesn't work well with remote devices etc.
    config[:network_interfaces] = [{
      :subnet_id => subnet_id,
      :groups => [security_group.group_id, ping_security_group.group_id],
      :device_index => 0,
      :associate_public_ip_address => assoc_pub_ip_addr,
    }]
  else
    config[:subnet_id] = subnet_id
  end
  config[:block_device_mappings] = block_device_mappings if image.root_device_type == :ebs
  reservation = client(region).run_instances(config)
  reservation.instances.first
end

#create_new_key_pair(region, pair_name) ⇒ Aws::EC2::KeyPair

Create a new key pair for a given Beaker run

Parameters:

  • region (Aws::EC2::Region)

    the region the key pair will be imported into

  • pair_name (String)

    the name of the key to be created

Returns:

  • (Aws::EC2::KeyPair)

    key pair created

Raises:

  • (RuntimeError)

    raised if AWS keypair not created



944
945
946
947
948
949
950
951
952
953
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 944

def create_new_key_pair(region, pair_name)
  @logger.debug("aws-sdk: importing new key pair: #{pair_name}")
  client(region).import_key_pair(:key_name => pair_name, :public_key_material => public_key)

  begin
    client(region).wait_until(:key_pair_exists, { :key_names => [pair_name] }, :max_attempts => 5, :delay => 2)
  rescue Aws::Waiters::Errors::WaiterFailed
    raise RuntimeError, "AWS key pair #{pair_name} can not be queried, even after import"
  end
end

#create_ping_group(region_or_vpc, sg_cidr_ips = ['0.0.0.0/0']) ⇒ Aws::EC2::SecurityGroup

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Create a new ping enabled security group

Accepts a region or VPC for group creation.

Parameters:

  • region_or_vpc (Aws::EC2::Region, Aws::EC2::VPC)

    the AWS region or vpc control object

  • sg_cidr_ips (Array<String>) (defaults to: ['0.0.0.0/0'])

    CIDRs used for outbound security group rule

Returns:

  • (Aws::EC2::SecurityGroup)

    created security group



1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 1033

def create_ping_group(region_or_vpc, sg_cidr_ips = ['0.0.0.0/0'])
  @logger.notify("aws-sdk: Creating group #{PING_SECURITY_GROUP_NAME}")
  cl = region_or_vpc.is_a?(String) ? client(region_or_vpc) : client

  params = {
    :description => 'Custom Beaker security group to enable ping',
    :group_name  => PING_SECURITY_GROUP_NAME,
  }
  params[:vpc_id] = region_or_vpc.vpc_id if region_or_vpc.is_a?(Aws::EC2::Types::Vpc)

  group = cl.create_security_group(params)

  sg_cidr_ips.each do |cidr_ip|
    add_ingress_rule(
      cl,
      group,
      cidr_ip,
      '8', # 8 == ICMPv4 ECHO request
      '-1', # -1 == All ICMP codes
      'icmp',
    )
  end

  group
end

#delete_key_pair(region, pair_name) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Deletes a given key pair

Parameters:

  • region (Aws::EC2::Region)

    the region the key belongs to

  • pair_name (String)

    the name of the key to be deleted



927
928
929
930
931
932
933
934
935
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 927

def delete_key_pair(region, pair_name)
  kp = client(region).describe_key_pairs(:key_names => [pair_name]).key_pairs.first
  unless kp.nil?
    @logger.debug("aws-sdk: delete key pair in region: #{region}")
    client(region).delete_key_pair(:key_name => pair_name)
  end
rescue Aws::EC2::Errors::InvalidKeyPairNotFound
  nil
end

#delete_key_pair_all_regions(keypair_name_filter = nil) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Deletes key pairs from all regions

Parameters:

  • keypair_name_filter (String) (defaults to: nil)

    if given, will get all keypairs that match a simple String#start_with? filter. If no filter is given, the basic key name returned by #key_name will be used.

Returns:

  • nil



890
891
892
893
894
895
896
897
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 890

def delete_key_pair_all_regions(keypair_name_filter=nil)
  region_keypairs_hash = my_key_pairs(keypair_name_filter)
  region_keypairs_hash.each_pair do |region, keypair_name_array|
    keypair_name_array.each do |keypair_name|
      delete_key_pair(region, keypair_name)
    end
  end
end

#enable_root(host) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Enables root access for a host when username is not root



697
698
699
700
701
702
703
704
705
706
707
708
709
710
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 697

def enable_root(host)
  if host['user'] != 'root'
    if host['platform'] =~ /f5-/
      enable_root_f5(host)
    elsif host['platform'] =~ /netscaler/
      enable_root_netscaler(host)
    else
      copy_ssh_to_root(host, @options)
      (host, @options)
      host['user'] = 'root'
    end
    host.close
  end
end

#enable_root_f5(host) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

This method does not support other platforms

Enables root access for a host on an f5 platform

Returns:

  • nil



717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 717

def enable_root_f5(host)
  for tries in 1..10
    begin
      #This command is problematic as the F5 is not always done loading
      if host.exec(Command.new("modify sys db systemauth.disablerootlogin value false"), :acceptable_exit_codes => [0,1]).exit_code == 0 \
          and host.exec(Command.new("modify sys global-settings gui-setup disabled"), :acceptable_exit_codes => [0,1]).exit_code == 0 \
          and host.exec(Command.new("save sys config"), :acceptable_exit_codes => [0,1]).exit_code == 0
        backoff_sleep(tries)
        break
      elsif tries == 10
        raise "Instance was unable to be configured"
      end
    rescue Beaker::Host::CommandFailure => e
      @logger.debug("Instance not yet configured (#{e})")
    end
    backoff_sleep(tries)
  end
  host['user'] = 'admin'
  sha256 = Digest::SHA256.new
  password = sha256.hexdigest((1..50).map{(rand(86)+40).chr}.join.gsub(/\\/,'\&\&')) + 'password!'
  # disabling password policy to account for the enforcement level set
  # and the generated password is sometimes too `01070366:3: Bad password (admin): BAD PASSWORD: \
  # it is too simplistic/systematic`
  host.exec(Command.new('modify auth password-policy policy-enforcement disabled'))
  host.exec(Command.new("modify auth user admin password #{password}"))
  @logger.notify("f5: Configured admin password to be #{password}")
  host.close
  host['ssh'] = {:password => password}
end

#enable_root_netscaler(host) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

This method does not support other platforms

Enables root access for a host on an netscaler platform

Returns:

  • nil



752
753
754
755
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 752

def enable_root_netscaler(host)
  host['ssh'] = {:password => host['instance'].instance_id}
  @logger.notify("netscaler: nsroot password is #{host['instance'].instance_id}")
end

#enable_root_on_hostsvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Enables root for instances with custom username like ubuntu-amis



682
683
684
685
686
687
688
689
690
691
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 682

def enable_root_on_hosts
  @hosts.each do |host|
    if host['disable_root_ssh'] == true
      @logger.notify("aws-sdk: Not enabling root for instance as disable_root_ssh is set to 'true'.")
    else
      @logger.notify("aws-sdk: Enabling root ssh")
      enable_root(host)
    end
  end
end

#ensure_group(vpc, ports, sg_cidr_ips = ['0.0.0.0/0']) ⇒ Aws::EC2::SecurityGroup

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Return an existing group, or create new one

Accepts a VPC as input for checking & creation.

Parameters:

  • vpc (Aws::EC2::VPC)

    the AWS vpc control object

  • ports (Array<Number>)

    an array of port numbers

  • sg_cidr_ips (Array<String>) (defaults to: ['0.0.0.0/0'])

    CIDRs used for outbound security group rule

Returns:

  • (Aws::EC2::SecurityGroup)

    created security group



1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 1007

def ensure_group(vpc, ports, sg_cidr_ips = ['0.0.0.0/0'])
  @logger.notify("aws-sdk: Ensure security group exists for ports #{ports.to_s}, create if not")
  name = group_id(ports)

  group = client.describe_security_groups(
    :filters => [
      { :name => 'group-name', :values => [name] },
      { :name => 'vpc-id', :values => [vpc.vpc_id] },
    ]
  ).security_groups.first

  if group.nil?
    group = create_group(vpc, ports, sg_cidr_ips)
  end

  group
end

#ensure_key_pair(region) ⇒ Aws::EC2::KeyPair

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Creates the KeyPair for this test run

Parameters:

  • region (Aws::EC2::Region)

    region to create the key pair in

Returns:

  • (Aws::EC2::KeyPair)

    created key_pair



876
877
878
879
880
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 876

def ensure_key_pair(region)
  pair_name = key_name()
  delete_key_pair(region, pair_name)
  create_new_key_pair(region, pair_name)
end

#ensure_ping_group(vpc, sg_cidr_ips = ['0.0.0.0/0']) ⇒ Aws::EC2::SecurityGroup

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Return an existing group, or create new one

Accepts a VPC as input for checking & creation.

Parameters:

  • vpc (Aws::EC2::VPC)

    the AWS vpc control object

  • sg_cidr_ips (Array<String>) (defaults to: ['0.0.0.0/0'])

    CIDRs used for outbound security group rule

Returns:

  • (Aws::EC2::SecurityGroup)

    created security group



981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 981

def ensure_ping_group(vpc, sg_cidr_ips = ['0.0.0.0/0'])
  @logger.notify("aws-sdk: Ensure security group exists that enables ping, create if not")

  group = client.describe_security_groups(
    :filters => [
      { :name => 'group-name', :values => [PING_SECURITY_GROUP_NAME] },
      { :name => 'vpc-id', :values => [vpc.vpc_id] },
    ]
  ).security_groups.first

  if group.nil?
    group = create_ping_group(vpc, sg_cidr_ips)
  end

  group
end

#etc_hosts_entry(host, interface = :ip) ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Return a valid /etc/hosts line for a given host

Parameters:

  • host (Beaker::Host)

    Beaker::Host object for generating /etc/hosts entry

  • interface (Symbol) (defaults to: :ip)

    Symbol identifies which ip should be used for host

Returns:

  • (String)

    formatted hosts entry for host



653
654
655
656
657
658
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 653

def etc_hosts_entry(host, interface = :ip)
  name = host.name
  domain = get_domain_name(host)
  ip = host[interface.to_s]
  "#{ip}\t#{name} #{name}.#{domain} #{host['dns_name']}\n"
end

#group_id(ports) ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Return a reproducable security group identifier based on input ports

Parameters:

  • ports (Array<Number>)

    array of port numbers

Returns:

  • (String)

    group identifier



960
961
962
963
964
965
966
967
968
969
970
971
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 960

def group_id(ports)
  if ports.nil? or ports.empty?
    raise ArgumentError, "Ports list cannot be nil or empty"
  end

  unless ports.is_a? Set
    ports = Set.new(ports)
  end

  # Lolwut, #hash is inconsistent between ruby processes
  "Beaker-#{Zlib.crc32(ports.inspect)}"
end

#instance_by_id(id) ⇒ Aws::EC2::Types::Instance

Provided an id return an instance object. Instance object will respond to methods described here: AWS Instance Object.

Parameters:

  • id (String)

    The id of the instance to return

Returns:



164
165
166
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 164

def instance_by_id(id)
  client.describe_instances(:instance_ids => [id]).reservations.first.instances.first
end

#instancesArray<Aws::Ec2::Types::Instance>

Return all instances currently on ec2.

Returns:

  • (Array<Aws::Ec2::Types::Instance>)

    An array of Aws::EC2 instance objects

See Also:



171
172
173
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 171

def instances
  client.describe_instances.reservations.map(&:instances).flatten
end

#key_nameString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Generate a reusable key name from the local hosts hostname

Returns:

  • (String)

    safe key name for current host



859
860
861
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 859

def key_name
  "#{key_name_prefix}-#{@options[:aws_keyname_modifier]}-#{@options[:timestamp].strftime("%F_%H_%M_%S_%N")}"
end

#key_name_prefixString

Note:

This is the part of the key that will stay static between Beaker runs on the same host.

Generate a key prefix for key pair names

Returns:

  • (String)

    Beaker key pair name based on sanitized hostname



850
851
852
853
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 850

def key_name_prefix
  safe_hostname = Socket.gethostname.gsub('.', '-')
  "Beaker-#{local_user}-#{safe_hostname}"
end

#kill_instances(instances) ⇒ void

This method returns an undefined value.

Kill all instances.

Parameters:



106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 106

def kill_instances(instances)
  running_instances = instances.compact.select do |instance|
    instance_by_id(instance.instance_id).state.name == 'running'
  end
  instance_ids = running_instances.map(&:instance_id)

  return nil if instance_ids.empty?

  @logger.notify("aws-sdk: killing EC2 instance(s) #{instance_ids.join(', ')}")
  client.terminate_instances(:instance_ids => instance_ids)

  nil
end

#kill_zombie_volumesObject

Destroy any volumes marked ‘available’, INCLUDING THOSE YOU DON’T OWN! Use with care.



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 241

def kill_zombie_volumes
  # Occasionaly, tearing down ec2 instances leaves orphaned EBS volumes behind -- these stack up quickly.
  # This simply looks for EBS volumes that are not in use
  @logger.notify("aws-sdk: Kill Zombie Volumes!")
  volume_count = 0

  regions.each do |region|
    @logger.debug "Reviewing: #{region}"
    available_volumes = client(region).describe_volumes(
      :filters => [
        { :name => 'status', :values => ['available'], }
      ]
    ).volumes

    available_volumes.each do |volume|
      begin
        client(region).delete_volume(:volume_id => volume.id)
        volume_count += 1
      rescue Aws::EC2::Errors::InvalidVolume::NotFound => e
        @logger.debug "Failed to remove volume: #{volume.id} #{e}"
      end
    end
  end

  @logger.notify "Freed #{volume_count} volume(s)"
end

#kill_zombies(max_age = ZOMBIE, key = key_name) ⇒ Object

Shutdown and destroy ec2 instances idenfitied by key that have been alive longer than ZOMBIE hours.

Parameters:

  • max_age (Integer) (defaults to: ZOMBIE)

    The age in hours that a machine needs to be older than to be considered a zombie

  • key (String) (defaults to: key_name)

    The key_name to match for



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 210

def kill_zombies(max_age = ZOMBIE, key = key_name)
  @logger.notify("aws-sdk: Kill Zombies! (keyname: #{key}, age: #{max_age} hrs)")

  instances_to_kill = []

  time_now = Time.now.getgm #ec2 uses GM time

  #examine all available regions
  regions.each do |region|
    @logger.debug "Reviewing: #{region}"

    client(region).describe_instances.reservations.each do |reservation|
      reservation.instances.each do |instance|
        if (instance.key_name =~ /#{key}/)
          @logger.debug "Examining #{instance.instance_id} (keyname: #{instance.key_name}, launch time: #{instance.launch_time}, state: #{instance.state.name})"
          if ((time_now - instance.launch_time) >  max_age*60*60) and instance.state.name !~ /terminated/
            @logger.debug "Kill! #{instance.instance_id}: #{instance.key_name} (Current status: #{instance.state.name})"
            instances_to_kill << instance
          end
        end
      end
    end
  end

  kill_instances(instances_to_kill)
  delete_key_pair_all_regions(key_name_prefix)

  @logger.notify "#{key}: Killed #{instances_to_kill.length} instance(s)"
end

#launch_all_nodesvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Create EC2 instances for all hosts, tag them, and wait until they’re running. When a host provides a subnet_id, create the instance in that subnet, otherwise prefer a CONFIG subnet_id. If neither are set but there is a CONFIG subnet_ids list, attempt to create the host in each specified subnet, which might fail due to capacity constraints, for example. Specifying both a CONFIG subnet_id and subnet_ids will provoke an error.



432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 432

def launch_all_nodes
  @logger.notify("aws-sdk: launch all hosts in configuration")
  ami_spec = YAML.load_file(@options[:ec2_yaml])["AMI"]
  global_subnet_id = @options['subnet_id']
  global_subnets = @options['subnet_ids']
  if global_subnet_id and global_subnets
    raise RuntimeError, 'Config specifies both subnet_id and subnet_ids'
  end
  no_subnet_hosts = []
  specific_subnet_hosts = []
  some_subnet_hosts = []
  @hosts.each do |host|
    if global_subnet_id or host['subnet_id']
      specific_subnet_hosts.push(host)
    elsif global_subnets
      some_subnet_hosts.push(host)
    else
      no_subnet_hosts.push(host)
    end
  end
  instances = [] # Each element is {:instance => i, :host => h}
  begin
    @logger.notify("aws-sdk: launch instances not particular about subnet")
    launch_nodes_on_some_subnet(some_subnet_hosts, global_subnets, ami_spec,
                                instances)
    @logger.notify("aws-sdk: launch instances requiring a specific subnet")
    specific_subnet_hosts.each do |host|
      subnet_id = host['subnet_id'] || global_subnet_id
      instance = create_instance(host, ami_spec, subnet_id)
      instances.push({:instance => instance, :host => host})
    end
    @logger.notify("aws-sdk: launch instances requiring no subnet")
    no_subnet_hosts.each do |host|
      instance = create_instance(host, ami_spec, nil)
      instances.push({:instance => instance, :host => host})
    end
    wait_for_status(:running, instances)
  rescue Exception => ex
    @logger.notify("aws-sdk: exception #{ex.class}: #{ex}")
    kill_instances(instances.map{|x| x[:instance]})
    raise ex
  end
  # At this point, all instances should be running since wait
  # either returns on success or throws an exception.
  if instances.empty?
    raise RuntimeError, "Didn't manage to launch any EC2 instances"
  end
  # Assign the now known running instances to their hosts.
  instances.each {|x| x[:host]['instance'] = x[:instance]}
  nil
end

#launch_nodes_on_some_subnet(hosts, subnets, ami_spec, instances_created) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

For each host, create an EC2 instance in one of the specified subnets and push it onto instances_created. Each subnet will be tried at most once for each host, and more than one subnet may be tried if capacity constraints are encountered. Each Hash in instances_created will contain an :instance and :host value.

Parameters:

  • hosts (Enumerable<Host>)
  • subnets (Enumerable<String>)
  • ami_spec (Hash)
  • instances_created

    Enumerable<HashSymbol=>EC2::Instance,Host>



393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 393

def launch_nodes_on_some_subnet(hosts, subnets, ami_spec, instances_created)
  # Shuffle the subnets so we don't always hit the same one
  # first, and cycle though the subnets independently of the
  # host, so we stick with one that's working.  Try each subnet
  # once per-host.
  if subnets.nil? or subnets.empty?
    return
  end
  subnet_i = 0
  shuffnets = subnets.shuffle
  hosts.each do |host|
    instance = nil
    shuffnets.length.times do
      begin
        subnet_id = shuffnets[subnet_i]
        instance = create_instance(host, ami_spec, subnet_id)
        instances_created.push({:instance => instance, :host => host})
        break
      rescue Aws::EC2::Errors::InsufficientInstanceCapacity
        @logger.notify("aws-sdk: hit #{subnet_id} capacity limit; moving on")
        subnet_i = (subnet_i + 1) % shuffnets.length
      end
    end
    if instance.nil?
      raise RuntimeError, "unable to launch host in any requested subnet"
    end
  end
end

#load_credentialsHash<Symbol, String>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Return a hash containing AWS credentials

Returns:

  • (Hash<Symbol, String>)

    AWS credentials



1119
1120
1121
1122
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 1119

def load_credentials
  return load_env_credentials if load_env_credentials.set?
  load_fog_credentials(@options[:dot_fog])
end

#load_env_credentials(prefix = 'AWS') ⇒ Aws::Credentials

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Return AWS credentials loaded from environment variables

Parameters:

  • prefix (String) (defaults to: 'AWS')

    environment variable prefix

Returns:

  • (Aws::Credentials)

    ec2 credentials



1129
1130
1131
1132
1133
1134
1135
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 1129

def load_env_credentials(prefix='AWS')
  Aws::Credentials.new(
    ENV["#{prefix}_ACCESS_KEY_ID"],
    ENV["#{prefix}_SECRET_ACCESS_KEY"],
    ENV["#{prefix}_SESSION_TOKEN"]
  )
end

#load_fog_credentials(dot_fog = '.fog') ⇒ Aws::Credentials

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Return a hash containing the fog credentials for EC2

Parameters:

  • dot_fog (String) (defaults to: '.fog')

    dot fog path

Returns:

  • (Aws::Credentials)

    ec2 credentials



1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 1142

def load_fog_credentials(dot_fog = '.fog')
  default = get_fog_credentials(dot_fog)

  raise "You must specify an aws_access_key_id in your .fog file (#{dot_fog}) for ec2 instances!" unless default[:aws_access_key_id]
  raise "You must specify an aws_secret_access_key in your .fog file (#{dot_fog}) for ec2 instances!" unless default[:aws_secret_access_key]

  Aws::Credentials.new(
    default[:aws_access_key_id],
    default[:aws_secret_access_key],
    default[:aws_session_token]
  )
end

#local_userString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the local user running this tool

Returns:

  • (String)

    username of local user



867
868
869
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 867

def local_user
  ENV['USER']
end

#log_instances(key = key_name, status = /running/) ⇒ Object

Print instances to the logger. Instances will be from all regions associated with provided key name and limited by regex compared to instance status. Defaults to running instances.

Parameters:

  • key (String) (defaults to: key_name)

    The key_name to match for

  • status (Regex) (defaults to: /running/)

    The regular expression to match against the instance’s status



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 139

def log_instances(key = key_name, status = /running/)
  instances = []
  regions.each do |region|
    @logger.debug "Reviewing: #{region}"
    client(region).describe_instances.reservations.each do |reservation|
      reservation.instances.each do |instance|
        if (instance.key_name =~ /#{key}/) and (instance.state.name =~ status)
          instances << instance
        end
      end
    end
  end
  output = ""
  instances.each do |instance|
    dns_name = instance.public_dns_name || instance.private_dns_name
    output << "#{instance.instance_id} keyname: #{instance.key_name}, dns name: #{dns_name}, private ip: #{instance.private_ip_address}, ip: #{instance.public_ip_address}, launch time #{instance.launch_time}, status: #{instance.state.name}\n"
  end
  @logger.notify("aws-sdk: List instances (keyname: #{key})")
  @logger.notify("#{output}")
end

#modify_network_interfacevoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Add correct security groups to hosts network_interface as during the create_instance stage it is too early in process to configure



608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 608

def modify_network_interface
  @hosts.each do |host|
    instance = host['instance']
    host['sg_cidr_ips'] = host['sg_cidr_ips'] || '0.0.0.0/0';
    sg_cidr_ips = host['sg_cidr_ips'].split(',')

    # Define tags for the instance
    @logger.notify("aws-sdk: Update network_interface for #{host.name}")

    security_group = ensure_group(instance[:network_interfaces].first, Beaker::EC2Helper.amiports(host), sg_cidr_ips)
    ping_security_group = ensure_ping_group(instance[:network_interfaces].first, sg_cidr_ips)

    client.modify_network_interface_attribute(
      :network_interface_id => "#{instance[:network_interfaces].first[:network_interface_id]}",
      :groups => [security_group.group_id, ping_security_group.group_id],
    )
  end

  nil
end

#my_key_pairs(name_filter = nil) ⇒ Hash{String=>Array[String]}

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Gets the Beaker user’s keypairs by region

Parameters:

  • name_filter (String) (defaults to: nil)

    if given, will get all keypairs that match a simple String#start_with? filter. If no filter is given, the basic key name returned by #key_name will be used.

Returns:

  • (Hash{String=>Array[String]})

    a hash of region name to an array of the keypair names that match for the filter



908
909
910
911
912
913
914
915
916
917
918
919
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 908

def my_key_pairs(name_filter=nil)
  keypairs_by_region = {}
  key_name_filter = name_filter ? "#{name_filter}-*" : key_name

  regions.each do |region|
    keypairs_by_region[region] = client(region).describe_key_pairs(
      :filters => [{ :name => 'key-name', :values => [key_name_filter] }]
    ).key_pairs.map(&:key_name)
  end

  keypairs_by_region
end

#populate_dnsvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Populate the hosts IP address from the EC2 dns_name



633
634
635
636
637
638
639
640
641
642
643
644
645
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 633

def populate_dns
  # Obtain the IP addresses and dns_name for each host
  @hosts.each do |host|
    @logger.notify("aws-sdk: Populate DNS for #{host.name}")
    instance = host['instance']
    host['ip'] = instance.public_ip_address || instance.private_ip_address
    host['private_ip'] = instance.private_ip_address
    host['dns_name'] = instance.public_dns_name || instance.private_dns_name
    @logger.notify("aws-sdk: name: #{host.name} ip: #{host['ip']} private_ip: #{host['private_ip']} dns_name: #{host['dns_name']}")
  end

  nil
end

#provisionvoid

This method returns an undefined value.

Provision all hosts on EC2 using the Aws::EC2 API



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 62

def provision
  start_time = Time.now

  # Perform the main launch work
  launch_all_nodes()

  # Add metadata tags to each instance
  # tagging early as some nodes take longer
  # to initialize and terminate before it has
  # a chance to provision
  add_tags()

  # adding the correct security groups to the
  # network interface, as during the `launch_all_nodes()`
  # step they never get assigned, although they get created
  modify_network_interface()

  wait_for_status_netdev()

  # Grab the ip addresses and dns from EC2 for each instance to use for ssh
  populate_dns()

  #enable root if user is not root
  enable_root_on_hosts()

  # Set the hostname for each box
  set_hostnames()

  # Configure /etc/hosts on each host
  configure_hosts()

  @logger.notify("aws-sdk: Provisioning complete in #{Time.now - start_time} seconds")

  nil #void
end

#public_keyString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Retrieve the public key locally from the executing users ~/.ssh directory

Returns:

  • (String)

    contents of public key



827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 827

def public_key
  keys = Array(@options[:ssh][:keys])
  keys << '~/.ssh/id_rsa'
  keys << '~/.ssh/id_dsa'
  key_file = keys.find do |key|
    key_pub = key + '.pub'
    File.exist?(File.expand_path(key_pub)) && File.exist?(File.expand_path(key))
  end

  if key_file
    @logger.debug("Using public key: #{key_file}")
  else
    raise RuntimeError, "Expected to find a public key, but couldn't in #{keys}"
  end
  File.read(File.expand_path(key_file + '.pub'))
end

#regionsObject



98
99
100
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 98

def regions
  @regions ||= client.describe_regions.regions.map(&:region_name)
end

#security_group_by_id(id) ⇒ Aws::EC2::Types::SecurityGroup

Provided an id return a security group object Security object will respond to methods described here: AWS SecurityGroup Object.

Parameters:

  • id (String)

    The id of the security group to return

Returns:

  • (Aws::EC2::Types::SecurityGroup)

    An Aws::EC2 security group object



194
195
196
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 194

def security_group_by_id(id)
  client.describe_security_groups(:group_ids => [id]).security_groups.first
end

#security_groupsArray<Aws::EC2::Types::SecurityGroup>

Return all security groups currently on ec2.

Returns:

  • (Array<Aws::EC2::Types::SecurityGroup>)

    An array of Aws::EC2 security group objects

See Also:

  • AwsSdk#security_goup_by_id


201
202
203
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 201

def security_groups
  client.describe_security_groups.security_groups
end

#set_hostnames@hosts

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Set the :vmhostname for each host object to be the dns_name, which is accessible publicly. Then configure each ec2 machine to that dns_name, so that when facter is installed the facts for hostname and domain match the dns_name.

if :use_beaker_hostnames: is true, set the :vmhostname and hostname of each ec2 machine to the host from the beaker hosts file.

Returns:

  • (@hosts)


766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 766

def set_hostnames
  if @options[:use_beaker_hostnames]
    @hosts.each do |host|
      host[:vmhostname] = host.name
      if host['platform'] =~ /el-7/
        # on el-7 hosts, the hostname command doesn't "stick" randomly
        host.exec(Command.new("hostnamectl set-hostname #{host.name}"))
      elsif host['platform'] =~ /windows/
        @logger.notify('aws-sdk: Change hostname on windows is not supported.')
      else
        next if host['platform'] =~ /f5-|netscaler/
        host.exec(Command.new("hostname #{host.name}"))
        if host['vmname'] =~ /^amazon/
          # Amazon Linux requires this to preserve host name changes across reboots.
          # http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/set-hostname.html
          # Also note that without an elastic ip set, while this will
          # preserve the hostname across a full shutdown/startup of the vm
          # (as opposed to a reboot) -- the ip address will have changed.
          host.exec(Command.new("sed -ie '/^HOSTNAME/ s/=.*/=#{host.name}/' /etc/sysconfig/network"))
        end
      end
    end
  else
    @hosts.each do |host|
      host[:vmhostname] = host[:dns_name]
      if host['platform'] =~ /el-7/
        # on el-7 hosts, the hostname command doesn't "stick" randomly
        host.exec(Command.new("hostnamectl set-hostname #{host.hostname}"))
      elsif host['platform'] =~ /windows/
        @logger.notify('aws-sdk: Change hostname on windows is not supported.')
      else
        next if host['platform'] =~ /ft-|netscaler/
        host.exec(Command.new("hostname #{host.hostname}"))
        if host['vmname'] =~ /^amazon/
          # See note above
          host.exec(Command.new("sed -ie '/^HOSTNAME/ s/=.*/=#{host.hostname}/' /etc/sysconfig/network"))
        end
      end
    end
  end
end

#test_split_installObject

Adds port 8143 to host if master, database and dashboard are not on same instance



1157
1158
1159
1160
1161
1162
1163
1164
1165
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 1157

def test_split_install
  @hosts.each do |host|
    mono_roles = ['master', 'database', 'dashboard']
    roles_intersection = host[:roles] & mono_roles
    if roles_intersection.size != 3 && roles_intersection.any?
      host[:additional_ports] ? host[:additional_ports].push(8143) : host[:additional_ports] = [8143]
    end
  end
end

#vpc_by_id(id) ⇒ Aws::EC2::Types::Vpc

Provided an id return a VPC object. VPC object will respond to methods described here: AWS VPC Object.

Parameters:

  • id (String)

    The id of the VPC to return

Returns:

  • (Aws::EC2::Types::Vpc)

    An Aws::EC2 vpc object



179
180
181
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 179

def vpc_by_id(id)
  client.describe_vpcs(:vpc_ids => [id]).vpcs.first
end

#vpcsArray<Aws::EC2::Types::Vpc>

Return all VPCs currently on ec2.

Returns:

  • (Array<Aws::EC2::Types::Vpc>)

    An array of Aws::EC2 vpc objects

See Also:



186
187
188
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 186

def vpcs
  client.describe_vpcs.vpcs
end

#wait_for_status(state_name, instances, &block) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Wait until all instances reach the desired state. Each Hash in instances must contain an :instance and :host value.

FIXME: rename to #wait_for_state

Parameters:

  • state_name (String)

    EC2 state to wait for, ‘running’, ‘stopped’, etc.

  • instances

    Enumerable<HashSymbol=>EC2::Instance,Host>

  • block (Proc)

    more complex checks can be made by passing a block in. This overrides the status parameter. EC2::Instance objects from the hosts will be yielded to the passed block



496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 496

def wait_for_status(state_name, instances, &block)
  # Wait for each node to reach status :running
  @logger.notify("aws-sdk: Waiting for all hosts to be #{state_name}")
  instances.each do |x|
    name = x[:host] ? x[:host].name : x[:name]
    instance = x[:instance]
    @logger.notify("aws-sdk: Wait for node #{name} to be #{state_name}")
    # Here we keep waiting for the machine state to reach 'running' with an
    # exponential backoff for each poll.
    # TODO: should probably be a in a shared method somewhere
    for tries in 1..10
      refreshed_instance = instance_by_id(instance.instance_id)

      if refreshed_instance.nil?
        @logger.debug("Instance #{name} not yet available (#{e})")
      else
        if block_given?
          test_result = yield refreshed_instance
        else
          test_result = refreshed_instance.state.name.to_s == state_name.to_s
        end
        if test_result
          x[:instance] = refreshed_instance
          # Always sleep, so the next command won't cause a throttle
          backoff_sleep(tries)
          break
        elsif tries == 10
          raise "Instance never reached state #{state_name}"
        end
      end

      backoff_sleep(tries)
    end
  end
end

#wait_for_status_netdevvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

if any host is an netdev one, these checks will happen once across all of the hosts, and then we’ll exit

This method returns an undefined value.

Handles special checks needed for netdev platforms.



539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
# File 'lib/beaker/hypervisor/aws_sdk.rb', line 539

def wait_for_status_netdev()
  @hosts.each do |host|
    if host['platform'] =~ /f5-|netscaler/
      wait_for_status(:running, @hosts)

      wait_for_status(nil, @hosts) do |instance|
        instance_status_collection = client.describe_instance_status({:instance_ids => [instance.instance_id]})
        first_instance = instance_status_collection.first[:instance_statuses].first
        first_instance[:instance_status][:status] == "ok" if first_instance
      end

      break
    end
  end
end