Class: RightScale::CloudUtilities

Inherits:
Object
  • Object
show all
Defined in:
lib/clouds/cloud_utilities.rb

Constant Summary collapse

IP_ADDRESS_REGEX =
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
DEFAULT_WHATS_MY_IP_HOST_NAME =
'eip-us-east.rightscale.com'
DEFAULT_WHATS_MY_IP_TIMEOUT =
10 * 60
DEFAULT_WHATS_MY_IP_RETRY_DELAY =
5

Class Method Summary collapse

Class Method Details

.can_contact_metadata_server?(addr, port, timeout = 2) ⇒ Boolean

Attempt to connect to the given address on the given port as a quick verification that the metadata service is available.

Parameters

addr(String)

address of the metadata service

port(Number)

port of the metadata service

timeout(Number)::Optional - time to wait for a response

Return

connected(Boolean)

true if a connection could be made, false otherwise

Returns:

  • (Boolean)


58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/clouds/cloud_utilities.rb', line 58

def self.(addr, port, timeout=2)
  t = Socket.new(Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0)
  saddr = Socket.pack_sockaddr_in(port, addr)
  connected = false

  begin
    t.connect_nonblock(saddr)
  rescue Errno::EINPROGRESS
    r, w, e = IO::select(nil, [t], nil, timeout)
    if !w.nil?
      connected = true
    else
      begin
        t.connect_nonblock(saddr)
      rescue Errno::EISCONN
        t.close
        connected = true
      rescue SystemCallError
      end
    end
  rescue SystemCallError
  end

  connected
end

.has_mac?(ohai, mac) ⇒ Boolean

Does an interface have the given mac address?

Parameters

ohai(Mash)

ohai state

mac(String)

MAC address to find

Return

(Boolean)

true if there is an interface with the given mac address

Returns:

  • (Boolean)


44
45
46
# File 'lib/clouds/cloud_utilities.rb', line 44

def self.has_mac?(ohai, mac)
  !!ohai[:network][:interfaces].values.detect { |iface| !iface[:arp].nil? && iface[:arp].value?(mac) }
end

.ip_for_interface(ohai, interface) ⇒ Object

Finds the first ip address for a given interface in the ohai mash

Parameters

ohai(Mash)

ohai state

interface(Symbol)

symbol of the interface (:eth0, :eth1, …)

Return

address(String|nil)

ip address associated with the given interface, nil if interface could not be found



92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/clouds/cloud_utilities.rb', line 92

def self.ip_for_interface(ohai, interface)
  address = nil
  if ohai[:network] != nil &&
     ohai[:network][:interfaces] != nil &&
     ohai[:network][:interfaces][interface] != nil &&
     ohai[:network][:interfaces][interface][:addresses] != nil

    addresses = ohai[:network][:interfaces][interface][:addresses].find { |key, item| item['family'] == 'inet' }
    address = addresses.first unless addresses.nil?
  end

  address
end

.ip_for_windows_interface(ohai, connection_type) ⇒ Object

Finds the first ip address that matches a given connection type (private|public)

Parameters

ohai(Mash)

ohai state

connection_type(Symbol)

either ‘public’ or ‘private’

Return

address(String|nil)

ip address associated with the given interface, nil if interface could not be found



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/clouds/cloud_utilities.rb', line 114

def self.ip_for_windows_interface(ohai, connection_type)
  address = nil

  # find the right interface
  if ohai[:network] != nil &&
     ohai[:network][:interfaces] != nil
    interface = ohai[:network][:interfaces].values.find do |item|
      !(item.nil? || item[:instance].nil?) && item[:instance][:net_connection_id] == connection_type
    end

    # grab the ip if there is one
    if interface != nil &&
       interface["configuration"] != nil &&
       interface["configuration"]["ip_address"] != nil
      address = interface["configuration"]["ip_address"].first
    end
  end

  address
end

.query_whats_my_ip(options = {}) ⇒ Object

Queries a whats-my-ip service for the public IP address of this instance. can query either for an expected IP or for any IP which is voted by majority (or unanimously). this is no guarantee that an instance actually has a public IP individually assigned to it as a private cloud instance will still appear to have it’s router’s public IP as it’s own address.

Parameters

options(String)

expected IP address or nil (no DNS names)

options[TrueClass|FalseClass]

