Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Google Compute Engine as a cloud platform #127

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
101 changes: 101 additions & 0 deletions lib/ohai/mixin/gce_metadata.rb
@@ -0,0 +1,101 @@
#
# Author:: Ranjib Dey (<dey.ranjib@gmail.com>)
# 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 CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require 'net/http'
require 'socket'

module Ohai
module Mixin
module GCEMetadata

GCE_METADATA_ADDR = "metadata.google.internal" unless defined?(GCE_METADATA_ADDR)
GCE_METADATA_URL = "/0.1/meta-data" unless defined?(GCE_METADATA_URL)

def can_metadata_connect?(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

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

def fetch_metadata(id='')
uri = "#{GCE_METADATA_URL}/#{id}"
response = http_client.get(uri)
return nil unless response.code == "200"

if json?(response.body)
data = StringIO.new(response.body)
parser = Yajl::Parser.new
parser.parse(data)
elsif has_trailing_slash?(id) or (id == '')
temp={}
response.body.split("\n").each do |sub_attr|
temp[sanitize_key(sub_attr)] = fetch_metadata("#{id}#{sub_attr}")
end
temp
else
response.body
end
end

def json?(data)
data = StringIO.new(data)
parser = Yajl::Parser.new
begin
parser.parse(data)
true
rescue Yajl::ParseError
false
end
end

def multiline?(data)
data.lines.to_a.size > 1
end

def has_trailing_slash?(data)
!! ( data =~ %r{/$} )
end

def sanitize_key(key)
key.gsub(/\-|\//, '_')
end
end
end
end
36 changes: 36 additions & 0 deletions lib/ohai/plugins/cloud.rb
Expand Up @@ -17,6 +17,7 @@
provides "cloud"

require_plugin "ec2"
require_plugin "gce"
require_plugin "rackspace"
require_plugin "eucalyptus"
require_plugin "linode"
Expand All @@ -29,6 +30,41 @@ def create_objects
cloud[:public_ips] = Array.new
cloud[:private_ips] = Array.new
end
#---------------------------------------
# Google Compute Engine (gce)
#--------------------------------------

def on_gce?
gce != nil
end
def get_gce_values
cloud[:public_ipv4] = []
cloud[:local_ipv4] = []

public_ips = gce['network']["networkInterface"].collect do |interface|
if interface.has_key?('accessConfiguration')
interface['accessConfiguration'].collect{|ac| ac['externalIp']}
end
end.flatten.compact

private_ips = gce['network']["networkInterface"].collect do |interface|
interface['ip']
end.compact

cloud[:public_ips] += public_ips
cloud[:private_ips] += private_ips
cloud[:public_ipv4] += public_ips
cloud[:public_hostname] = nil
cloud[:local_ipv4] += private_ips
cloud[:local_hostname] = gce['hostname']
cloud[:provider] = "gce"
end

# setup gce cloud
if on_gce?
create_objects
get_gce_values
end

# ----------------------------------------
# ec2
Expand Down
40 changes: 40 additions & 0 deletions lib/ohai/plugins/gce.rb
@@ -0,0 +1,40 @@
#
# Author:: Ranjib Dey (<dey.ranjib@google.com>)
# 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 CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

provides "gce"

require 'ohai/mixin/gce_metadata'

extend Ohai::Mixin::GCEMetadata
GOOGLE_SYSFS_DMI = '/sys/firmware/dmi/entries/1-0/raw'

#https://developers.google.com/compute/docs/instances#dmi
def has_google_dmi?
::File.read(GOOGLE_SYSFS_DMI).include?('Google')
end

def looks_like_gce?
hint?('gce') || has_google_dmi? && can_metadata_connect?(GCE_METADATA_ADDR,80)
end

if looks_like_gce?
Ohai::Log.debug("looks_like_gce? == true")
gce Mash.new
fetch_metadata.each {|k, v| gce[k] = v }
else
Ohai::Log.debug("looks_like_gce? == false")
false
end
130 changes: 130 additions & 0 deletions spec/unit/plugins/gce_spec.rb
@@ -0,0 +1,130 @@
#
# Author:: Ranjib Dey (dey.ranjib@gmail.com)
# 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 'open-uri'

describe Ohai::System, "plugin gce" do
before(:each) do
@ohai = Ohai::System.new
@ohai.stub!(:require_plugin).and_return(true)
end

shared_examples_for "!gce" do
it "should NOT attempt to fetch the gce metadata" do
@ohai.should_not_receive(:http_client)
@ohai._require_plugin("gce")
end
end

shared_examples_for "gce" do
before(:each) do
@http_client = mock("Net::HTTP client")
@ohai.stub!(:http_client).and_return(@http_client)
IO.stub!(:select).and_return([[],[1],[]])
t = mock("connection")
t.stub!(:connect_nonblock).and_raise(Errno::EINPROGRESS)
Socket.stub!(:new).and_return(t)
Socket.stub!(:pack_sockaddr_in).and_return(nil)
end

it "should recursively fetch metadata" do
@http_client.should_receive(:get).
with("/0.1/meta-data/").
and_return(mock("Net::HTTPOK",
:body => "domain\nhostname\ndescription", :code=>"200"))
@http_client.should_receive(:get).
with("/0.1/meta-data/domain").
and_return(mock("Net::HTTPOK", :body => "test-domain", :code=>"200"))
@http_client.should_receive(:get).
with("/0.1/meta-data/hostname").
and_return(mock("Net::HTTPOK", :body => "test-host", :code=>"200"))
@http_client.should_receive(:get).
with("/0.1/meta-data/description").
and_return(mock("Net::HTTPOK", :body => "test-description", :code=>"200"))

@ohai._require_plugin("gce")

@ohai[:gce].should_not be_nil
@ohai[:gce]['hostname'].should == "test-host"
@ohai[:gce]['domain'].should == "test-domain"
@ohai[:gce]['description'].should == "test-description"
end

it "should properly parse json metadata" do
@http_client.should_receive(:get).
with("/0.1/meta-data/").
and_return(mock("Net::HTTP Response", :body => "attached-disks\n", :code=>"200"))
@http_client.should_receive(:get).
with("/0.1/meta-data/attached-disks").
and_return(mock("Net::HTTP Response", :body => '{"disks":[{"deviceName":"boot",
"index":0,"mode":"READ_WRITE","type":"EPHEMERAL"}]}', :code=>"200"))

@ohai._require_plugin("gce")

@ohai[:gce].should_not be_nil
@ohai[:gce]['attached_disks'].should eq({"disks"=>[{"deviceName"=>"boot",
"index"=>0,"mode"=>"READ_WRITE",
"type"=>"EPHEMERAL"}]})
end
end

describe "with dmi and metadata address connected" do
it_should_behave_like "gce"
before(:each) do
File.should_receive(:read).with('/sys/firmware/dmi/entries/1-0/raw').and_return('Google')
end
end

describe "without dmi and metadata address connected" do
it_should_behave_like "!gce"
before(:each) do
File.should_receive(:read).with('/sys/firmware/dmi/entries/1-0/raw').and_return('Test')
end
end

describe "with hint file" do
it_should_behave_like "gce"

before(:each) do
File.stub!(:exist?).with('/etc/chef/ohai/hints/gce.json').and_return(true)
File.stub!(:read).with('/etc/chef/ohai/hints/gce.json').and_return('')
File.stub!(:exist?).with('C:\chef\ohai\hints/gce.json').and_return(true)
File.stub!(:read).with('C:\chef\ohai\hints/gce.json').and_return('')
end
end

describe "without hint file" do
it_should_behave_like "!gce"

before(:each) do
File.stub!(:exist?).with('/etc/chef/ohai/hints/gce.json').and_return(false)
File.stub!(:exist?).with('C:\chef\ohai\hints/gce.json').and_return(false)
end
end

describe "with ec2 cloud file" do
it_should_behave_like "!gce"

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('')
File.stub!(:exist?).with('C:\chef\ohai\hints/ec2.json').and_return(true)
File.stub!(:read).with('C:\chef\ohai\hints/ec2.json').and_return('')
end
end
end