Class: Souffle::Provider::AWS

Inherits:
Base
  • Object
show all
Defined in:
lib/souffle/provider/aws.rb

Overview

The AWS souffle provider.

Instance Attribute Summary collapse

Attributes inherited from Base

#system

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#cookbook_paths, #create_cookbooks_tarball, #create_ssh_dir_if_missing, #generate_chef_json, #name, #role_paths, #ssh_key, #ssh_key_exists?, #ssh_key_path

Constructor Details

#initializeAWS

Setup the internal AWS configuration and object.



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/souffle/provider/aws.rb', line 33

def initialize
  super()
  @access_key    = @system.try_opt(:aws_access_key)
  @access_secret = @system.try_opt(:aws_access_secret)
  @newest_cookbooks = create_cookbooks_tarball

  if Souffle::Config[:debug]
    logger = Souffle::Log.logger
  else
    logger = Logger.new('/dev/null')
  end
  
  @ec2 = RightAws::Ec2.new(
    @access_key, @access_secret,
    :region => @system.try_opt(:aws_region),
    :logger => logger)
  rescue
    raise Souffle::Exceptions::InvalidAwsKeys,
          "AWS access keys are required to operate on EC2"
end

Instance Attribute Details

#access_keyObject (readonly)

Returns the value of attribute access_key.



30
31
32
# File 'lib/souffle/provider/aws.rb', line 30

def access_key
  @access_key
end

#access_secretObject (readonly)

Returns the value of attribute access_secret.



30
31
32
# File 'lib/souffle/provider/aws.rb', line 30

def access_secret
  @access_secret
end

Class Method Details

.get_base_ec2_infoRightAws::Ec2

Returns the base (configured) ec2 object for status updates.

Returns:



716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
# File 'lib/souffle/provider/aws.rb', line 716

def get_base_ec2_info
  access_key = Souffle::Config[:aws_access_key]
  access_secret = Souffle::Config[:aws_access_secret]
  aws_region = Souffle::Config[:aws_region]

  if Souffle::Config[:debug]
    logger = Souffle::Log.logger
  else
    logger = Logger.new('/dev/null')
  end

  RightAws::Ec2.new(access_key, access_secret,
    :region => aws_region, :logger => logger)
rescue
  nil
end

.update_status(ec2 = nil) ⇒ Object

Updates the souffle status with the latest AWS information.

Parameters:

  • ec2 (RightAws::Ec2) (defaults to: nil)

    The ec2 object to use for the status update.



702
703
704
705
706
707
708
709
710
711
# File 'lib/souffle/provider/aws.rb', line 702

def update_status(ec2=nil)
  ec2 = get_base_ec2_info if ec2.nil?
  return if ec2.nil?

  ec2.describe_instances(
      :filters => { 'tag-key' => "souffle" }).each do |instance|
    instance[:tags]["souffle"]
    # TODO: ADD Status update.
  end
end

Instance Method Details

#_format_device(node, device, filesystem = "ext4") ⇒ Object

Formats a device on a given node with the provided filesystem.

Parameters:

  • node (Souffle::Node)

    The node to format a device on.

  • device (String)

    The device to format.

  • filesystem (String) (defaults to: "ext4")

    The filesystem to use when formatting.



193
194
195
196
197
198
199
200
# File 'lib/souffle/provider/aws.rb', line 193

def _format_device(node, device, filesystem="ext4")
  return if node.options[:volumes].nil?
  setup_lvm(node)
  ssh_block(node) do |ssh|
    ssh.exec!("#{fs_formatter(filesystem)} #{device}")
    mount_lvm(node) { node.provisioner.device_formatted }
  end
end

#attach_ebs(node) ⇒ Object

Attaches ebs volumes to the given node.

Parameters:



392
393
394
395
396
397
398
399
400
401
402
403
404
# File 'lib/souffle/provider/aws.rb', line 392

