bosh_agent 1.5.0.pre.1113

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. data/CHANGELOG +0 -0
  2. data/bin/bosh_agent +102 -0
  3. data/lib/bosh_agent/alert.rb +191 -0
  4. data/lib/bosh_agent/alert_processor.rb +96 -0
  5. data/lib/bosh_agent/apply_plan/helpers.rb +30 -0
  6. data/lib/bosh_agent/apply_plan/job.rb +235 -0
  7. data/lib/bosh_agent/apply_plan/package.rb +58 -0
  8. data/lib/bosh_agent/apply_plan/plan.rb +96 -0
  9. data/lib/bosh_agent/bootstrap.rb +341 -0
  10. data/lib/bosh_agent/config.rb +5 -0
  11. data/lib/bosh_agent/configuration.rb +102 -0
  12. data/lib/bosh_agent/disk_util.rb +103 -0
  13. data/lib/bosh_agent/errors.rb +25 -0
  14. data/lib/bosh_agent/ext.rb +48 -0
  15. data/lib/bosh_agent/file_aggregator.rb +78 -0
  16. data/lib/bosh_agent/file_matcher.rb +45 -0
  17. data/lib/bosh_agent/handler.rb +440 -0
  18. data/lib/bosh_agent/heartbeat.rb +74 -0
  19. data/lib/bosh_agent/heartbeat_processor.rb +45 -0
  20. data/lib/bosh_agent/http_handler.rb +135 -0
  21. data/lib/bosh_agent/infrastructure/aws/registry.rb +177 -0
  22. data/lib/bosh_agent/infrastructure/aws/settings.rb +59 -0
  23. data/lib/bosh_agent/infrastructure/aws.rb +17 -0
  24. data/lib/bosh_agent/infrastructure/dummy.rb +24 -0
  25. data/lib/bosh_agent/infrastructure/openstack/registry.rb +220 -0
  26. data/lib/bosh_agent/infrastructure/openstack/settings.rb +76 -0
  27. data/lib/bosh_agent/infrastructure/openstack.rb +17 -0
  28. data/lib/bosh_agent/infrastructure/vsphere/settings.rb +135 -0
  29. data/lib/bosh_agent/infrastructure/vsphere.rb +16 -0
  30. data/lib/bosh_agent/infrastructure.rb +25 -0
  31. data/lib/bosh_agent/message/apply.rb +184 -0
  32. data/lib/bosh_agent/message/base.rb +38 -0
  33. data/lib/bosh_agent/message/compile_package.rb +250 -0
  34. data/lib/bosh_agent/message/drain.rb +195 -0
  35. data/lib/bosh_agent/message/list_disk.rb +25 -0
  36. data/lib/bosh_agent/message/logs.rb +108 -0
  37. data/lib/bosh_agent/message/migrate_disk.rb +55 -0
  38. data/lib/bosh_agent/message/mount_disk.rb +102 -0
  39. data/lib/bosh_agent/message/ssh.rb +109 -0
  40. data/lib/bosh_agent/message/state.rb +47 -0
  41. data/lib/bosh_agent/message/unmount_disk.rb +29 -0
  42. data/lib/bosh_agent/monit.rb +354 -0
  43. data/lib/bosh_agent/monit_client.rb +158 -0
  44. data/lib/bosh_agent/mounter.rb +42 -0
  45. data/lib/bosh_agent/ntp.rb +32 -0
  46. data/lib/bosh_agent/platform/centos/disk.rb +27 -0
  47. data/lib/bosh_agent/platform/centos/network.rb +39 -0
  48. data/lib/bosh_agent/platform/centos/templates/centos-ifcfg.erb +9 -0
  49. data/lib/bosh_agent/platform/centos/templates/dhclient_conf.erb +56 -0
  50. data/lib/bosh_agent/platform/centos/templates/logrotate.erb +8 -0
  51. data/lib/bosh_agent/platform/centos.rb +4 -0
  52. data/lib/bosh_agent/platform/dummy/templates/dummy_template.erb +1 -0
  53. data/lib/bosh_agent/platform/linux/adapter.rb +36 -0
  54. data/lib/bosh_agent/platform/linux/disk.rb +121 -0
  55. data/lib/bosh_agent/platform/linux/logrotate.rb +32 -0
  56. data/lib/bosh_agent/platform/linux/network.rb +124 -0
  57. data/lib/bosh_agent/platform/linux/password.rb +22 -0
  58. data/lib/bosh_agent/platform/linux.rb +4 -0
  59. data/lib/bosh_agent/platform/ubuntu/network.rb +59 -0
  60. data/lib/bosh_agent/platform/ubuntu/templates/dhclient_conf.erb +56 -0
  61. data/lib/bosh_agent/platform/ubuntu/templates/interfaces.erb +14 -0
  62. data/lib/bosh_agent/platform/ubuntu/templates/logrotate.erb +8 -0
  63. data/lib/bosh_agent/platform/ubuntu.rb +4 -0
  64. data/lib/bosh_agent/platform.rb +26 -0
  65. data/lib/bosh_agent/remote_exception.rb +62 -0
  66. data/lib/bosh_agent/runner.rb +36 -0
  67. data/lib/bosh_agent/settings.rb +61 -0
  68. data/lib/bosh_agent/sigar_box.rb +26 -0
  69. data/lib/bosh_agent/smtp_server.rb +96 -0
  70. data/lib/bosh_agent/state.rb +100 -0
  71. data/lib/bosh_agent/syslog_monitor.rb +53 -0
  72. data/lib/bosh_agent/template.rb +50 -0
  73. data/lib/bosh_agent/util.rb +190 -0
  74. data/lib/bosh_agent/version.rb +8 -0
  75. data/lib/bosh_agent.rb +92 -0
  76. metadata +332 -0
