summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryan McLellan <btm@opscode.com>2013-04-15 06:28:38 -0700
committerBryan McLellan <btm@opscode.com>2013-04-15 06:28:38 -0700
commita82462867180e7b3f20dc9bdbbd3ac5d7a7b41ec (patch)
tree18f7262cb5a4e5bed52901871ac4e256f74d4e60
parent505c11d9c53dc19c8604266608782292eeaf8bef (diff)
parent99fda9c132e318c851ecfce94690ada8e5434f60 (diff)
downloadohai-a82462867180e7b3f20dc9bdbbd3ac5d7a7b41ec.tar.gz
Merge branch 'OHAI-434'
-rw-r--r--lib/ohai/mixin/ec2_metadata.rb88
-rw-r--r--spec/unit/mixin/ec2_metadata_spec.rb82
-rw-r--r--spec/unit/plugins/ec2_spec.rb51
-rw-r--r--spec/unit/plugins/eucalyptus_spec.rb21
4 files changed, 189 insertions, 53 deletions
diff --git a/lib/ohai/mixin/ec2_metadata.rb b/lib/ohai/mixin/ec2_metadata.rb
index 3cef7402..49b4647e 100644
--- a/lib/ohai/mixin/ec2_metadata.rb
+++ b/lib/ohai/mixin/ec2_metadata.rb
@@ -22,11 +22,28 @@ require 'socket'
module Ohai
module Mixin
+ ##
+ # 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.
+ #
module Ec2Metadata
EC2_METADATA_ADDR = "169.254.169.254" unless defined?(EC2_METADATA_ADDR)
- EC2_METADATA_URL = "/2012-01-12/meta-data" unless defined?(EC2_METADATA_URL)
- EC2_USERDATA_URL = "/2012-01-12/user-data" unless defined?(EC2_USERDATA_URL)
+ 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)
@@ -57,67 +74,99 @@ module Ohai
connected
end
+ def best_api_version
+ response = http_client.get("/")
+ unless 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
+
def http_client
Net::HTTP.start(EC2_METADATA_ADDR).tap {|h| h.read_timeout = 600}
end
- def fetch_metadata(id='')
+ def metadata_get(id, api_version)
+ response = http_client.get("/#{api_version}/meta-data/#{id}")
+ unless response.code == '200'
+ raise "Encountered error retrieving EC2 metadata (returned #{response.code} response)"
+ end
+ response
+ end
+
+ def fetch_metadata(id='', api_version=nil)
+ api_version ||= best_api_version
+ return Hash.new if api_version.nil?
metadata = Hash.new
- http_client.get("#{EC2_METADATA_URL}/#{id}").body.split("\n").each do |o|
+ metadata_get(id, api_version).body.split("\n").each do |o|
key = expand_path("#{id}#{o}")
if key[-1..-1] != '/'
metadata[metadata_key(key)] =
if EC2_ARRAY_VALUES.include? key
- http_client.get("#{EC2_METADATA_URL}/#{key}").body.split("\n")
+ metadata_get(key, api_version).body.split("\n")
else
- http_client.get("#{EC2_METADATA_URL}/#{key}").body
+ metadata_get(key, api_version).body
end
elsif not key.eql?(id) and not key.eql?('/')
name = key[0..-2]
- sym = metadata_key(name)
+ sym = metadata_key(name)
if EC2_ARRAY_DIR.include?(name)
- metadata[sym] = fetch_dir_metadata(key)
+ metadata[sym] = fetch_dir_metadata(key, api_version)
elsif EC2_JSON_DIR.include?(name)
- metadata[sym] = fetch_json_dir_metadata(key)
+ metadata[sym] = fetch_json_dir_metadata(key, api_version)
else
- fetch_metadata(key).each{|k,v| metadata[k] = v}
+ fetch_metadata(key, api_version).each{|k,v| metadata[k] = v}
end
end
end
metadata
end
- def fetch_dir_metadata(id)
+ def fetch_dir_metadata(id, api_version)
metadata = Hash.new
- http_client.get("#{EC2_METADATA_URL}/#{id}").body.split("\n").each do |o|
+ metadata_get(id, api_version).body.split("\n").each do |o|
key = expand_path(o)
if key[-1..-1] != '/'
- metadata[metadata_key(key)] = http_client.get("#{EC2_METADATA_URL}/#{id}#{key}").body
+ metadata[metadata_key(key)] = metadata_get("#{id}#{key}", api_version).body
elsif not key.eql?('/')
- metadata[key[0..-2]] = fetch_dir_metadata("#{id}#{key}")
+ metadata[key[0..-2]] = fetch_dir_metadata("#{id}#{key}", api_version)
end
end
metadata
end
- def fetch_json_dir_metadata(id)
+ def fetch_json_dir_metadata(id, api_version)
metadata = Hash.new
- http_client.get("#{EC2_METADATA_URL}/#{id}").body.split("\n").each do |o|
+ metadata_get(id, api_version).body.split("\n").each do |o|
key = expand_path(o)
if key[-1..-1] != '/'
- data = http_client.get("#{EC2_METADATA_URL}/#{id}#{key}").body
+ data = metadata_get("#{id}#{key}", api_version).body
json = StringIO.new(data)
parser = Yajl::Parser.new
metadata[metadata_key(key)] = parser.parse(json)
elsif not key.eql?('/')
- metadata[key[0..-2]] = fetch_json_dir_metadata("#{id}#{key}")
+ metadata[key[0..-2]] = fetch_json_dir_metadata("#{id}#{key}", api_version)
end
end
metadata
end
def fetch_userdata()
- response = http_client.get("#{EC2_USERDATA_URL}/")
+ 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
@@ -138,4 +187,3 @@ module Ohai
end
end
end
-
diff --git a/spec/unit/mixin/ec2_metadata_spec.rb b/spec/unit/mixin/ec2_metadata_spec.rb
new file mode 100644
index 00000000..a91da4c1
--- /dev/null
+++ b/spec/unit/mixin/ec2_metadata_spec.rb
@@ -0,0 +1,82 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDIT"Net::HTTP Response"NS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb')
+require 'ohai/mixin/ec2_metadata'
+
+describe Ohai::Mixin::Ec2Metadata do
+ let(:mixin) {
+ metadata_object = Object.new.extend(Ohai::Mixin::Ec2Metadata)
+ http_client = mock("Net::HTTP client")
+ http_client.stub!(:get).and_return(response)
+ metadata_object.stub!(:http_client).and_return(http_client)
+ metadata_object
+ }
+
+ context "#best_api_version" do
+ context "with a sorted list of metadata versions" do
+ let(:response) { mock("Net::HTTP Response", :body => "1.0\n2011-05-01\n2012-01-12\nUnsupported", :code => "200") }
+
+ it "returns the most recent version" do
+ mixin.best_api_version.should == "2012-01-12"
+ end
+ end
+
+ context "with an unsorted list of metadata versions" do
+ let(:response) { mock("Net::HTTP Response", :body => "1.0\n2009-04-04\n2007-03-01\n2011-05-01\n2008-09-01\nUnsupported", :code => "200") }
+
+ it "returns the most recent version (using string sort)" do
+ mixin.best_api_version.should == "2011-05-01"
+ end
+ end
+
+ context "when no supported versions are found" do
+ let(:response) { mock("Net::HTTP Response", :body => "2020-01-01\nUnsupported", :code => "200") }
+
+ it "raises an error" do
+ lambda { mixin.best_api_version}.should raise_error
+ end
+ end
+
+ context "when the response code is 404" do
+ let(:response) { mock("Net::HTTP Response", :body => "1.0\n2011-05-01\n2012-01-12\nUnsupported", :code => "404") }
+
+ it "raises an error" do
+ lambda { mixin.best_api_version}.should raise_error
+ end
+ end
+
+ context "when the response code is unexpected" do
+ let(:response) { mock("Net::HTTP Response", :body => "1.0\n2011-05-01\n2012-01-12\nUnsupported", :code => "418") }
+
+ it "raises an error" do
+ lambda { mixin.best_api_version}.should raise_error
+ end
+ end
+ end
+
+ context "#metadata_get" do
+ context "when the response code is unexpected" do
+ let(:response) { mock("Net::HTTP Response", :body => "", :code => "418") }
+
+ it "raises an error" do
+ lambda { mixin.metadata_get('', '2012-01-12') }.should raise_error(RuntimeError)
+ end
+ end
+ end
+end
diff --git a/spec/unit/plugins/ec2_spec.rb b/spec/unit/plugins/ec2_spec.rb
index 51f15792..e27788f4 100644
--- a/spec/unit/plugins/ec2_spec.rb
+++ b/spec/unit/plugins/ec2_spec.rb
@@ -42,21 +42,24 @@ describe Ohai::System, "plugin ec2" do
t = mock("connection")
t.stub!(:connect_nonblock).and_raise(Errno::EINPROGRESS)
Socket.stub!(:new).and_return(t)
+ @http_client.should_receive(:get).
+ with("/").twice.
+ and_return(mock("Net::HTTP Response", :body => "2012-01-12", :code => "200"))
end
it "should recursively fetch all the ec2 metadata" do
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/").
- and_return(mock("Net::HTTP Response", :body => "instance_type\nami_id\nsecurity-groups"))
+ and_return(mock("Net::HTTP Response", :body => "instance_type\nami_id\nsecurity-groups", :code => "200"))
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/instance_type").
- and_return(mock("Net::HTTP Response", :body => "c1.medium"))
+ and_return(mock("Net::HTTP Response", :body => "c1.medium", :code => "200"))
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/ami_id").
- and_return(mock("Net::HTTP Response", :body => "ami-5d2dc934"))
+ and_return(mock("Net::HTTP Response", :body => "ami-5d2dc934", :code => "200"))
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/security-groups").
- and_return(mock("Net::HTTP Response", :body => "group1\ngroup2"))
+ and_return(mock("Net::HTTP Response", :body => "group1\ngroup2", :code => "200"))
@http_client.should_receive(:get).
with("/2012-01-12/user-data/").
and_return(mock("Net::HTTP Response", :body => "By the pricking of my thumb...", :code => "200"))
@@ -71,22 +74,22 @@ describe Ohai::System, "plugin ec2" do
it "should parse ec2 network/ directory as a multi-level hash" do
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/").
- and_return(mock("Net::HTTP Response", :body => "network/"))
+ and_return(mock("Net::HTTP Response", :body => "network/", :code => "200"))
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/network/").
- and_return(mock("Net::HTTP Response", :body => "interfaces/"))
+ and_return(mock("Net::HTTP Response", :body => "interfaces/", :code => "200"))
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/network/interfaces/").
- and_return(mock("Net::HTTP Response", :body => "macs/"))
+ and_return(mock("Net::HTTP Response", :body => "macs/", :code => "200"))
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/network/interfaces/macs/").
- and_return(mock("Net::HTTP Response", :body => "12:34:56:78:9a:bc/"))
+ and_return(mock("Net::HTTP Response", :body => "12:34:56:78:9a:bc/", :code => "200"))
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/network/interfaces/macs/12:34:56:78:9a:bc/").
- and_return(mock("Net::HTTP Response", :body => "public_hostname"))
+ and_return(mock("Net::HTTP Response", :body => "public_hostname", :code => "200"))
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/network/interfaces/macs/12:34:56:78:9a:bc/public_hostname").
- and_return(mock("Net::HTTP Response", :body => "server17.opscode.com"))
+ and_return(mock("Net::HTTP Response", :body => "server17.opscode.com", :code => "200"))
@ohai._require_plugin("ec2")
@ohai[:ec2].should_not be_nil
@@ -96,16 +99,16 @@ describe Ohai::System, "plugin ec2" do
it "should parse ec2 iam/ directory and its JSON files properly" do
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/").
- and_return(mock("Net::HTTP Response", :body => "iam/"))
+ and_return(mock("Net::HTTP Response", :body => "iam/", :code => "200"))
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/iam/").
- and_return(mock("Net::HTTP Response", :body => "security-credentials/"))
+ and_return(mock("Net::HTTP Response", :body => "security-credentials/", :code => "200"))
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/iam/security-credentials/").
- and_return(mock("Net::HTTP Response", :body => "MyRole"))
+ and_return(mock("Net::HTTP Response", :body => "MyRole", :code => "200"))
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/iam/security-credentials/MyRole").
- and_return(mock("Net::HTTP Response", :body => "{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2012-08-22T07:47:22Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"AAAAAAAA\",\n \"SecretAccessKey\" : \"SSSSSSSS\",\n \"Token\" : \"12345678\",\n \"Expiration\" : \"2012-08-22T11:25:52Z\"\n}"))
+ and_return(mock("Net::HTTP Response", :body => "{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2012-08-22T07:47:22Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"AAAAAAAA\",\n \"SecretAccessKey\" : \"SSSSSSSS\",\n \"Token\" : \"12345678\",\n \"Expiration\" : \"2012-08-22T11:25:52Z\"\n}", :code => "200"))
@ohai._require_plugin("ec2")
@ohai[:ec2].should_not be_nil
@@ -116,7 +119,7 @@ describe Ohai::System, "plugin ec2" do
it "should ignore \"./\" and \"../\" on ec2 metadata paths to avoid infinity loops" do
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/").
- and_return(mock("Net::HTTP Response", :body => ".\n./\n..\n../\npath1/.\npath2/./\npath3/..\npath4/../"))
+ and_return(mock("Net::HTTP Response", :body => ".\n./\n..\n../\npath1/.\npath2/./\npath3/..\npath4/../", :code => "200"))
@http_client.should_not_receive(:get).
with("/2012-01-12/meta-data/.")
@@ -131,16 +134,16 @@ describe Ohai::System, "plugin ec2" do
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/path1/").
- and_return(mock("Net::HTTP Response", :body => ""))
+ and_return(mock("Net::HTTP Response", :body => "", :code => "200"))
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/path2/").
- and_return(mock("Net::HTTP Response", :body => ""))
+ and_return(mock("Net::HTTP Response", :body => "", :code => "200"))
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/path3/").
- and_return(mock("Net::HTTP Response", :body => ""))
+ and_return(mock("Net::HTTP Response", :body => "", :code => "200"))
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/path4/").
- and_return(mock("Net::HTTP Response", :body => ""))
+ and_return(mock("Net::HTTP Response", :body => "", :code => "200"))
@ohai._require_plugin("ec2")
@@ -164,7 +167,7 @@ describe Ohai::System, "plugin ec2" do
@ohai[:network][:interfaces][:eth0][:arp] = {"169.254.1.0"=>"00:50:56:c0:00:08"}
end
end
-
+
describe "with ec2 cloud file" do
it_should_behave_like "ec2"
@@ -178,16 +181,16 @@ describe Ohai::System, "plugin ec2" do
describe "without cloud file" do
it_should_behave_like "!ec2"
-
+
before(:each) do
File.stub!(:exist?).with('/etc/chef/ohai/hints/ec2.json').and_return(false)
File.stub!(:exist?).with('C:\chef\ohai\hints/ec2.json').and_return(false)
end
end
-
+
describe "with rackspace cloud file" do
it_should_behave_like "!ec2"
-
+
before(:each) do
File.stub!(:exist?).with('/etc/chef/ohai/hints/rackspace.json').and_return(true)
File.stub!(:read).with('/etc/chef/ohai/hints/rackspace.json').and_return('')
@@ -195,5 +198,5 @@ describe Ohai::System, "plugin ec2" do
File.stub!(:read).with('C:\chef\ohai\hints/rackspace.json').and_return('')
end
end
-
+
end
diff --git a/spec/unit/plugins/eucalyptus_spec.rb b/spec/unit/plugins/eucalyptus_spec.rb
index d78b9951..afe80ed4 100644
--- a/spec/unit/plugins/eucalyptus_spec.rb
+++ b/spec/unit/plugins/eucalyptus_spec.rb
@@ -39,17 +39,20 @@ describe Ohai::System, "plugin eucalyptus" do
@ohai.stub!(:http_client).and_return(@http_client)
@http_client.should_receive(:get).
+ with("/").twice.
+ and_return(mock("Net::HTTP Response", :body => "2012-01-12", :code => "200"))
+ @http_client.should_receive(:get).
with("/2012-01-12/meta-data/").
- and_return(mock("Net::HTTP Response", :body => "instance_type\nami_id\nsecurity-groups"))
+ and_return(mock("Net::HTTP Response", :body => "instance_type\nami_id\nsecurity-groups", :code => "200"))
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/instance_type").
- and_return(mock("Net::HTTP Response", :body => "c1.medium"))
+ and_return(mock("Net::HTTP Response", :body => "c1.medium", :code => "200"))
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/ami_id").
- and_return(mock("Net::HTTP Response", :body => "ami-5d2dc934"))
+ and_return(mock("Net::HTTP Response", :body => "ami-5d2dc934", :code => "200"))
@http_client.should_receive(:get).
with("/2012-01-12/meta-data/security-groups").
- and_return(mock("Net::HTTP Response", :body => "group1\ngroup2"))
+ and_return(mock("Net::HTTP Response", :body => "group1\ngroup2", :code => "200"))
@http_client.should_receive(:get).
with("/2012-01-12/user-data/").
and_return(mock("Net::HTTP Response", :body => "By the pricking of my thumb...", :code => "200"))
@@ -84,7 +87,7 @@ describe Ohai::System, "plugin eucalyptus" do
@ohai[:network] = { "interfaces" => { "eth0" => { "addresses" => { "ff:ff:95:47:6E:ED"=> { "family" => "lladdr" } } } } }
end
end
-
+
describe "with eucalyptus cloud file" do
it_should_behave_like "eucalyptus"
@@ -98,16 +101,16 @@ describe Ohai::System, "plugin eucalyptus" do
describe "without cloud file" do
it_should_behave_like "!eucalyptus"
-
+
before(:each) do
File.stub!(:exist?).with('/etc/chef/ohai/hints/eucalyptus.json').and_return(false)
File.stub!(:exist?).with('C:\chef\ohai\hints/eucalyptus.json').and_return(false)
end
end
-
+
describe "with ec2 cloud file" do
it_should_behave_like "!eucalyptus"
-
+
before(:each) do
File.stub!(:exist?).with('/etc/chef/ohai/hints/ec2.json').and_return(true)
File.stub!(:read).with('/etc/chef/ohai/hints/ec2.json').and_return('')
@@ -115,5 +118,5 @@ describe Ohai::System, "plugin eucalyptus" do
File.stub!(:read).with('C:\chef\ohai\hints/ec2.json').and_return('')
end
end
-
+
end