def attach_ebs(node)
  Souffle::Log.info "#{node.log_prefix} Attaching EBS..."
  node.options[:volumes].each_with_index do |volume, index|
    @ec2.attach_volume(
      volume[:aws_id],
      node.options[:aws_instance_id],
      volume_id_to_aws_device(index) )
    @ec2.modify_block_device_delete_on_termination_attribute(
      node.options[:aws_instance_id],
      volume_id_to_aws_device(index),
      node.try_opt(:delete_on_termination) )
  end
end

#boot(node) ⇒ Object

Wait for the machine to boot up.

Parameters:



175
176
177
# File 'lib/souffle/provider/aws.rb', line 175

def boot(node)
  wait_for_boot(node)
end

#create_ebs(node) ⇒ Array

Creates ebs volumes for the given node.

Parameters:

Returns:

  • (Array)

    The list of created ebs volumes.



309
310
311
312
313
314
315
316
317
318
319
# File 'lib/souffle/provider/aws.rb', line 309

def create_ebs(node)
  volumes = Array.new
  node.options.fetch(:volume_count, 0).times do
    volumes << @ec2.create_volume(
      node.try_opt(:aws_snapshot_id),
      node.try_opt(:aws_ebs_size),
      node.try_opt(:aws_availability_zone) )
  end
  node.options[:volumes] = volumes
  volumes
end

#create_node(node, tag = nil) ⇒ Object

Takes a node definition and begins the provisioning process.

Parameters:

  • node (Souffle::Node)

    The node to instantiate.

  • tag (String) (defaults to: nil)

    The tag to use for the node.



91
92
93
94
95
96
97
98
99
100
101
# File 'lib/souffle/provider/aws.rb', line 91

def create_node(node, tag=nil)
  opts = prepare_node_options(node)
  node.options[:tag] = tag unless tag.nil?

  create_ebs(node)
  instance_info = @ec2.launch_instances(
    node.try_opt(:aws_image_id), opts).first

  node.options[:aws_instance_id] = instance_info[:aws_instance_id]
  wait_until_node_running(node) { tag_node(node, node.try_opt(:tag)) }
end

#create_raid(node, devices = [], md_device = 0, chunk = 64, level = "raid0") ⇒ Object

Creates a raid array with the given requirements.

options are: linear, raid0, 0, stipe, raid1, 1, mirror, raid4, 4, raid5, 5, raid6, 6, multipath, mp

Parameters:

  • node (Souffle::Node)

    The node to the raid for.

  • devices (Array) (defaults to: [])

    The list of devices to use for the raid.

  • md_device (Fixnum) (defaults to: 0)

    The md device number.

  • chunk (Fixnum) (defaults to: 64)

    The chunk size in kilobytes.

  • level (String) (defaults to: "raid0")

    The raid level to use.



157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/souffle/provider/aws.rb', line 157

def create_raid(node, devices=[], md_device=0, chunk=64, level="raid0")
  dev_list = devices.map { |s| "#{s}1" }
  mdadm_string =  "/sbin/mdadm --create /dev/md#{md_device} "
  mdadm_string << "--chunk=#{chunk} --level=#{level} "
  mdadm_string << "--raid-devices=#{devices.size} #{dev_list.join(' ')}"

  export_mdadm = "/sbin/mdadm --detail --scan > /etc/mdadm.conf"

  ssh_block(node) do |ssh|
    ssh.exec!(mdadm_string)
    ssh.exec!(export_mdadm)
    yield if block_given?
  end
end

#create_system(system, tag_prefix = nil) ⇒ String

Creates a system using aws as the provider.

Parameters:

  • system (Souffle::System)

    The system to instantiate.

  • tag_prefix (String) (defaults to: nil)

    The tag prefix to use for the system.

Returns:

  • (String)

    The tag for the created system.



73
74
75
76
77
78
# File 'lib/souffle/provider/aws.rb', line 73

def create_system(system, tag_prefix=nil)
  system.options[:tag] = generate_tag(tag_prefix)
  system.provisioner = Souffle::Provisioner::System.new(system, self)
  system.provisioner.initialized
  system.options[:tag]
