Module: Ohai::Mixin::Ec2Metadata

Included in:
CloudstackMetadata
Defined in:
lib/ohai/mixin/ec2_metadata.rb

Overview

This code parses the EC2 Instance Metadata API to provide details of the running instance.

Earlier version of this code assumed a specific version of the metadata API was available. Unfortunately the API versions supported by a particular instance are determined at instance launch and are not extended over the life of the instance. As such the earlier code would fail depending on the age of the instance.

The updated code probes the instance metadata endpoint for available versions, determines the most advanced version known to work and executes the metadata retrieval using that version.

If no compatible version is found, an empty hash is returned.

Constant Summary collapse

EC2_METADATA_ADDR =
"169.254.169.254"
EC2_SUPPORTED_VERSIONS =
%w[ 1.0 2007-01-19 2007-03-01 2007-08-29 2007-10-10 2007-12-15
2008-02-01 2008-09-01 2009-04-04 2011-01-01 2011-05-01 2012-01-12 ]
EC2_ARRAY_VALUES =
%w(security-groups)
EC2_ARRAY_DIR =
%w(network/interfaces/macs)
EC2_JSON_DIR =
%w(iam)

Instance Method Summary collapse

Instance Method Details

#best_api_versionObject


77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/ohai/mixin/ec2_metadata.rb', line 77

def best_api_version
  response = http_client.get("/")
  if response.code == '404'
    Ohai::Log.debug("Received HTTP 404 from metadata server while determining API version, assuming 'latest'")
    return "latest"
  elsif response.code != '200'
    raise "Unable to determine EC2 metadata version (returned #{response.code} response)"
  end
  # Note: Sorting the list of versions may have unintended consequences in
  # non-EC2 environments. It appears to be safe in EC2 as of 2013-04-12.
  versions = response.body.split("\n")
  versions = response.body.split("\n").sort
  until (versions.empty? || EC2_SUPPORTED_VERSIONS.include?(versions.last)) do
    pv = versions.pop
    Ohai::Log.debug("EC2 shows unsupported metadata version: #{pv}") unless pv == 'latest'
  end
  Ohai::Log.debug("EC2 metadata version: #{versions.last}")
  if versions.empty?
    raise "Unable to determine EC2 metadata version (no supported entries found)"
  end
  versions.last
end

#can_metadata_connect?(addr, port, timeout = 2) ⇒ Boolean


51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/ohai/mixin/ec2_metadata.rb', line 51

def (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
  Ohai::Log.debug("can_metadata_connect? == #{connected}")
  connected
end

#fetch_dir_metadata(id, api_version) ⇒ Object


158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/ohai/mixin/ec2_metadata.rb', line 158

def (id, api_version)
   = Hash.new
   = (id, api_version)
  if 
    .split("\n").each do |o|
      key = expand_path(o)
      if key[-1..-1] != '/'
        retr_meta = ("#{id}#{key}", api_version)
        [(key)] = retr_meta ? retr_meta : ''
      elsif not key.eql?('/')
        [key[0..-2]] = ("#{id}#{key}", api_version)
      end
    end
    
  end
end

#fetch_json_dir_metadata(id, api_version) ⇒ Object


175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/ohai/mixin/ec2_metadata.rb', line 175

def (id, api_version)
   = Hash.new
   = (id, api_version)
  if 
    .split("\n").each do |o|
      key = expand_path(o)
      if key[-1..-1] != '/'
        retr_meta = ("#{id}#{key}", api_version)
        data = retr_meta ? retr_meta : ''
        json = StringIO.new(data)
        parser = FFI_Yajl::Parser.new
        [(key)] = parser.parse(json)
      elsif not key.eql?('/')
        [key[0..-2]] = ("#{id}#{key}", api_version)
      end
    end
    
  end
end

#fetch_metadata(id = '', api_version = nil) ⇒ Object


125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/ohai/mixin/ec2_metadata.rb', line 125

def (id='', api_version=nil)
  api_version ||= best_api_version
  return Hash.new if api_version.nil?

   = Hash.new
   = (id, api_version)
  if 
    .split("\n").each do |o|
      key = expand_path("#{id}#{o}")
      if key[-1..-1] != '/'
        [(key)] =
          if EC2_ARRAY_VALUES.include? key
            retr_meta = (key, api_version)
            retr_meta ? retr_meta.split("\n") : retr_meta
          else
            (key, api_version)
          end
      elsif not key.eql?(id) and not key.eql?('/')
        name = key[0..-2]
        sym = (name)
        if EC2_ARRAY_DIR.include?(name)
          [sym] = (key, api_version)
        elsif EC2_JSON_DIR.include?(name)
          [sym] = (key, api_version)
        else
          (key, api_version).each{|k,v| [k] = v}
        end
      end
    end
    
  end
end

#fetch_userdataObject


195
196
197
198
199
200
# File 'lib/ohai/mixin/ec2_metadata.rb', line 195

def fetch_userdata()
  api_version = best_api_version
  return nil if api_version.nil?
  response = http_client.get("/#{api_version}/user-data/")
  response.code == "200" ? response.body : nil
end

#http_clientObject


100
101
102
# File 'lib/ohai/mixin/ec2_metadata.rb', line 100

def http_client
  Net::HTTP.start().tap {|h| h.read_timeout = 600}
end

#metadata_get(id, api_version) ⇒ Object

Get metadata for a given path and API version


111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/ohai/mixin/ec2_metadata.rb', line 111

def (id, api_version)
  path = "/#{api_version}/meta-data/#{id}"
  response = http_client.get(path)
  case response.code
  when '200'
    response.body
  when '404'
    Ohai::Log.debug("Encountered 404 response retreiving EC2 metadata path: #{path} ; continuing.")
    nil
  else
    raise "Encountered error retrieving EC2 metadata (#{path} returned #{response.code} response)"
  end
end