@@ -0,0 +1,45 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Agent
4
+ class HeartbeatProcessor
5
+
6
+ MAX_OUTSTANDING_HEARTBEATS = 2
7
+
8
+ def enable(interval)
9
+ unless EM.reactor_running?
10
+ raise Bosh::Agent::HeartbeatError, "Event loop must be running in order to enable heartbeats"
11
+ end
12
+
13
+ if @timer
14
+ Config.logger.warn("Heartbeat timer already running, canceling")
15
+ disable
16
+ end
17
+
18
+ @pending = 0
19
+
20
+ @timer = EM.add_periodic_timer(interval) do
21
+ beat
22
+ end
23
+ end
24
+
25
+ def disable
26
+ Config.logger.info("Disabled heartbeats")
27
+ @timer.cancel if @timer
28
+ @timer = nil
29
+ end
30
+
31
+ def beat
32
+ raise HeartbeatError, "#{@pending} outstanding heartbeat(s)" if @pending > MAX_OUTSTANDING_HEARTBEATS
33
+
34
+ Heartbeat.new.send_via_mbus do
35
+ @pending -= 1
36
+ end
37
+ @pending += 1
38
+ rescue => e
39
+ Config.logger.warn("Error sending heartbeat: #{e}")
40
+ Config.logger.warn(e.backtrace.join("\n"))
41
+ raise e if @pending > MAX_OUTSTANDING_HEARTBEATS
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,135 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ require "thin"
4
+ require "sinatra"
5
+ require "monitor"
6
+ require "common/ssl"
7
+
8
+ module Bosh::Agent
9
+
10
+ class HTTPHandler < Handler
11
+
12
+ def self.start
13
+ new.start
14
+ end
15
+
16
+ def start
17
+ handler = self
18
+
19
+ uri = URI.parse(Config.mbus)
20
+
21
+ @server = Thin::Server.new(uri.host, uri.port) do
22
+ use Rack::CommonLogger
23
+
24
+ if uri.userinfo
25
+ use Rack::Auth::Basic do |user, password|
26
+ "#{user}:#{password}" == uri.userinfo
27
+ end
28
+ end
29
+
30
+ map "/" do
31
+ run AgentController.new(handler)
32
+ end
33
+ end
34
+
35
+ certificate = Bosh::Ssl::Certificate.new('agent.key',
36
+ 'agent.cert',
37
+ uri.host
38
+ ).load_or_create
39
+
40
+ @server.ssl = true
41
+ @server.ssl_options = {:ssl_verify => false, :ssl_key_file => certificate.key_path, :ssl_cert_file => certificate.certificate_path }
42
+
43
+ @server.start!
44
+ end
45
+
46
+ def shutdown
47
+ @logger.info("Exit")
48
+ @server.stop
49
+ end
50
+
51
+ def handle_message(json)
52
+ result = {}
53
+ result.extend(MonitorMixin)
54
+
55
+ cond = result.new_cond
56
+ timeout_time = Time.now.to_f + 30
57
+
58
+ @callback = Proc.new do |response|
59
+ result.synchronize do
60
+ result.merge!(response)
61
+ cond.signal
62
+ end
63
+ end
64
+
65
+ super(json)
66
+
67
+ result.synchronize do
68
+ while result.empty?
69
+ timeout = timeout_time - Time.now.to_f
70
+ unless timeout > 0
71
+ raise "Timed out"
72
+ end
73
+ cond.wait(timeout)
74
+ end
75
+ end
76
+
77
+ result
78
+ end
79
+
80
+ def publish(reply_to, payload, &blk)
81
+ response = @callback.call(payload)
82
+ blk.call if blk
83
+ response
84
+ end
85
+ end
86
+
87
+ module Message
88
+ class ReleaseApplySpec < Base
89
+
90
+ def self.process(args)
91
+ self.new.apply_spec
92
+ end
93
+
94
+ def release_apply_spec
95
+ #generated from bosh-release and baked into the stemcell
96
+ "/var/vcap/micro/apply_spec.yml"
97
+ end
98
+
99
+ def apply_spec
100
+ Psych.load_file(release_apply_spec)
101
+ end
102
+ end
103
+ end
104
+
105
+ class AgentController < Sinatra::Base
106
+
107
+ def initialize(handler)
108
+ super()
109
+ @handler = handler
110
+ end
111
+
112
+ configure do
113
+ set(:show_exceptions, false)
114
+ set(:raise_errors, false)
115
+ set(:dump_errors, false)
116
+ end
117
+
118
+ post "/agent" do
119
+ body = request.env["rack.input"].read
120
+ response = handle_message(body)
121
+ content_type(:json)
122
+ response
123
+ end
124
+
125
+ def handle_message(json)
126
+ begin
127
+ payload = @handler.handle_message(json)
128
+ rescue => e
129
+ payload = RemoteException.from(e).to_hash
130
+ end
131
+
132
+ Yajl::Encoder.encode(payload, :terminator => "\n")
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,177 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Agent
4
+ class Infrastructure::Aws::Registry
5
+ class << self
6
+
7
+ API_TIMEOUT = 86400 * 3
8
+ CONNECT_TIMEOUT = 30
9
+ INSTANCE_DATA_URI = "http://169.254.169.254/latest"
10
+
11
+ def get_uri(uri)
12
+ client = HTTPClient.new
13
+ client.send_timeout = API_TIMEOUT
14
+ client.receive_timeout = API_TIMEOUT
15
+ client.connect_timeout = CONNECT_TIMEOUT
16
+
17
+ response = client.get(INSTANCE_DATA_URI + uri)
18
+ unless response.status == 200
19
+ raise(LoadSettingsError, "Instance metadata endpoint returned " \
20
+ "HTTP #{response.status}")
21
+ end
22
+
23
+ response.body
24
+ rescue HTTPClient::BadResponseError => e
25
+ raise(LoadSettingsError,
26
+ "Received bad HTTP response for #{uri}: #{e.inspect}")
27
+ rescue HTTPClient::TimeoutError
28
+ raise(LoadSettingsError,
29
+ "Timed out reading uri #{uri}, " \
30
+ "please make sure agent is running on EC2 instance")
31
+ rescue URI::Error, SocketError, Errno::ECONNREFUSED, SystemCallError => e
32
+ raise(LoadSettingsError,
33
+ "Error requesting current instance id from #{uri} #{e.inspect}")
34
+ end
35
+
36
+ ##
37
+ # Reads current instance id from EC2 metadata. We are assuming
38
+ # instance id cannot change while current process is running
39
+ # and thus memoizing it.
40
+ def current_instance_id
41
+ return @current_instance_id if @current_instance_id
42
+ @current_instance_id = get_uri("/meta-data/instance-id/")
43
+ end
44
+
45
+ def get_json_from_url(url)
46
+ client = HTTPClient.new
47
+ client.send_timeout = API_TIMEOUT
48
+ client.receive_timeout = API_TIMEOUT
49
+ client.connect_timeout = CONNECT_TIMEOUT
50
+
51
+ headers = {"Accept" => "application/json"}
52
+ response = client.get(url, {}, headers)
53
+
54
+ if response.status != 200
55
+ raise(LoadSettingsError,
56
+ "Cannot read settings for `#{url}' from registry, " \
57
+ "got HTTP #{response.status}")
58
+ end
59
+
60
+ body = Yajl::Parser.parse(response.body)
61
+ unless body.is_a?(Hash)
62
+ raise(LoadSettingsError,
63
+ "Invalid response from #{url} , Hash expected, " \
64
+ "got #{body.class}: #{body}")
65
+ end
66
+
67
+ body
68
+
69
+ rescue HTTPClient::BadResponseError => e
70
+ raise(LoadSettingsError,
71
+ "Received bad HTTP response from registry: #{e.inspect}")
72
+ rescue HTTPClient::TimeoutError
73
+ raise(LoadSettingsError,
74
+ "Timed out reading json from #{url}, " \
75
+ "please make sure agent is running on EC2 instance")
76
+ rescue URI::Error, SocketError, Errno::ECONNREFUSED, SystemCallError => e
77
+ raise(LoadSettingsError,
78
+ "Error requesting registry information #{e.inspect}")
79
+ rescue Yajl::ParseError => e
80
+ raise(LoadSettingsError,
81
+ "Cannot parse settings for from registry #{e.inspect}")
82
+ end
83
+
84
+ def get_registry_endpoint
85
+ user_data = get_json_from_url(INSTANCE_DATA_URI + "/user-data")
86
+ unless user_data.has_key?("registry") &&
87
+ user_data["registry"].has_key?("endpoint")
88
+ raise(LoadSettingsError,
89
+ "Cannot parse user data for endpoint #{user_data.inspect}")
90
+ end
91
+ Bosh::Agent::Config.logger.info("got user_data: #{user_data}")
92
+ lookup_registry(user_data)
93
+ end
94
+
95
+ # If the registry endpoint is specified with a bosh dns name, e.g.
96
+ # 0.registry.default.aws.bosh, then the agent needs to lookup the
97
+ # name and insert the IP address, as the agent doesn't update
98
+ # resolv.conf until after the bootstrap is run.
99
+ def lookup_registry(user_data)
100
+ endpoint = user_data["registry"]["endpoint"]
101
+
102
+ # if we get data from an old director which doesn't set dns
103
+ # info, there is noting we can do, so just return the endpoint
104
+ if user_data["dns"].nil? || user_data["dns"]["nameserver"].nil?
105
+ return endpoint
106
+ end
107
+
108
+ hostname = extract_registry_hostname(endpoint)
109
+
110
+ # if the registry endpoint is an IP address, just return the endpoint
111
+ unless (IPAddr.new(hostname) rescue(nil)).nil?
112
+ return endpoint
113
+ end
114
+
115
+ nameservers = user_data["dns"]["nameserver"]
116
+ ip = bosh_lookup(hostname, nameservers)
117
+ inject_registry_ip(ip, endpoint)
118
+ rescue Resolv::ResolvError => e
119
+ raise(LoadSettingsError,
120
+ "Cannot lookup #{hostname} using #{nameservers.join(', ')}" +
121
+ "\n#{e.inspect}")
122
+ end
123
+
124
+ def bosh_lookup(hostname, nameservers)
125
+ resolver = Resolv.new([Resolv::Hosts.new, Resolv::DNS.new(nameserver: nameservers)])
126
+ resolver.each_address(hostname) do |address|
127
+ begin
128
+ return address if IPAddr.new(address).ipv4?
129
+ rescue ArgumentError
130
+ end
131
+ end
132
+ raise Resolv::ResolvError, "Could not resolve #{hostname}"
133
+ end
134
+
135
+ def extract_registry_hostname(endpoint)
136
+ uri = URI.parse(endpoint)
137
+ hostname = uri.hostname
138
+
139
+ if hostname.nil?
140
+ raise LoadSettingsError, "Could not extract registry hostname"
141
+ end
142
+
143
+ hostname
144
+ end
145
+
146
+ def inject_registry_ip(ip, endpoint)
147
+ uri = URI.parse(endpoint)
148
+ uri.hostname = ip
149
+ uri.to_s
150
+ end
151
+
152
+ def get_openssh_key
153
+ get_uri("/meta-data/public-keys/0/openssh-key")
154
+ end
155
+
156
+ def get_settings
157
+ @registry_endpoint ||= get_registry_endpoint
158
+ url = "#{@registry_endpoint}/instances/#{current_instance_id}/settings"
159
+ body = get_json_from_url(url)
160
+
161
+ settings = Yajl::Parser.parse(body["settings"])
162
+ unless settings.is_a?(Hash)
163
+ raise(LoadSettingsError, "Invalid settings format, " \
164
+ "Hash expected, got #{settings.class}: " \
165
+ "#{settings}")
166
+ end
167
+
168
+ settings
169
+
170
+ rescue Yajl::ParseError
171
+ raise(LoadSettingsError,
172
+ "Cannot parse settings from registry #{@registry_endpoint}")
173
+ end
174
+
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,59 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Agent
4
+ class Infrastructure::Aws::Settings
5
+
6
+ VIP_NETWORK_TYPE = "vip"
7
+ DHCP_NETWORK_TYPE = "dynamic"
8
+ MANUAL_NETWORK_TYPE = "manual"
9
+
10
+ SUPPORTED_NETWORK_TYPES = [
11
+ VIP_NETWORK_TYPE, DHCP_NETWORK_TYPE, MANUAL_NETWORK_TYPE
12
+ ]
13
+
14
+ AUTHORIZED_KEYS = File.join("/home/", BOSH_APP_USER, ".ssh/authorized_keys")
15
+
16
+ def logger
17
+ Bosh::Agent::Config.logger
18
+ end
19
+
20
+ def authorized_keys
21
+ AUTHORIZED_KEYS
22
+ end
23
+
24
+ def setup_openssh_key
25
+ public_key = Infrastructure::Aws::Registry.get_openssh_key
26
+ if public_key.nil? || public_key.empty?
27
+ return
28
+ end
29
+ FileUtils.mkdir_p(File.dirname(authorized_keys))
30
+ FileUtils.chmod(0700, File.dirname(authorized_keys))
31
+ FileUtils.chown(Bosh::Agent::BOSH_APP_USER, Bosh::Agent::BOSH_APP_GROUP,
32
+ File.dirname(authorized_keys))
33
+ File.open(authorized_keys, "w") { |f| f.write(public_key) }
34
+ FileUtils.chown(Bosh::Agent::BOSH_APP_USER, Bosh::Agent::BOSH_APP_GROUP,
35
+ authorized_keys)
36
+ FileUtils.chmod(0644, authorized_keys)
37
+ end
38
+
39
+ def load_settings
40
+ setup_openssh_key
41
+ Infrastructure::Aws::Registry.get_settings
42
+ end
43
+
44
+ def get_network_settings(network_name, properties)
45
+ type = properties["type"] || "manual"
46
+ unless type && SUPPORTED_NETWORK_TYPES.include?(type)
47
+ raise Bosh::Agent::StateError,
48
+ "Unsupported network type '%s', valid types are: %s" %
49
+ [type, SUPPORTED_NETWORK_TYPES.join(', ')]
50
+ end
51
+
52
+ # Nothing to do for "vip" and "manual" networks
53
+ return nil if [VIP_NETWORK_TYPE, MANUAL_NETWORK_TYPE].include? type
54
+
55
+ Bosh::Agent::Util.get_network_info
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,17 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Agent
4
+ class Infrastructure::Aws
5
+ require 'bosh_agent/infrastructure/aws/settings'
6
+ require 'bosh_agent/infrastructure/aws/registry'
7
+
8
+ def load_settings
9
+ Settings.new.load_settings
10
+ end
11
+
12
+ def get_network_settings(network_name, properties)
13
+ Settings.new.get_network_settings(network_name, properties)
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Agent
4
+ class Infrastructure::Dummy
5
+
6
+ def load_settings
7
+ {
8
+ "blobstore" => {
9
+ "provider" => Bosh::Agent::Config.blobstore_provider,
10
+ "options" => Bosh::Agent::Config.blobstore_options,
11
+ },
12
+ "ntp" => [],
13
+ "disks" => {
14
+ "persistent" => {},
15
+ },
16
+ "mbus" => Bosh::Agent::Config.mbus,
17
+ }
18
+ end
19
+
20
+ def get_network_settings(network_name, properties)
21
+ # Nothing to do
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,220 @@
1
+ # Copyright (c) 2009-2013 VMware, Inc.
2
+
3
+ module Bosh::Agent
4
+ class Infrastructure::Openstack::Registry
5
+ class << self
6
+
7
+ attr_accessor :user_data
8
+
9
+ HTTP_API_TIMEOUT = 300
10
+ HTTP_CONNECT_TIMEOUT = 30
11
+ META_DATA_URI = "http://169.254.169.254/latest"
12
+ USER_DATA_FILE = File.join(File::SEPARATOR, "var", BOSH_APP_USER, "bosh", "user_data.json")
13
+
14
+ ##
15
+ # Returns the logger.
16
+ #
17
+ # @return [Logger] Bosh Agent logger
18
+ def logger
19
+ Bosh::Agent::Config.logger
20
+ end
21
+
22
+ ##
23
+ # Gets the OpenSSH public key. First we try to get it from the OpenStack meta data endpoint, if we fail,
24
+ # then we fallback to the injected user data file.
25
+ #
26
+ # @return [String] OpenSSH key
27
+ def get_openssh_key
28
+ get_uri(META_DATA_URI + "/meta-data/public-keys/0/openssh-key")
29
+ rescue LoadSettingsError => e
30
+ logger.info("Failed to get OpenSSH public key from OpenStack meta data endpoint: #{e.message}")
31
+ user_data = parse_user_data(get_user_data_from_file)
32
+ unless user_data.has_key?("openssh") && user_data["openssh"].has_key?("public_key")
33
+ raise LoadSettingsError, "Cannot get OpenSSH public key from injected user data file: #{user_data.inspect}"
34
+ end
35
+ user_data["openssh"]["public_key"]
36
+ end
37
+
38
+ ##
39
+ # Gets the settings for this agent from the Bosh registry.
40
+ #
41
+ # @return [Hash] Agent Settings
42
+ def get_settings
43
+ @registry_endpoint ||= get_registry_endpoint
44
+ url = "#{@registry_endpoint}/instances/#{get_server_name}/settings"
45
+ raw_response = get_uri(url)
46
+
47
+ registry_data = Yajl::Parser.parse(raw_response)
48
+ unless registry_data.is_a?(Hash) && registry_data.has_key?("settings")
49
+ raise LoadSettingsError, "Invalid response received from Bosh registry, " \
50
+ "got #{registry_data.class}: #{registry_data}"
51
+ end
52
+
53
+ settings = Yajl::Parser.parse(registry_data["settings"])
54
+ unless settings.is_a?(Hash)
55
+ raise(LoadSettingsError, "Invalid settings received from Bosh registry, " \
56
+ "got #{settings.class}: #{settings}")
57
+ end
58
+
59
+ settings
60
+ rescue Yajl::ParseError => e
61
+ raise LoadSettingsError, "Cannot parse settings from Bosh registry, got #{raw_response} - #{e.message}"
62
+ end
63
+
64
+ ##
65
+ # Gets the server name from OpenStack user data.
66
+ #
67
+ # @return [String] OpenStack server name
68
+ def get_server_name
69
+ user_data = get_user_data
70
+ unless user_data.has_key?("server") && user_data["server"].has_key?("name")
71
+ raise LoadSettingsError, "Cannot get OpenStack server name from user data #{user_data.inspect}"
72
+ end
73
+ user_data["server"]["name"]
74
+ end
75
+
76
+ ##
77
+ # Gets the Bosh registry endpoint from OpenStack user data.
78
+ #
79
+ # @return [String] Bosh registry endpoint
80
+ def get_registry_endpoint
81
+ user_data = get_user_data
82
+ unless user_data.has_key?("registry") && user_data["registry"].has_key?("endpoint")
83
+ raise LoadSettingsError, "Cannot get Bosh registry endpoint from user data #{user_data.inspect}"
84
+ end
85
+ lookup_registry_endpoint(user_data)
86
+ end
87
+
88
+ ##
89
+ # If the Bosh registry endpoint is specified with a Bosh DNS name, i.e. 0.registry.default.openstack.bosh,
90
+ # then the agent needs to lookup the name and insert the IP address, as the agent doesn't update
91
+ # resolv.conf until after the bootstrap is run.
92
+ #
93
+ # @param [Hash] user_data OpenStack user data (generated by the CPI)
94
+ # @return [String] Bosh registry endpoint
95
+ def lookup_registry_endpoint(user_data)
96
+ registry_endpoint = user_data["registry"]["endpoint"]
97
+
98
+ # If user data doesn't contain dns info, there is noting we can do, so just return the endpoint
99
+ return registry_endpoint if user_data["dns"].nil? || user_data["dns"]["nameserver"].nil?
100
+
101
+ # If the endpoint is an IP address, just return the endpoint
102
+ registry_hostname = extract_registry_hostname(registry_endpoint)
103
+ return registry_endpoint unless (IPAddr.new(registry_hostname) rescue(nil)).nil?
104
+
105
+ nameservers = user_data["dns"]["nameserver"]
106
+ registry_ip = lookup_registry_ip_address(registry_hostname, nameservers)
107
+ inject_registry_ip_address(registry_ip, registry_endpoint)
108
+ rescue Resolv::ResolvError => e
109
+ raise LoadSettingsError, "Cannot lookup #{registry_hostname} using #{nameservers.join(", ")}: #{e.inspect}"
110
+ end
111
+
112
+ ##
113
+ # Extracts the hostname from the Bosh registry endpoint.
114
+ #
115
+ # @param [String] endpoint Bosh registry endpoint
116
+ # @return [String] Bosh registry hostname
117
+ def extract_registry_hostname(endpoint)
118
+ match = endpoint.match(%r{https*://([^:]+):})
119
+ unless match && match.size == 2
120
+ raise LoadSettingsError, "Cannot extract Bosh registry hostname from #{endpoint}"
121
+ end
122
+ match[1]
123
+ end
124
+
125
+ ##
126
+ # Lookups for the Bosh registry IP address.
127
+ #
128
+ # @param [String] hostname Bosh registry hostname
129
+ # @param [Array] nameservers Array containing nameserver address
130
+ # @return [Resolv::IPv4] Bosh registry IP address
131
+ def lookup_registry_ip_address(hostname, nameservers)
132
+ resolver = Resolv::DNS.new(:nameserver => nameservers)
133
+ resolver.getaddress(hostname)
134
+ end
135
+
136
+ ##
137
+ # Injects an IP address in the Bosh registry endpoint.
138
+ #
139
+ # @param [Resolv::IPv4] ip Bosh registry IP address
140
+ # @param [String] endpoint Bosh registry endpoint
141
+ # @return [String] Bosh registry endpoint
142
+ def inject_registry_ip_address(ip, endpoint)
143
+ endpoint.sub(%r{//[^:]+:}, "//#{ip}:")
144
+ end
145
+
146
+ ##
147
+ # Gets the OpenStack user data. First we try to get it from the OpenStack user data endpoint, if we fail,
148
+ # then we fallback to the injected user data file.
149
+ #
150
+ # @return [Hash] OpenStack user data
151
+ def get_user_data
152
+ return @user_data if @user_data
153
+ begin
154
+ raw_user_data = get_uri(META_DATA_URI + "/user-data")
155
+ rescue LoadSettingsError => e
156
+ logger.info("Failed to get user data from OpenStack user data endpoint: #{e.message}")
157
+ raw_user_data = get_user_data_from_file
158
+ end
159
+
160
+ logger.info("OpenStack user data: #{raw_user_data.inspect}")
161
+ @user_data = parse_user_data(raw_user_data)
162
+ end
163
+
164
+ ##
165
+ # Gets the OpenStack user data from the injected user data file.
166
+ #
167
+ # @return [String] OpenStack user data
168
+ def get_user_data_from_file
169
+ File.read(USER_DATA_FILE)
170
+ rescue SystemCallError => e
171
+ raise LoadSettingsError, "Failed to get user data from OpenStack injected user data file: #{e.message}"
172
+ end
173
+
174
+ ##
175
+ # Parses the OpenStack user data.
176
+ #
177
+ # @param [String] raw_user_data Raw OpenStack user data
178
+ # @return [Hash] OpenStack user data
179
+ def parse_user_data(raw_user_data)
180
+ begin
181
+ user_data = Yajl::Parser.parse(raw_user_data)
182
+ rescue Yajl::ParseError => e
183
+ raise LoadSettingsError, "Cannot parse user data #{raw_user_data.inspect}: #{e.message}"
184
+ end
185
+
186
+ unless user_data.is_a?(Hash)
187
+ raise LoadSettingsError, "Invalid user data format, Hash expected, got #{user_data.class}: #{user_data}"
188
+ end
189
+
190
+ user_data
191
+ end
192
+
193
+ ##
194
+ # Sends GET request to an specified URI.
195
+ #
196
+ # @param [String] uri URI to request
197
+ # @return [String] Response body
198
+ def get_uri(uri)
199
+ client = HTTPClient.new
200
+ client.send_timeout = HTTP_API_TIMEOUT
201
+ client.receive_timeout = HTTP_API_TIMEOUT
202
+ client.connect_timeout = HTTP_CONNECT_TIMEOUT
203
+
204
+ headers = {"Accept" => "application/json"}
205
+ response = client.get(uri, {}, headers)
206
+ unless response.status == 200
207
+ raise LoadSettingsError, "Endpoint #{uri} returned HTTP #{response.status}"
208
+ end
209
+
210
+ response.body
211
+ rescue HTTPClient::TimeoutError
212
+ raise LoadSettingsError, "Timed out reading endpoint #{uri}"
213
+ rescue HTTPClient::BadResponseError => e
214
+ raise LoadSettingsError, "Received bad HTTP response from endpoint #{uri}: #{e.inspect}"
215
+ rescue URI::Error, SocketError, Errno::ECONNREFUSED, SystemCallError => e
216
+ raise LoadSettingsError, "Error requesting endpoint #{uri}: #{e.inspect}"
217
+ end
218
+ end
219
+ end
220
+ end