end

#delete_ebs(node) ⇒ Object

Deletes the ebs volumes from a given node.

Parameters:



432
433
434
435
436
# File 'lib/souffle/provider/aws.rb', line 432

def delete_ebs(node)
  node.options[:volumes].each do |volume|
    @ec2.delete_volume(volume[:aws_id])
  end
end

#detach_and_delete_ebs(node) ⇒ Object

Detach and delete all volumes from a given node.

Parameters:



409
410
411
412
# File 'lib/souffle/provider/aws.rb', line 409

def detach_and_delete_ebs(node)
  detach_ebs(node, force=true)
  delete_ebs(node)
end

#detach_ebs(node, force = false) ⇒ Object

Detaches all ebs volumes from a given node.

detachment.

Parameters:

  • node (Souffle::Node)

    The node to detach volumes from.

  • force (Boolean) (defaults to: false)

    Whether or not to force the



419
420
421
422
423
424
425
426
427
# File 'lib/souffle/provider/aws.rb', line 419

def detach_ebs(node, force=false)
  node.options[:volumes].each_with_index do |volume, index|
    @ec2.detach_volume(
      volume[:aws_id],
      node.options[:aws_instance_id],
      volume_id_to_aws_device(index),
      force)
  end
end

#format_device(node) ⇒ Object

Formats all of the devices on a given node for the provisioner interface.

Parameters:

  • node (Souffle::Node)

    The node to format it’s new partitions.



182
183
184
185
186
# File 'lib/souffle/provider/aws.rb', line 182

def format_device(node)
  partition_device(node, "/dev/md0", "8e") do
    _format_device(node, "/dev/md0p1")
  end
end

#fs_formatter(filesystem) ⇒ String

Chooses the appropriate formatter for the given filesystem.

Parameters:

  • filesystem (String)

    The filessytem you intend to use.

Returns:

  • (String)

    The filesystem formatter.



694
695
696
# File 'lib/souffle/provider/aws.rb', line 694

def fs_formatter(filesystem)
  "mkfs.#{filesystem}"
end

#generate_tag(tag_prefix = "sys") ⇒ String

Generates a prefixed unique tag.

Parameters:

  • tag_prefix (String) (defaults to: "sys")

    The tag prefix to use.

Returns:

  • (String)

    The unique tag with prefix.



59
60
61
62
63
64
65
# File 'lib/souffle/provider/aws.rb', line 59

def generate_tag(tag_prefix="sys")
  if tag_prefix
    "#{tag_prefix}-#{SecureRandom.hex(4)}"
  else
    SecureRandom.hex(4)
  end
end

#instance_id_list(nodes) ⇒ Object

Takes a list of nodes and returns the list of their aws instance_ids.

Parameters:

  • nodes (Array)

    The list of nodes to get instance_id’s from.



83
84
85
# File 'lib/souffle/provider/aws.rb', line 83

def instance_id_list(nodes)
  Array(nodes).map { |n| n.options[:aws_instance_id] }
end

#kill(nodes) ⇒ Object

Takes a list of nodes and kills them. (Haha)

Parameters:



136
137
138
# File 'lib/souffle/provider/aws.rb', line 136

def kill(nodes)
  @ec2.terminate_instances(instance_id_list(nodes))
end

#kill_and_recreate(nodes) ⇒ Object

Takes a list of nodes kills them and then recreates them.

Parameters:

  • nodes (Souffle::Node)

    The list of nodes to kill and recreate.



143
144
145
146
# File 'lib/souffle/provider/aws.rb', line 143

def kill_and_recreate(nodes)
  kill(nodes)
  @provisioner.reclaimed
end

#mount_lvm(node) ⇒ Object

Mounts the newly created lvm configuration and adds it to fstab.

Parameters:



268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/souffle/provider/aws.rb', line 268