true if vote must be unanimous, false for simple majority of all responders

options(String)

host name for whats-my-ip query or DEFAULT_WHATS_MY_IP_HOST_NAME

options(Logger)

logger or defaults to null logger

options[Fixnum]

timeout in seconds or DEFAULT_WHATS_MY_IP_TIMEOUT

options[Fixnum]

retry delay in seconds or DEFAULT_WHATS_MY_IP_RETRY_DELAY

Return

public_ip(String)

the consensus public IP for this instance or nil

Raises:

  • (ArgumentError)


169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
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
236
237
238
239
240
241
242
# File 'lib/clouds/cloud_utilities.rb', line 169

def self.query_whats_my_ip(options={})
  expected_ip = options[:expected_ip]
  raise ArgumentError.new("expected_ip is invalid") if expected_ip && !(expected_ip =~ IP_ADDRESS_REGEX)
  unanimous = options[:unanimous] || false
  host_name = options[:host_name] || DEFAULT_WHATS_MY_IP_HOST_NAME
  logger = options[:logger] || Logger.new(::RightScale::Platform::Shell::NULL_OUTPUT_NAME)
  timeout = options[:timeout] || DEFAULT_WHATS_MY_IP_TIMEOUT
  retry_delay = options[:retry_delay] || DEFAULT_WHATS_MY_IP_RETRY_DELAY

  if expected_ip
    logger.info("Waiting for IP=#{expected_ip}")
  else
    logger.info("Waiting for any IP to converge.")
  end

  # attempt to dig some hosts.
  hosts = `dig +short #{host_name}`.strip.split
  if hosts.empty?
    logger.info("No hosts to poll for IP from #{host_name}.")
  else
    # a little randomization avoids hitting the same hosts from each
    # instance since there is no guarantee that the hosts are returned in
    # random order.
    hosts = hosts.sort { (rand(2) * 2) - 1 }
    if logger.debug?
      message = ["Using these hosts to check the IP:"]
      hosts.each { |host| message << "  #{host}" }
      message << "-------------------------"
      logger.debug(message.join("\n"))
    end

    unanimity = hosts.count
    required_votes = unanimous ? unanimity : (1 + unanimity / 2)
    logger.info("Required votes = #{required_votes}/#{unanimity}")
    end_time = Time.now + timeout
    loop do
      reported_ips_to_voters = {}
      address_to_hosts = {}
      hosts.each do |host|
        address = `curl --max-time 1 -S -s http://#{host}/ip/mine`.strip
        logger.debug("Host=#{host} reports IP=#{address}")
        address_to_hosts[address] ||= []
        address_to_hosts[address] << host
        if expected_ip
          vote = (address_to_hosts[expected_ip] || []).count
          popular_address = expected_ip
        else
          popular_address = nil
          vote = 0
          address_to_hosts.each do |address, hosts|
            if hosts.count > vote
              vote = hosts.count
              popular_address = address
            end
          end
        end
        if vote >= required_votes
          logger.info("IP=#{popular_address} has the required vote count of #{required_votes}.")
          return popular_address
        end
      end

      # go around again, if possible.
      now_time = Time.now
      break if now_time >= end_time
      retry_delay = [retry_delay, end_time - now_time].min.to_i
      logger.debug("Sleeping for #{retry_delay} seconds...")
      sleep retry_delay
      retry_delay = [retry_delay * 2, 60].min  # a little backoff helps when launching thousands
    end
    logger.info("Never got the required vote count of #{required_votes}/#{unanimity} after #{timeout} seconds; public IP did not converge.")
    return nil
  end
end

.split_metadata(data, splitter, hash, name_value_delimiter = '=') ⇒ Object

Splits data on the given splitter character and merges to the given hash.

Parameters

data(String)

raw data

splitter(String)

splitter character

hash(Hash)

hash to merge

name_value_delimiter(String)

name/value delimiter (defaults to ‘=’)

Return

hash(Hash)

merged hash result



145
146
147
148
149
150
151
# File 'lib/clouds/cloud_utilities.rb', line 145

def self.(data, splitter, hash, name_value_delimiter = '=')
  data.split(splitter).each do |pair|
    name, value = pair.split(name_value_delimiter, 2)
    hash[name.strip] = value.strip if name && value
  end
  hash
end