From 44f249531ad23df7610d3db98bf5dc2615c5d219 Mon Sep 17 00:00:00 2001 From: Mike Javorski Date: Tue, 5 Feb 2013 10:02:04 -0800 Subject: [PATCH 01/12] Add proper response codes to http request mocks --- spec/unit/plugins/ec2_spec.rb | 48 ++++++++++++++-------------- spec/unit/plugins/eucalyptus_spec.rb | 18 +++++------ 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/spec/unit/plugins/ec2_spec.rb b/spec/unit/plugins/ec2_spec.rb index 51f157923..4c9b91443 100644 --- a/spec/unit/plugins/ec2_spec.rb +++ b/spec/unit/plugins/ec2_spec.rb @@ -47,16 +47,16 @@ 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 +71,22 @@ 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 +96,16 @@ 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 +116,7 @@ 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 +131,16 @@ @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 +164,7 @@ @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 +178,16 @@ 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 +195,5 @@ 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 d78b9951a..7a108d4b3 100644 --- a/spec/unit/plugins/eucalyptus_spec.rb +++ b/spec/unit/plugins/eucalyptus_spec.rb @@ -40,16 +40,16 @@ @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 +84,7 @@ @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 +98,16 @@ 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 +115,5 @@ File.stub!(:read).with('C:\chef\ohai\hints/ec2.json').and_return('') end end - + end From 639c9cebb59719489ec6e2358bdab69e1c2e7090 Mon Sep 17 00:00:00 2001 From: Mike Javorski Date: Wed, 6 Feb 2013 20:37:17 -0800 Subject: [PATCH 02/12] [#OHAI-434] Detect and use the latest (recognized-working) EC2 metadata API Supported API's are determined at instance launch and are not extended over the life of the instance. As such the current code - which makes an assumption of a specific version - will fail depending on the age of the instance. This new 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, the plugin returns an empty hash. --- lib/ohai/mixin/ec2_metadata.rb | 59 ++++++++++++++++++---------- spec/unit/plugins/ec2_spec.rb | 3 ++ spec/unit/plugins/eucalyptus_spec.rb | 3 ++ 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/lib/ohai/mixin/ec2_metadata.rb b/lib/ohai/mixin/ec2_metadata.rb index 3cef74025..4450b2871 100644 --- a/lib/ohai/mixin/ec2_metadata.rb +++ b/lib/ohai/mixin/ec2_metadata.rb @@ -25,8 +25,9 @@ module Mixin 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 +58,86 @@ def can_metadata_connect?(addr, port, timeout=2) connected end + def best_api_version + response = http_client.get("/") + versions = response.body.split("\n") + 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}") + 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) + http_client.get("/#{api_version}/meta-data/#{id}") + 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 +158,3 @@ def metadata_key(key) end end end - diff --git a/spec/unit/plugins/ec2_spec.rb b/spec/unit/plugins/ec2_spec.rb index 4c9b91443..e27788f4b 100644 --- a/spec/unit/plugins/ec2_spec.rb +++ b/spec/unit/plugins/ec2_spec.rb @@ -42,6 +42,9 @@ 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 diff --git a/spec/unit/plugins/eucalyptus_spec.rb b/spec/unit/plugins/eucalyptus_spec.rb index 7a108d4b3..afe80ed49 100644 --- a/spec/unit/plugins/eucalyptus_spec.rb +++ b/spec/unit/plugins/eucalyptus_spec.rb @@ -38,6 +38,9 @@ @http_client = mock("Net::HTTP client") @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", :code => "200")) From 1904600d611a431ae7cb6ef8f3434963292232c1 Mon Sep 17 00:00:00 2001 From: Mike Javorski Date: Wed, 10 Apr 2013 16:41:51 -0700 Subject: [PATCH 03/12] Add a class comment explaining the multi-version retrieval logic --- lib/ohai/mixin/ec2_metadata.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/ohai/mixin/ec2_metadata.rb b/lib/ohai/mixin/ec2_metadata.rb index 4450b2871..700b3f336 100644 --- a/lib/ohai/mixin/ec2_metadata.rb +++ b/lib/ohai/mixin/ec2_metadata.rb @@ -22,6 +22,22 @@ 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) From 4f0f1ee0d8dddf8ed4d70b6e3517d874abb8d8b4 Mon Sep 17 00:00:00 2001 From: Mike Javorski Date: Fri, 12 Apr 2013 13:38:35 -0700 Subject: [PATCH 04/12] Handle non-200 response codes in response to EC2 best_api_version query --- lib/ohai/mixin/ec2_metadata.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ohai/mixin/ec2_metadata.rb b/lib/ohai/mixin/ec2_metadata.rb index 700b3f336..a09830863 100644 --- a/lib/ohai/mixin/ec2_metadata.rb +++ b/lib/ohai/mixin/ec2_metadata.rb @@ -76,6 +76,7 @@ def can_metadata_connect?(addr, port, timeout=2) def best_api_version response = http_client.get("/") + return nil if (response.code != '200') versions = response.body.split("\n") until (versions.empty? || EC2_SUPPORTED_VERSIONS.include?(versions.last)) do pv = versions.pop From db97a05d334ce1014c4f72074de3571afb122d7a Mon Sep 17 00:00:00 2001 From: Mike Javorski Date: Fri, 12 Apr 2013 13:39:18 -0700 Subject: [PATCH 05/12] Extend EC2 mixin specs to ensure that proper metadata API is selected --- spec/unit/plugins/ec2_spec.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/spec/unit/plugins/ec2_spec.rb b/spec/unit/plugins/ec2_spec.rb index e27788f4b..0448dc91b 100644 --- a/spec/unit/plugins/ec2_spec.rb +++ b/spec/unit/plugins/ec2_spec.rb @@ -44,7 +44,14 @@ 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")) + and_return(mock("Net::HTTP Response", :body => "1.0\n2011-05-01\n2012-01-12\nUnsupported", :code => "200")) + end + + it "should determine the best supported ec2 metadata api version" do + @http_client.should_receive(:get). + with("/2012-01-12/meta-data/"). + and_return(mock("Net::HTTP Response", :body => "", :code => "200")) + @ohai._require_plugin("ec2") end it "should recursively fetch all the ec2 metadata" do From c19b4928f49a312ba4160a03c5253ce680e89b99 Mon Sep 17 00:00:00 2001 From: Mike Javorski Date: Fri, 12 Apr 2013 16:27:55 -0700 Subject: [PATCH 06/12] Revert db97a05 "Extend EC2 mixin specs..." as btm has added an additional spec. --- spec/unit/plugins/ec2_spec.rb | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/spec/unit/plugins/ec2_spec.rb b/spec/unit/plugins/ec2_spec.rb index 0448dc91b..e27788f4b 100644 --- a/spec/unit/plugins/ec2_spec.rb +++ b/spec/unit/plugins/ec2_spec.rb @@ -44,14 +44,7 @@ Socket.stub!(:new).and_return(t) @http_client.should_receive(:get). with("/").twice. - and_return(mock("Net::HTTP Response", :body => "1.0\n2011-05-01\n2012-01-12\nUnsupported", :code => "200")) - end - - it "should determine the best supported ec2 metadata api version" do - @http_client.should_receive(:get). - with("/2012-01-12/meta-data/"). - and_return(mock("Net::HTTP Response", :body => "", :code => "200")) - @ohai._require_plugin("ec2") + and_return(mock("Net::HTTP Response", :body => "2012-01-12", :code => "200")) end it "should recursively fetch all the ec2 metadata" do From 7e38fae002d09d8c5efb0afebb097728a9da436c Mon Sep 17 00:00:00 2001 From: Bryan McLellan Date: Fri, 12 Apr 2013 15:29:17 -0700 Subject: [PATCH 07/12] OHAI-434: Add unit tests for Ec2Metadata#best_api_version --- spec/unit/mixin/ec2_metadata_spec.rb | 72 ++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 spec/unit/mixin/ec2_metadata_spec.rb diff --git a/spec/unit/mixin/ec2_metadata_spec.rb b/spec/unit/mixin/ec2_metadata_spec.rb new file mode 100644 index 000000000..0d2060c72 --- /dev/null +++ b/spec/unit/mixin/ec2_metadata_spec.rb @@ -0,0 +1,72 @@ +# +# Author:: Bryan McLellan +# 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" 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 +end From 52ac18e80ba90224ae7cc2945274ec7d5d601c2e Mon Sep 17 00:00:00 2001 From: Mike Javorski Date: Fri, 12 Apr 2013 16:16:34 -0700 Subject: [PATCH 08/12] Whitespace --- spec/unit/mixin/ec2_metadata_spec.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/unit/mixin/ec2_metadata_spec.rb b/spec/unit/mixin/ec2_metadata_spec.rb index 0d2060c72..9862b1484 100644 --- a/spec/unit/mixin/ec2_metadata_spec.rb +++ b/spec/unit/mixin/ec2_metadata_spec.rb @@ -20,7 +20,7 @@ require 'ohai/mixin/ec2_metadata' describe Ohai::Mixin::Ec2Metadata do - let(:mixin) { + let(:mixin) { metadata_object = Object.new.extend(Ohai::Mixin::Ec2Metadata) http_client = mock("Net::HTTP client") http_client.stub!(:get).and_return(response) @@ -31,39 +31,39 @@ 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" 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 From ee04f0d9244f753cdbbc517b1f20271f80735ec1 Mon Sep 17 00:00:00 2001 From: Mike Javorski Date: Fri, 12 Apr 2013 16:21:18 -0700 Subject: [PATCH 09/12] OHAI-434: Raise error on non-200 response when determining EC2 metadata version --- lib/ohai/mixin/ec2_metadata.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/ohai/mixin/ec2_metadata.rb b/lib/ohai/mixin/ec2_metadata.rb index a09830863..14a844adc 100644 --- a/lib/ohai/mixin/ec2_metadata.rb +++ b/lib/ohai/mixin/ec2_metadata.rb @@ -76,7 +76,9 @@ def can_metadata_connect?(addr, port, timeout=2) def best_api_version response = http_client.get("/") - return nil if (response.code != '200') + unless response.code == '200' + raise "Unable to determine EC2 metadata version (returned #{response.code} response)" + end versions = response.body.split("\n") until (versions.empty? || EC2_SUPPORTED_VERSIONS.include?(versions.last)) do pv = versions.pop From 9dc1965c1e4f6b6054dfc8102fff442c7917e291 Mon Sep 17 00:00:00 2001 From: Mike Javorski Date: Fri, 12 Apr 2013 16:21:38 -0700 Subject: [PATCH 10/12] OHAI-434: Raise error when no supported EC2 metadata version is found --- lib/ohai/mixin/ec2_metadata.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/ohai/mixin/ec2_metadata.rb b/lib/ohai/mixin/ec2_metadata.rb index 14a844adc..0b750aae4 100644 --- a/lib/ohai/mixin/ec2_metadata.rb +++ b/lib/ohai/mixin/ec2_metadata.rb @@ -85,6 +85,9 @@ def best_api_version 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 From e636075ceaca5c20f706ec33f921172ba04609b1 Mon Sep 17 00:00:00 2001 From: Mike Javorski Date: Fri, 12 Apr 2013 16:24:39 -0700 Subject: [PATCH 11/12] OHAI-434: Order metadata versions with basic String/Array#sort --- lib/ohai/mixin/ec2_metadata.rb | 3 +++ spec/unit/mixin/ec2_metadata_spec.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/ohai/mixin/ec2_metadata.rb b/lib/ohai/mixin/ec2_metadata.rb index 0b750aae4..0c66cf31c 100644 --- a/lib/ohai/mixin/ec2_metadata.rb +++ b/lib/ohai/mixin/ec2_metadata.rb @@ -79,7 +79,10 @@ def best_api_version 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' diff --git a/spec/unit/mixin/ec2_metadata_spec.rb b/spec/unit/mixin/ec2_metadata_spec.rb index 9862b1484..d327fabd2 100644 --- a/spec/unit/mixin/ec2_metadata_spec.rb +++ b/spec/unit/mixin/ec2_metadata_spec.rb @@ -39,8 +39,8 @@ 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" do + it "returns the most recent version (using string sort)" do mixin.best_api_version.should == "2011-05-01" end end From 99fda9c132e318c851ecfce94690ada8e5434f60 Mon Sep 17 00:00:00 2001 From: Mike Javorski Date: Fri, 12 Apr 2013 16:26:18 -0700 Subject: [PATCH 12/12] OHAI-434: Raise error on non-200 response while retrieving EC2 metadata --- lib/ohai/mixin/ec2_metadata.rb | 6 +++++- spec/unit/mixin/ec2_metadata_spec.rb | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/ohai/mixin/ec2_metadata.rb b/lib/ohai/mixin/ec2_metadata.rb index 0c66cf31c..49b4647e1 100644 --- a/lib/ohai/mixin/ec2_metadata.rb +++ b/lib/ohai/mixin/ec2_metadata.rb @@ -99,7 +99,11 @@ def http_client end def metadata_get(id, api_version) - http_client.get("/#{api_version}/meta-data/#{id}") + 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) diff --git a/spec/unit/mixin/ec2_metadata_spec.rb b/spec/unit/mixin/ec2_metadata_spec.rb index d327fabd2..a91da4c1b 100644 --- a/spec/unit/mixin/ec2_metadata_spec.rb +++ b/spec/unit/mixin/ec2_metadata_spec.rb @@ -69,4 +69,14 @@ 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