def mount_lvm(node)
  fstab_str =  "/dev/md0p1      /data"
  fstab_str << "     ext4    noatime,nodiratime  1  1"

  mount_str =  "mount -o rw,noatime,nodiratime"
  mount_str << " /dev/mapper/VolGroup00-data /data"
  ssh_block(node) do |ssh|
    ssh.exec!("mkdir /data")
    ssh.exec!(mount_str)
    ssh.exec!("echo #{fstab_str} >> /etc/fstab")
    ssh.exec!("echo #{fstab_str} >> /etc/mtab")
    yield if block_given?
  end
end

#partition(node, iteration = 0) ⇒ Object

Partition each of the volumes with raid for the node.

Parameters:

  • node (Souffle::Node)

    The node to partition the volumes on.

  • iteration (Fixnum) (defaults to: 0)

    The current retry iteration.



206
207
208
209
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
# File 'lib/souffle/provider/aws.rb', line 206

def partition(node, iteration=0)
  return node.provisioner.error_occurred if iteration == 3
  Souffle::PollingEvent.new(node) do
    timeout 30

    pre_event do
      @partitions = 0
      @provider = node.provider
      node.options[:volumes].each_with_index do |volume, index|
        @provider.partition_device(
          node, @provider.volume_id_to_device(index)) do |count|
          @partitions += count
        end
      end
    end

    event_loop do
      if @partitions == node.options[:volumes].size
        event_complete
        node.provisioner.partitioned_device
      end
    end

    error_handler do
      error_msg = "#{node.log_prefix} Timeout during partitioning..."
      Souffle::Log.error error_msg
      @provider.partition(node, iteration+1)
    end
  end
end

#partition_device(node, device, partition_type = "fd") ⇒ Object

Note:

Currently this is a naive implementation and uses the full disk.

Partitions a device on a given node with the given partition_type.

Parameters:

  • node (Souffle::Node)

    The node to partition a device on.

  • device (String)

    The device to partition.

  • partition_type (String) (defaults to: "fd")

    The type of partition to create.



244
245
246
247
248
249
250
251
# File 'lib/souffle/provider/aws.rb', line 244

def partition_device(node, device, partition_type="fd")
  partition_cmd =  "echo \",,#{partition_type}\""
  partition_cmd << "| /sbin/sfdisk #{device}"
  ssh_block(node) do |ssh|
    ssh.exec!("#{partition_cmd}")
    yield(1) if block_given?
  end
end

#prepare_node_options(node) ⇒ Hash

Prepares the node options using the system or global defaults.

Parameters:

  • node (Souffle::Node)

    The node you wish to prepare options for.

Returns:

  • (Hash)

    The options hash to pass into ec2 launch instance.



642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
# File 'lib/souffle/provider/aws.rb', line 642

def prepare_node_options(node)
  opts = Hash.new
  opts[:instance_type] = node.try_opt(:aws_instance_type)
  opts[:min_count] = 1
  opts[:max_count] = 1
  if using_vpc?(node)
    opts[:subnet_id] = node.try_opt(:aws_subnet_id)
    opts[:aws_subnet_id] = node.try_opt(:aws_subnet_id)
    opts[:aws_vpc_id] = Array(node.try_opt(:aws_vpc_id))
    opts[:group_ids] = Array(node.try_opt(:group_ids))
  else
    opts[:group_names] = node.try_opt(:group_names)
  end
  opts[:key_name] = node.try_opt(:key_name)
  opts
end

#provision(node) ⇒ Object

TODO:

Setup the chef/chef-solo tar gzip and ssh connections.

Provisions a node with the chef/chef-solo configuration.



480
481
482
483
484
485
486
487
# File 'lib/souffle/provider/aws.rb', line 480

def provision(node)
  set_hostname(node)
  if node.try_opt(:chef_provisioner).to_s.downcase == "solo"
    provision_chef_solo(node, generate_chef_json(node))
  else
    provision_chef_client(node)
  end
end

#provision_chef_client(node) ⇒ Object

TODO:

Chef client provisioner needs to be completed.

