Permalink
Browse files

Add OpenStack Agent

Change-Id: Icb3447fb454f8cba40e054ab37f87f2dcf9909d4
  • Loading branch information...
1 parent 3fe5cf1 commit 99a767415eafdbf6e8184f60f143b855366cdb90 @frodenas frodenas committed Jul 27, 2012
@@ -0,0 +1,18 @@
+# Copyright (c) 2009-2012 VMware, Inc.
+
+module Bosh::Agent
+ class Infrastructure::Openstack
+ require 'sigar'
+ require 'agent/infrastructure/openstack/settings'
+ require 'agent/infrastructure/openstack/registry'
+
+ def load_settings
+ Settings.new.load_settings
+ end
+
+ def get_network_settings(network_name, properties)
+ Settings.new.get_network_settings(network_name, properties)
+ end
+
+ end
+end
@@ -0,0 +1,113 @@
+# Copyright (c) 2009-2012 VMware, Inc.
+
+module Bosh::Agent
+ class Infrastructure::Openstack::Registry
+ class << self
+
+ API_TIMEOUT = 86400 * 3
+ CONNECT_TIMEOUT = 30
+ SERVER_DATA_URI = "http://169.254.169.254/1.0"
+
+ def get_uri(uri)
+ client = HTTPClient.new
+ client.send_timeout = API_TIMEOUT
+ client.receive_timeout = API_TIMEOUT
+ client.connect_timeout = CONNECT_TIMEOUT
+
+ response = client.get(SERVER_DATA_URI + uri)
+ unless response.status == 200
+ raise("Server metadata endpoint returned " \
+ "HTTP #{response.status}")
+ end
+
+ response.body
+ rescue HTTPClient::BadResponseError => e
+ raise("Received bad HTTP response for #{uri}: #{e.inspect}")
+ rescue HTTPClient::TimeoutError
+ raise("Timed out reading uri #{uri}, " \
+ "please make sure agent is running on OpenStack server")
+ rescue URI::Error, SocketError, Errno::ECONNREFUSED, SystemCallError => e
+ raise("Error requesting current server id from #{uri} #{e.inspect}")
+ end
+
+ ##
+ # Reads current server name from OpenStack user-data. We are assuming
+ # server name cannot change while current process is running
+ # and thus memoizing it.
+ def current_server_id
+ return @current_server_id if @current_server_id
+ user_data = get_json_from_url(SERVER_DATA_URI + "/user-data")
+ unless user_data.has_key?("server") &&
+ user_data["server"].has_key?("name")
+ raise("Cannot parse user data for endpoint #{user_data.inspect}")
+ end
+ @current_server_id = user_data["server"]["name"]
+ end
+
+ def get_json_from_url(url)
+ client = HTTPClient.new
+ client.send_timeout = API_TIMEOUT
+ client.receive_timeout = API_TIMEOUT
+ client.connect_timeout = CONNECT_TIMEOUT
+
+ headers = {"Accept" => "application/json"}
+ response = client.get(url, {}, headers)
+
+ if response.status != 200
+ raise("Cannot read settings for `#{url}' from registry, " \
+ "got HTTP #{response.status}")
+ end
+
+ body = Yajl::Parser.parse(response.body)
+ unless body.is_a?(Hash)
+ raise("Invalid response from #{url} , Hash expected, " \
+ "got #{body.class}: #{body}")
+ end
+
+ body
+
+ rescue HTTPClient::BadResponseError => e
+ raise("Received bad HTTP response from server registry: #{e.inspect}")
+ rescue HTTPClient::TimeoutError
+ raise("Timed out reading json from #{url}, " \
+ "please make sure agent is running on OpenStack server")
+ rescue URI::Error, SocketError, Errno::ECONNREFUSED, SystemCallError => e
+ raise("Error requesting registry information #{e.inspect}")
+ rescue Yajl::ParseError => e
+ raise("Cannot parse settings for from registry #{e.inspect}")
+ end
+
+ def get_registry_endpoint
+ user_data = get_json_from_url(SERVER_DATA_URI + "/user-data")
+ unless user_data.has_key?("registry") &&
+ user_data["registry"].has_key?("endpoint")
+ raise("Cannot parse user data for endpoint #{user_data.inspect}")
+ end
+ user_data["registry"]["endpoint"]
+ end
+
+ def get_openssh_key
+ get_uri("/meta-data/public-keys/0/openssh-key")
+ end
+
+ def get_settings
+ @registry_endpoint ||= get_registry_endpoint
+ url = "#{@registry_endpoint}/servers/#{current_server_id}/settings"
+ body = get_json_from_url(url)
+
+ settings = Yajl::Parser.parse(body["settings"])
+ unless settings.is_a?(Hash)
+ raise("Invalid settings format, " \
+ "Hash expected, got #{settings.class}: " \
+ "#{settings}")
+ end
+
+ settings
+
+ rescue Yajl::ParseError
+ raise("Cannot parse settings from registry #{@registry_endpoint}")
+ end
+
+ end
+ end
+end
@@ -0,0 +1,67 @@
+# Copyright (c) 2009-2012 VMware, Inc.
+
+module Bosh::Agent
+ class Infrastructure::Openstack::Settings
+
+ VIP_NETWORK_TYPE = "vip"
+ DHCP_NETWORK_TYPE = "dynamic"
+ AUTHORIZED_KEYS = File.join("/home/", BOSH_APP_USER, ".ssh/authorized_keys")
+
+ def initialize
+ @settings_file = Bosh::Agent::Config.settings_file
+ end
+
+ def logger
+ Bosh::Agent::Config.logger
+ end
+
+ def authorized_keys
+ AUTHORIZED_KEYS
+ end
+
+ def setup_openssh_key
+ public_key = Infrastructure::Openstack::Registry.get_openssh_key
+ if public_key.nil? || public_key.empty?
+ return
+ end
+ FileUtils.mkdir_p(File.dirname(authorized_keys))
+ File.open(authorized_keys, "w") { |f| f.write(public_key) }
+ FileUtils.chown(Bosh::Agent::BOSH_APP_USER, Bosh::Agent::BOSH_APP_GROUP, authorized_keys)
+ FileUtils.chmod(0644, authorized_keys)
+ end
+
+ def load_settings
+ setup_openssh_key
+ settings = Infrastructure::Openstack::Registry.get_settings
+ settings_json = Yajl::Encoder.encode(settings)
+ File.open(@settings_file, 'w') { |f| f.write(settings_json) }
+ Bosh::Agent::Config.settings = settings
+ end
+
+ def get_network_settings(network_name, properties)
+ unless properties["type"] &&
+ [VIP_NETWORK_TYPE, DHCP_NETWORK_TYPE].include?(properties["type"])
+ raise Bosh::Agent::StateError, "Unsupported network #{properties["type"]}"
+ end
+
+ # Nothing to do for "vip" networks
+ return nil if properties["type"] == VIP_NETWORK_TYPE
+
+ sigar = Sigar.new
+ net_info = sigar.net_info
+ ifconfig = sigar.net_interface_config(net_info.default_gateway_interface)
+
+ properties = {}
+ properties["ip"] = ifconfig.address
+ properties["netmask"] = ifconfig.netmask
+ properties["dns"] = []
+ properties["dns"] << net_info.primary_dns if net_info.primary_dns &&
+ !net_info.primary_dns.empty?
+ properties["dns"] << net_info.secondary_dns if net_info.secondary_dns &&
+ !net_info.secondary_dns.empty?
+ properties["gateway"] = net_info.default_gateway
+ properties
+ end
+
+ end
+end
@@ -60,8 +60,10 @@ def lookup_disk_by_cid(cid)
File.join('/dev', blockdev)
when "aws"
# AWS passes in the device name
- xvd_dev_path = get_xvd_path(disk_id)
- get_available_path(disk_id, xvd_dev_path)
+ get_available_path(disk_id)
+ when "openstack"
+ # OpenStack passes in the device name
+ get_available_path(disk_id)
else
raise Bosh::Agent::FatalError, "Lookup disk failed, unsupported infrastructure " \
"#{Bosh::Agent::Config.infrastructure_name}"
@@ -85,23 +87,28 @@ def detect_block_device(disk_id)
Dir[dev_path].first
end
- def get_xvd_path(dev_path)
- dev_path_suffix = dev_path.match("/dev/sd(.*)")[1]
- "/dev/xvd#{dev_path_suffix}"
+ def get_dev_paths(dev_path)
+ dev_paths = [] << dev_path
+ dev_path_suffix = dev_path.match("/dev/sd(.*)")
+ unless dev_path_suffix.nil?
+ dev_paths << "/dev/vd#{dev_path_suffix[1]}" # KVM
+ dev_paths << "/dev/xvd#{dev_path_suffix[1]}" # Xen
+ end
+ dev_paths
end
- def get_available_path(dev_path, xvd_dev_path)
+ def get_available_path(dev_path)
start = Time.now
- while Dir[dev_path, xvd_dev_path].empty?
- logger.info("Waiting for #{dev_path} or #{xvd_dev_path}")
+ dev_paths = get_dev_paths(dev_path)
+ while Dir.glob(dev_paths).empty?
+ logger.info("Waiting for #{dev_paths}")
sleep 0.1
if (Time.now - start) > dev_path_timeout
- raise Bosh::Agent::FatalError, "Timed out waiting for #{dev_path} or #{xvd_dev_path}"
+ raise Bosh::Agent::FatalError, "Timed out waiting for #{dev_paths}"
end
end
- return xvd_dev_path unless Dir[xvd_dev_path].empty?
- dev_path
+ Dir.glob(dev_paths).last
end
VSPHERE_DATA_DISK = "/dev/sdb"
@@ -116,8 +123,15 @@ def get_data_disk_device_name
raise Bosh::Agent::FatalError, "Unknown data or ephemeral disk"
end
- xvd_dev_path = get_xvd_path(dev_path)
- get_available_path(dev_path, xvd_dev_path)
+ get_available_path(dev_path)
+ when "openstack"
+ settings = Bosh::Agent::Config.settings
+ dev_path = settings['disks']['ephemeral']
+ unless dev_path
+ raise Bosh::Agent::FatalError, "Unknown data or ephemeral disk"
+ end
+
+ get_available_path(dev_path)
else
raise Bosh::Agent::FatalError, "Lookup disk failed, unsupported infrastructure " \
"#{Bosh::Agent::Config.infrastructure_name}"
@@ -16,6 +16,8 @@ def setup_networking
setup_networking_from_settings
when "aws"
# Nothing to do
+ when "openstack"
+ # Nothing to do
else
raise Bosh::Agent::FatalError, "Setup networking failed, unsupported infrastructure #{Bosh::Agent::Config.infrastructure_name}"
end
@@ -0,0 +1,48 @@
+# Copyright (c) 2009-2012 VMware, Inc.
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+Bosh::Agent::Infrastructure.new("openstack").infrastructure
+
+describe Bosh::Agent::Infrastructure::Openstack::Registry do
+
+ before(:each) do
+ @settings = { 'status' => "ok", 'settings' => settings_json }
+ end
+
+ it 'should get settings' do
+ Bosh::Agent::Infrastructure::Openstack::Registry.stub(:current_server_id).and_return("i-server")
+ Bosh::Agent::Infrastructure::Openstack::Registry.stub(:get_json_from_url).and_return(@settings)
+ Bosh::Agent::Infrastructure::Openstack::Registry.stub(:get_registry_endpoint).and_return("blah")
+ settings = Bosh::Agent::Infrastructure::Openstack::Registry.get_settings
+ settings.should == Yajl::Parser.new.parse(settings_json)
+ end
+
+ it 'should get registry endpoint' do
+ endpoint = {"registry" => {"endpoint" => "blah"}}
+ Bosh::Agent::Infrastructure::Openstack::Registry.stub(:get_json_from_url).and_return(endpoint)
+ Bosh::Agent::Infrastructure::Openstack::Registry.get_registry_endpoint.should == "blah"
+ end
+
+ it 'should get current_server_id' do
+ class TestHTTPResponse
+ def status
+ 200
+ end
+ def body
+ "{\"registry\": {\"endpoint\": \"blah\"}, \"server\": {\"name\": \"server-id\"}}"
+ end
+ end
+
+ client = HTTPClient.new
+ client.stub(:get).and_return(TestHTTPResponse.new)
+ HTTPClient.stub(:new).and_return(client)
+ server_id = Bosh::Agent::Infrastructure::Openstack::Registry.current_server_id
+ server_id.should == "server-id"
+ end
+
+ def settings_json
+ %q[{"vm":{"name":"vm-273a202e-eedf-4475-a4a1-66c6d2628742","id":"vm-51290"},"disks":{"ephemeral":1,"persistent":{"250":2},"system":0},"mbus":"nats://user:pass@11.0.0.11:4222","networks":{"network_a":{"netmask":"255.255.248.0","mac":"00:50:56:89:17:70","ip":"172.30.40.115","default":["gateway","dns"],"gateway":"172.30.40.1","dns":["172.30.22.153","172.30.22.154"],"cloud_properties":{"name":"VLAN440"}}},"blobstore":{"plugin":"simple","properties":{"password":"Ag3Nt","user":"agent","endpoint":"http://172.30.40.11:25250"}},"ntp":["ntp01.las01.emcatmos.com","ntp02.las01.emcatmos.com"],"agent_id":"a26efbe5-4845-44a0-9323-b8e36191a2c8"}]
+ end
+
+end
@@ -0,0 +1,63 @@
+# Copyright (c) 2009-2012 VMware, Inc.
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+Bosh::Agent::Infrastructure.new("openstack").infrastructure
+
+describe Bosh::Agent::Infrastructure::Openstack::Settings do
+ before(:all) do
+ @test_authorized_dir = Dir.mktmpdir
+ @test_authorized_keys = File.join(@test_authorized_dir, "test_auth")
+ end
+
+ before(:each) do
+ Bosh::Agent::Config.settings_file = File.join(base_dir, 'settings.json')
+ @settings = {"vm" => "test_vm", "disks" => "test_disks"}
+ end
+
+ it 'should load settings' do
+ Bosh::Agent::Infrastructure::Openstack::Registry.stub(:get_settings).and_return(@settings)
+ Bosh::Agent::Infrastructure::Openstack::Registry.stub(:get_openssh_key).and_return("test_key")
+ settings_wrapper = Bosh::Agent::Infrastructure::Openstack::Settings.new
+ settings_wrapper.stub(:authorized_keys).and_return(@test_authorized_keys)
+ FileUtils.stub(:chown).and_return(true)
+ settings = settings_wrapper.load_settings
+ settings.should == @settings
+ end
+
+ it 'should get network settings for dhcp network' do
+ settings_wrapper = Bosh::Agent::Infrastructure::Openstack::Settings.new
+ network_properties = {"type" => "dynamic"}
+ properties = settings_wrapper.get_network_settings("test", network_properties)
+
+ properties.should have_key("ip")
+ properties.should have_key("netmask")
+ properties.should have_key("dns")
+ properties.should have_key("gateway")
+ end
+
+ it 'should get nothing for vip network' do
+ settings_wrapper = Bosh::Agent::Infrastructure::Openstack::Settings.new
+ network_properties = {"type" => "vip"}
+ properties = settings_wrapper.get_network_settings("test", network_properties)
+ properties.should be_nil
+ end
+
+ it 'should raise unsupported network exception for manual network' do
+ settings_wrapper = Bosh::Agent::Infrastructure::Openstack::Settings.new
+ network_properties = {}
+ lambda {
+ properties = settings_wrapper.get_network_settings("test", network_properties)
+ }.should raise_error(Bosh::Agent::StateError, /Unsupported network/)
+ end
+
+ it 'should setup the ssh public key' do
+ Bosh::Agent::Infrastructure::Openstack::Registry.stub!(:get_openssh_key).and_return("test_key")
+ settings_wrapper = Bosh::Agent::Infrastructure::Openstack::Settings.new
+ settings_wrapper.stub(:authorized_keys).and_return(@test_authorized_keys)
+ FileUtils.stub(:chown).and_return(true)
+ settings_wrapper.setup_openssh_key
+ File.open(@test_authorized_keys, "r") { |f| f.read.should == "test_key" }
+ end
+
+end
Oops, something went wrong.

0 comments on commit 99a7674

Please sign in to comment.