Provisions a box using the chef_client provisioner.



589
590
591
592
593
594
595
596
597
598
599
# File 'lib/souffle/provider/aws.rb', line 589

def provision_chef_client(node)
  client_cmds =  "chef-client -N #{node.fqdn} "
  client_cmds << "-j /tmp/client.json "
  client_cmds << "-S #{node.try_opt(:chef_server)} "
  n = node; ssh_block(node) do |ssh|
    write_temp_chef_json(ssh, n)
    ssh.exec!(client_cmds)
    cleanup_temp_chef_files(ssh, n)
    node.provisioner.provisioned
  end
end

#provision_chef_solo(node, solo_json) ⇒ Object

Provisions a box using the chef_solo provisioner.

Parameters:

  • node (String)

    The node to provision.

  • solo_json (String)

    The chef solo json string to use.



569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
# File 'lib/souffle/provider/aws.rb', line 569

def provision_chef_solo(node, solo_json)
  rsync_file(node, @newest_cookbooks, "/tmp")
  solo_config =  "node_name \"#{node.fqdn}\"\n"
  solo_config << "cookbook_path \"/tmp/cookbooks\"\n"
  solo_config << 'role_path "/tmp/roles"'
  n = node; ssh_block(node) do |ssh|
    ssh.exec!("sleep 2; tar -zxf /tmp/cookbooks-latest.tar.gz -C /tmp")
    ssh.exec!("echo '#{solo_config}' >/tmp/solo.rb")
    ssh.exec!("echo '#{solo_json}' >/tmp/solo.json")
    ssh.exec!("chef-solo -c /tmp/solo.rb -j /tmp/solo.json")
    rm_files = %w{ /tmp/cookbooks /tmp/cookbooks-latest.tar.gz
      /tmp/roles /tmp/solo.rb /tmp/solo.json /tmp/chef_bootstrap }
    ssh.exec!("rm -rf #{rm_files}")
    n.provisioner.provisioned
  end
end

#rsync_file(node, file, path = '.') ⇒ Object

Rsync’s a file to a remote node.

Parameters:

  • node (Souffle::Node)

    The node to connect to.

  • file (String)

    The file to rsync.

  • path (String) (defaults to: '.')

    The remote path to rsync.



606
607
608
609
# File 'lib/souffle/provider/aws.rb', line 606

def rsync_file(node, file, path='.')
  n = @ec2.describe_instances(node.options[:aws_instance_id]).first
  super(n[:private_ip_address], file, path)
end

#set_hostname(node) ⇒ Object

Sets the hostname for the given node for the chef run.

Parameters:

  • node (Souffle:Node)

    The node to update the hostname for.



555
556
557
558
559
560
561
562
563
# File 'lib/souffle/provider/aws.rb', line 555

def set_hostname(node)
  local_lookup = "127.0.0.1       #{node.fqdn} #{node.name}\n"
  fqdn = node.fqdn
  ssh_block(node) do |ssh|
    ssh.exec!("hostname '#{fqdn}'")
    ssh.exec!("echo \"#{local_lookup}\" >> /etc/hosts")
    ssh.exec!("echo \"HOSTNAME=#{fqdn}\" >> /etc/sysconfig/network")
  end
end

#setup_lvm(node) ⇒ Object

Sets up the lvm partition for the raid devices.

Parameters:



256
257
258
259
260
261
262
263
# File 'lib/souffle/provider/aws.rb', line 256

def setup_lvm(node)
  return if node.options[:volumes].nil?
  ssh_block(node) do |ssh|
    ssh.exec!("pvcreate /dev/md0p1")
    ssh.exec!("vgcreate VolGroup00 /dev/md0p1")
    ssh.exec!("lvcreate -l 100%vg VolGroup00 -n data")
  end
end

#setup_mdadm(node) ⇒ Object

Installs mdadm (multiple device administration) to manage raid.

Parameters:



286
287
288
289
290
291
# File 'lib/souffle/provider/aws.rb', line 286

def setup_mdadm(node)
  n = node; ssh_block(node) do |ssh|
    ssh.exec!("/usr/bin/yum install -y mdadm")
    n.provisioner.mdadm_installed
  end
end

#setup_raid(node) ⇒ Object

Sets up software raid for the given node.

Parameters:



296
297
298
299
300
301
302
# File 'lib/souffle/provider/aws.rb', line 296

def setup_raid(node)
  volume_list = []
  node.options[:volumes].each_with_index do |volume, index|
    volume_list << volume_id_to_device(index)
  end
  create_raid(node, volume_list) { node.provisioner.raid_initialized }
end

#ssh_block(node, user = "root", pass = nil, opts = {}) {|EventMachine::Ssh::Session| ... } ⇒ Object

Yields an ssh object to manage the commands naturally from there.

will be attempted. see Net::SSH.start and #send_wait calls.

Parameters:

  • node (Souffle::Node)

    The node to run commands against.

  • user (String) (defaults to: "root")

    The user to connect as.

  • pass (String, NilClass) (defaults to: nil)

    By default publickey and password auth

  • opts (Hash) (defaults to: {})

    The options hash.

Options Hash (opts):

  • :net_ssh (Hash)

    Options to pass to Net::SSH,

  • :timeout (Hash) — default: TIMEOUT

    default timeout for all #wait_for

  • :reconnect (Boolean)

    When disconnected reconnect.

Yields:

  • (EventMachine::Ssh::Session)

    The ssh session.



625
626
627
628
629
630
631
632
633
634
635
# File 'lib/souffle/provider/aws.rb', line 625

def ssh_block(node, user="root", pass=nil, opts={})
  n = @ec2.describe_instances(node.options[:aws_instance_id]).first
  if n.nil?
    raise AwsInstanceDoesNotExist,
      "The AWS instance (#{node.options[:aws_instance_id]}) does not exist."
  else
    key = n[:ssh_key_name]
    opts[:keys] = ssh_key(key) if ssh_key_exists?(key)
    super(n[:private_ip_address], user, pass, opts)
  end
end

#stop_nodes(nodes) ⇒ Object

Takes a list of nodes an stops the instances.

Parameters:



122
123
124
# File 'lib/souffle/provider/aws.rb', line 122

def stop_nodes(nodes)
  @ec2.stop_instances(instance_id_list(nodes))
end

#stop_system(system) ⇒ Object

Stops all nodes in a given system.

Parameters:



129
130
131
# File 'lib/souffle/provider/aws.rb', line 129

def stop_system(system)
  stop_nodes(system.nodes)
end

#subnet_exists?(node) ⇒ Boolean

Checks whether or not the subnet currently exists.

Parameters:

  • node (Souffle::Node)

    The node to check vpc information for.

Returns:

  • (Boolean)

    Whether or not the subnet exists.



472
473
474
475
# File 'lib/souffle/provider/aws.rb', line 472

def subnet_exists?(node)
  @ec2.describe_subnets({:filters =>
    { 'subnet-id' => node.try_opt(:aws_subnet_id) } }).any?
end

#tag_node(node, tag = "") ⇒ Object

Tags a node and it’s volumes.

Parameters:

  • node (Souffle::Node)

    The node to tag.

  • tag (String) (defaults to: "")

    The tag to use for the node.



107
108
109
110
111
112
113
114
115
116
117
# File 'lib/souffle/provider/aws.rb', line 107

def tag_node(node, tag="")
  @ec2.create_tags(Array(node.options[:aws_instance_id]), {
    :Name => node.name,
    :souffle => tag
  })
  volume_ids = node.options[:volumes].map { |vol| vol[:aws_id] }
  @ec2.create_tags(Array(volume_ids), {
    :instance_id => node.options[:aws_instance_id],
    :souffle => tag
  }) unless Array(volume_ids).empty?
end

#using_vpc?(node) ⇒ Boolean

Whether or not to use a vpc instance and subnet for provisioning.

specific subnet.

Parameters:

  • node (Souffle::Node)

    The node to check vpc information for.

Returns:

  • (Boolean)

    Whether to use a vpc instance and



443
444
445
446
# File 'lib/souffle/provider/aws.rb', line 443

def using_vpc?(node)
  !!node.try_opt(:aws_vpc_id) and
  !!node.try_opt(:aws_subnet_id)
end

#volume_id_to_aws_device(volume_id) ⇒ String

Note:

This starts at /dev/xvda and goes to /dev/xvdb, etc.

Takes the volume count in the array and converts it to a device name.

And due to the special case on AWS, skips /dev/xvde.

Parameters:

  • volume_id (Fixnum)

    The count in the array for the volume id.

Returns:

  • (String)

    The device string to mount to.



682
683
684
685
686
687
# File 'lib/souffle/provider/aws.rb', line 682

def volume_id_to_aws_device(volume_id)
  if volume_id >= 4
    volume_id += 1
  end
  "/dev/hd#{(volume_id + "a".ord).chr}"
end

#volume_id_to_device(volume_id) ⇒ String

Note:

This starts at /dev/xvda and goes to /dev/xvdb, etc.

Takes the volume count in the array and converts it to a device name.

And due to the special case on AWS, skips /dev/xvde.

Parameters:

  • volume_id (Fixnum)

    The count in the array for the volume id.

Returns:

  • (String)

    The device string to mount to.



667
668
669
670
671
672
# File 'lib/souffle/provider/aws.rb', line 667

def volume_id_to_device(volume_id)
  if volume_id >= 4
    volume_id += 1
  end
  "/dev/xvd#{(volume_id + "a".ord).chr}"
end

#vpc_exists?(node) ⇒ Boolean

Checks whether or not the vpc currently exists.

Parameters:

  • node (Souffle::Node)

    The node to check vpc information for.

Returns:

  • (Boolean)

    Whether or not the vpc exists.



462
463
464
465
# File 'lib/souffle/provider/aws.rb', line 462

def vpc_exists?(node)
  @ec2.describe_vpcs({:filters =>
    { 'vpc-id' => node.try_opt(:aws_vpc_id) } }).any?
end

#vpc_setup?(node) ⇒ Boolean

Checks whether or not the vpc and subnet are setup proeprly.

Parameters:

  • node (Souffle::Node)

    The node to check vpc information for.

Returns:

  • (Boolean)

    Whether or not the vpc is setup.



453
454
455
# File 'lib/souffle/provider/aws.rb', line 453

def vpc_setup?(node)
  vpc_exists? and subnet_exists?
end

#wait_for_boot(node, user = "root", pass = nil, opts = {}, poll_timeout = 100, iteration = 0) {|Eventmachine::Ssh:Session| ... } ⇒ Object

Waits for ssh to be accessible for a node for the initial connection and yields an ssh object to manage the commands naturally from there.

will be attempted. see Net::SSH.start and #send_wait calls.

Parameters:

  • node (Souffle::Node)

    The node to run commands against.

  • user (String) (defaults to: "root")

    The user to connect as.

  • pass (String, NilClass) (defaults to: nil)

    By default publickey and password auth

  • opts (Hash) (defaults to: {})

    The options hash.

  • poll_timeout (Fixnum) (defaults to: 100)

    The maximum number of seconds to wait.

  • iteration (Fixnum) (defaults to: 0)

    The current retry iteration.

Options Hash (opts):

  • :net_ssh (Hash)

    Options to pass to Net::SSH,

  • :timeout (Hash) — default: TIMEOUT

    default timeout for all #wait_for

  • :reconnect (Boolean)

    When disconnected reconnect.

Yields:

  • (Eventmachine::Ssh:Session)

    The ssh session.



507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
# File 'lib/souffle/provider/aws.rb', line 507

def wait_for_boot(node, user="root", pass=nil, opts={},
                  poll_timeout=100, iteration=0, &blk)
  return node.provisioner.error_occurred if iteration == 3

  ec2 = @ec2; Souffle::PollingEvent.new(node) do
    timeout poll_timeout
    interval EM::Ssh::Connection::TIMEOUT

    pre_event do
      Souffle::Log.info "#{node.log_prefix} Waiting for ssh..."
      @provider = node.provider
      @blk = blk
    end

    event_loop do
      n = ec2.describe_instances(node.options[:aws_instance_id]).first
      unless n.nil?
        key = n[:ssh_key_name]
        if @provider.ssh_key_exists?(key)
          opts[:keys] = @provider.ssh_key(key)
        end
        opts[:password] = pass unless pass.nil?
        opts[:paranoid] = false
        address = n[:private_ip_address]

        EM::Ssh.start(address, user, opts) do |connection|
          connection.errback  { |err| nil }
          connection.callback do |ssh|
            event_complete
            node.provisioner.booted
            @blk.call(ssh) unless @blk.nil?
            ssh.close
          end
        end
      end
    end

    error_handler do
      Souffle::Log.error "#{node.log_prefix} SSH Boot timeout..."
      @provider.wait_for_boot(node, user, pass, opts,
        poll_timeout, iteration+1, &blk)
    end
  end
end

#wait_until_ebs_ready(node, poll_timeout = 100, poll_interval = 2) ⇒ Object

Polls the EBS volume status until they’re ready then runs the given block.

Parameters:

  • node (Souffle::Node)

    The node to wait for EBS on.

  • poll_timeout (Fixnum) (defaults to: 100)

    The maximum number of seconds to wait.

  • poll_interval (Fixnum) (defaults to: 2)

    The interval in seconds to poll EC2.



360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/souffle/provider/aws.rb', line 360

def wait_until_ebs_ready(node, poll_timeout=100, poll_interval=2)
  ec2 = @ec2; Souffle::PollingEvent.new(node) do
    timeout poll_timeout
    interval poll_interval

    pre_event do
      Souffle::Log.info "#{node.log_prefix} Waiting for EBS to be ready..."
      @provider = node.provider
      @volume_ids = node.options[:volumes].map { |v| v[:aws_id] }
    end

    event_loop do
      vol_status = ec2.describe_volumes(@volume_ids)
      avail = Array(vol_status).select { |v| v[:aws_status] == "available" }
      if avail.size == vol_status.size
        event_complete
        @provider.attach_ebs(node)
        node.provisioner.created
      end
    end

    error_handler do
      error_msg = "#{node.log_prefix} Waiting for EBS Timed out..."
      Souffle::Log.error error_msg
      node.provisioner.error_occurred
    end
  end
end

#wait_until_node_running(node, poll_timeout = 100, poll_interval = 2, &blk) ⇒ Object

Polls the EC2 instance information until it is in the running state.

Parameters:

  • node (Souffle::Node)

    The node to wait until running on.

  • poll_timeout (Fixnum) (defaults to: 100)

    The maximum number of seconds to wait.

  • poll_interval (Fixnum) (defaults to: 2)

    The interval in seconds to poll EC2.



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
# File 'lib/souffle/provider/aws.rb', line 326

def wait_until_node_running(node, poll_timeout=100, poll_interval=2, &blk)
  ec2 = @ec2; Souffle::PollingEvent.new(node) do
    timeout poll_timeout
    interval poll_interval

    pre_event do
      Souffle::Log.info "#{node.log_prefix} Waiting for node running..."
      @provider = node.provider
      @blk = blk
    end

    event_loop do
      instance = ec2.describe_instances(
        node.options[:aws_instance_id]).first
      if instance[:aws_state].downcase == "running"
        event_complete
        @blk.call unless @blk.nil?
        @provider.wait_until_ebs_ready(node)
      end
    end

    error_handler do
      error_msg = "#{node.log_prefix} Wait for node running timeout..."
      Souffle::Log.error error_msg
      node.provisioner.error_occurred
    end
  end
end