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
data/CHANGELOG ADDED
File without changes
data/bin/bosh_agent ADDED
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2009-2012 VMware, Inc.
4
+
5
+ require "bosh_agent"
6
+ require "optparse"
7
+
8
+ # Defaults
9
+ options = {
10
+ "configure" => false,
11
+ "logging" => {"level" => "DEBUG"},
12
+ "mbus" => "nats://localhost:4222",
13
+ "agent_id" => "not_configured",
14
+ "blobstore_options" => {},
15
+ "blobstore_provider" => "simple",
16
+ "infrastructure_name" => "vsphere",
17
+ "platform_name" => "ubuntu",
18
+ "base_dir" => "/var/vcap",
19
+ "smtp_port" => 2825,
20
+ "process_alerts" => true,
21
+ "heartbeat_interval" => 60
22
+ }
23
+
24
+ opts = OptionParser.new do |opts|
25
+ opts.on("-c", "--configure", "Invoke") do |opt|
26
+ options["configure"] = true
27
+ end
28
+
29
+ opts.on("-a", "--agent_id String", "Agent ID") do |opt|
30
+ options["agent_id"] = opt
31
+ end
32
+
33
+ opts.on("-b", "--base_dir String", "Base directory") do |opt|
34
+ options["base_dir"] = opt
35
+ end
36
+
37
+ opts.on("-l", "--log_level String", "Log level") do |opt|
38
+ options["logging"] = {"level" => opt}
39
+ end
40
+
41
+ opts.on("-n", "--nats String", "Nats/mbus (nats://user:pass@host:port)") do |opt|
42
+ options["mbus"] = opt
43
+ end
44
+
45
+ opts.on("-p", "--provider String", "Blobstore provider") do |opt|
46
+ options["blobstore_provider"] = opt
47
+ end
48
+
49
+ opts.on("-I", "--infrastructure String", "Infrastructure") do |opt|
50
+ options["infrastructure_name"] = opt
51
+ end
52
+
53
+ opts.on("-P", "--platform String", "Platform") do |opt|
54
+ options["platform_name"] = opt
55
+ end
56
+
57
+ opts.on("-r", "--root_dir String", "Root directory") do |opt|
58
+ options["root_dir"] = opt
59
+ end
60
+
61
+ opts.on("-t", "--smtp-port Integer", "Built-in SMTP server port") do |opt|
62
+ options["smtp_port"] = opt
63
+ end
64
+
65
+ opts.on("--no-alerts", "Don't process alerts from Monit or sshd") do |opt|
66
+ options["process_alerts"] = false
67
+ end
68
+
69
+ opts.on("-h", "--heartbeat-interval Integer", "Heartbeat interval") do |opt|
70
+ options["heartbeat_interval"] = opt
71
+ end
72
+
73
+ opts.on("-s", "--blobstore String", "Blobstore options") do |opt|
74
+ uri = URI(opt)
75
+ bs_opts = options['blobstore_options']
76
+
77
+ if uri.user
78
+ bs_opts.merge!('user' => uri.user)
79
+ uri.user = nil
80
+ end
81
+
82
+ if uri.password
83
+ bs_opts.merge!('password' => uri.password)
84
+ uri.password = nil
85
+ end
86
+
87
+ # handle both 'file://foo/bar' and '/foo/bar'
88
+ if uri.scheme == 'file' || uri.scheme.nil?
89
+ bs_opts['blobstore_path'] = uri.path
90
+ else
91
+ bs_opts['endpoint'] = uri.to_s
92
+ end
93
+ end
94
+
95
+ opts.on("--help", "Help") do
96
+ puts opts
97
+ exit 0
98
+ end
99
+ end
100
+ opts.parse!(ARGV.dup)
101
+
102
+ Bosh::Agent::Runner.run(options)
@@ -0,0 +1,191 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Agent
4
+ class Alert
5
+
6
+ ALERT_RETRIES = 3
7
+ RETRY_PERIOD = 1 # second
8
+ SEVERITY_CUTOFF = 5
9
+ DEFAULT_SEVERITY = 2
10
+
11
+ # The main area of responsibility for this class is conversion
12
+ # of Monit alert format to BOSH Health Monitor alert format.
13
+
14
+ attr_reader :id, :service, :event, :description, :action, :date, :severity
15
+
16
+ def self.register(attrs)
17
+ new(attrs).register
18
+ end
19
+
20
+ def initialize(attrs)
21
+ unless attrs.is_a?(Hash)
22
+ raise ArgumentError, "#{self.class} expects an attributes Hash as a parameter"
23
+ end
24
+
25
+ @logger = Config.logger
26
+ @nats = Config.nats
27
+ @agent_id = Config.agent_id
28
+ @state = Config.state
29
+
30
+ @id = attrs[:id]
31
+ @service = attrs[:service]
32
+ @event = attrs[:event]
33
+ @action = attrs[:action]
34
+ @date = attrs[:date]
35
+ @description = attrs[:description]
36
+ @severity = self.calculate_severity
37
+ end
38
+
39
+ # As we don't (currently) require ACKs for alerts we might need to
40
+ # send alerts several times in case HM temporarily goes down
41
+ def register
42
+ return if severity >= SEVERITY_CUTOFF || severity <= 0
43
+
44
+ ALERT_RETRIES.times do |i|
45
+ EM.add_timer(i * RETRY_PERIOD) do
46
+ send_via_mbus
47
+ end
48
+ end
49
+ end
50
+
51
+ def send_via_mbus
52
+ if @state.nil?
53
+ @logger.warn("Unable to send alert: unknown agent state")
54
+ return
55
+ end
56
+
57
+ if @state["job"].blank?
58
+ @logger.info("No job, ignoring alert")
59
+ return
60
+ end
61
+
62
+ @nats.publish("hm.agent.alert.#{@agent_id}", Yajl::Encoder.encode(converted_alert_data))
63
+ end
64
+
65
+ def converted_alert_data
66
+ # INPUT: id, service, event, action, date, description
67
+ # OUTPUT: id, severity, title, summary, created_at (unix timestamp)
68
+ {
69
+ "id" => @id,
70
+ "severity" => self.calculate_severity,
71
+ "title" => self.title,
72
+ "summary" => @description,
73
+ "created_at" => self.timestamp
74
+ }
75
+ end
76
+
77
+ def title
78
+ ips = @state.ips
79
+ service = ips.size > 0 ? "#{@service} (#{ips.sort.join(", ")})" : @service
80
+ "#{service} - #{@event} - #{@action}"
81
+ end
82
+
83
+ def timestamp
84
+ Time.rfc822(@date).utc.to_i
85
+ rescue ArgumentError => e
86
+ @logger.warn("Cannot parse monit alert date `#{@date}', using current time instead")
87
+ Time.now.utc.to_i
88
+ end
89
+
90
+ def calculate_severity
91
+ known_severity = SEVERITY_MAP[@event.to_s.downcase]
92
+ if known_severity.nil?
93
+ @logger.warn("Unknown monit event name `#{@event}', using default severity #{DEFAULT_SEVERITY}")
94
+ DEFAULT_SEVERITY
95
+ else
96
+ known_severity
97
+ end
98
+ end
99
+
100
+ # use same values as syslog
101
+ ALERT = 1
102
+ CRITICAL = 2
103
+ ERROR = 3
104
+ WARNING = 4
105
+ IGNORED = -1
106
+
107
+ SEVERITY_MAP = {
108
+ "action done" => IGNORED,
109
+ "checksum failed" => CRITICAL,
110
+ "checksum changed" => WARNING,
111
+ "checksum succeeded" => IGNORED,
112
+ "checksum not changed" => IGNORED,
113
+ "connection failed" => ALERT,
114
+ "connection succeeded" => IGNORED,
115
+ "connection changed" => ERROR,
116
+ "connection not changed" => IGNORED,
117
+ "content failed" => ERROR,
118
+ "content succeeded" => IGNORED,
119
+ "content match" => IGNORED,
120
+ "content doesn't match" => ERROR,
121
+ "data access error" => ERROR,
122
+ "data access succeeded" => IGNORED,
123
+ "data access changed" => WARNING,
124
+ "data access not changed" => IGNORED,
125
+ "execution failed" => ALERT,
126
+ "execution succeeded" => IGNORED,
127
+ "execution changed" => WARNING,
128
+ "execution not changed" => IGNORED,
129
+ "filesystem flags failed" => ERROR,
130
+ "filesystem flags succeeded" => IGNORED,
131
+ "filesystem flags changed" => WARNING,
132
+ "filesystem flags not changed" => IGNORED,
133
+ "gid failed" => ERROR,
134
+ "gid succeeded" => IGNORED,
135
+ "gid changed" => WARNING,
136
+ "gid not changed" => IGNORED,
137
+ "heartbeat failed" => ERROR,
138
+ "heartbeat succeeded" => IGNORED,
139
+ "heartbeat changed" => WARNING,
140
+ "heartbeat not changed" => IGNORED,
141
+ "icmp failed" => CRITICAL,
142
+ "icmp succeeded" => IGNORED,
143
+ "icmp changed" => WARNING,
144
+ "icmp not changed" => IGNORED,
145
+ "monit instance failed" => ALERT,
146
+ "monit instance succeeded" => IGNORED,
147
+ "monit instance changed" => IGNORED,
148
+ "monit instance not changed" => IGNORED,
149
+ "invalid type" => ERROR,
150
+ "type succeeded" => IGNORED,
151
+ "type changed" => WARNING,
152
+ "type not changed" => IGNORED,
153
+ "does not exist" => ALERT,
154
+ "exists" => IGNORED,
155
+ "existence changed" => WARNING,
156
+ "existence not changed" => IGNORED,
157
+ "permission failed" => ERROR,
158
+ "permission succeeded" => IGNORED,
159
+ "permission changed" => WARNING,
160
+ "permission not changed" => IGNORED,
161
+ "pid failed" => CRITICAL,
162
+ "pid succeeded" => IGNORED,
163
+ "pid changed" => WARNING,
164
+ "pid not changed" => IGNORED,
165
+ "ppid failed" => CRITICAL,
166
+ "ppid succeeded" => IGNORED,
167
+ "ppid changed" => WARNING,
168
+ "ppid not changed" => IGNORED,
169
+ "resource limit matched" => ERROR,
170
+ "resource limit succeeded" => IGNORED,
171
+ "resource limit changed" => WARNING,
172
+ "resource limit not changed" => IGNORED,
173
+ "size failed" => ERROR,
174
+ "size succeeded" => IGNORED,
175
+ "size changed" => ERROR,
176
+ "size not changed" => IGNORED,
177
+ "timeout" => CRITICAL,
178
+ "timeout recovery" => IGNORED,
179
+ "timeout changed" => WARNING,
180
+ "timeout not changed" => IGNORED,
181
+ "timestamp failed" => ERROR,
182
+ "timestamp succeeded" => IGNORED,
183
+ "timestamp changed" => WARNING,
184
+ "timestamp not changed" => IGNORED,
185
+ "uid failed" => CRITICAL,
186
+ "uid succeeded" => IGNORED,
187
+ "uid changed" => WARNING,
188
+ "uid not changed" => IGNORED
189
+ }
190
+ end
191
+ end
@@ -0,0 +1,96 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Agent
4
+
5
+ # AlertProcessor is a simple SMTP server + callback for processing alerts.
6
+ # It is primarily meant to be used with Monit.
7
+
8
+ class AlertProcessor
9
+ class Error < StandardError; end
10
+
11
+ def self.start(host, port, user, password)
12
+ processor = new(host, port, user, password)
13
+ processor.start
14
+ processor
15
+ end
16
+
17
+ def initialize(host, port, smtp_user, smtp_password)
18
+ @host = host
19
+ @port = port
20
+ @smtp_user = smtp_user
21
+ @smtp_password = smtp_password
22
+ @logger = Config.logger
23
+ end
24
+
25
+ def start
26
+ unless EM.reactor_running?
27
+ raise Error, "Cannot start SMTP server as event loop is not running"
28
+ end
29
+
30
+ @server = EM.start_server(@host, @port, Bosh::Agent::SmtpServer, :user => @smtp_user, :password => @smtp_password, :processor => self)
31
+ @logger.info "Now accepting SMTP connections on address #{@host}, port #{@port}"
32
+ end
33
+
34
+ def stop
35
+ if @server
36
+ if EM.reactor_running?
37
+ EM.stop_server(@server)
38
+ @logger.info "Stopped alert processor"
39
+ end
40
+ @server = nil
41
+ end
42
+ end
43
+
44
+ # Processes raw alert received by email (i.e. from Monit).
45
+ #
46
+ # == Parameters:
47
+ # raw_email::
48
+ # A String containg raw alert data. In Monit case it is essentially
49
+ # a raw email text (incl. headers). We only accept the following
50
+ # Monit alert format:
51
+ # set mail-format {
52
+ # from: monit@localhost
53
+ # subject: Monit Alert
54
+ # message: Service: $SERVICE
55
+ # Event: $EVENT
56
+ # Action: $ACTION
57
+ # Date: $DATE
58
+ # Description: $DESCRIPTION
59
+ # }
60
+
61
+ # == Returns:
62
+ # true if succeeded to process the alert, false if failed
63
+ #
64
+ def process_email_alert(raw_email)
65
+ create_alert_from_email(raw_email).register
66
+ end
67
+
68
+ def create_alert_from_email(raw_email)
69
+ @logger.debug "Received email alert: #{raw_email}"
70
+
71
+ attrs = { }
72
+
73
+ raw_email.split(/\r?\n/).each do |line|
74
+ case line
75
+ when /^\s*Message-id:\s*<(.*)>$/i
76
+ attrs[:id] = $1.split("@")[0] # Remove host
77
+ when /^\s*Service:\s*(.*)$/i
78
+ attrs[:service] = $1
79
+ when /^\s*Event:\s*(.*)$/i
80
+ attrs[:event] = $1
81
+ when /^\s*Action:\s*(.*)$/i
82
+ attrs[:action] = $1
83
+ when /^\s*Date:\s*(.*)$/i
84
+ attrs[:date] = $1
85
+ when /^\s*Description:\s*(.*)$/i
86
+ attrs[:description] = $1
87
+ end
88
+ end
89
+
90
+ @logger.debug("Extracted email alert data: #{attrs}")
91
+ Alert.new(attrs)
92
+ end
93
+
94
+ end
95
+
96
+ end
@@ -0,0 +1,30 @@
1
+ module Bosh::Agent
2
+ module ApplyPlan
3
+ module Helpers
4
+
5
+ private
6
+
7
+ def validate_spec(spec)
8
+ unless spec.is_a?(Hash)
9
+ raise ArgumentError, "Invalid #{self.class} spec: " +
10
+ "Hash expected, #{spec.class} given"
11
+ end
12
+
13
+ required_keys = %w(name version sha1 blobstore_id)
14
+ missing_keys = required_keys.select { |k| spec[k].nil? }
15
+ unless missing_keys.empty?
16
+ raise ArgumentError, "Invalid #{self.class} spec: " +
17
+ "#{missing_keys.join(', ')} missing"
18
+ end
19
+ end
20
+
21
+ def fetch_bits
22
+ FileUtils.mkdir_p(File.dirname(@install_path))
23
+ FileUtils.mkdir_p(File.dirname(@link_path))
24
+
25
+ Bosh::Agent::Util.unpack_blob(@blobstore_id, @checksum, @install_path)
26
+ Bosh::Agent::Util.create_symlink(@install_path, @link_path)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,235 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Agent
4
+ module ApplyPlan
5
+ class Job
6
+
7
+ class InstallationError < StandardError; end
8
+ class ConfigurationError < StandardError; end
9
+
10
+ include ApplyPlan::Helpers
11
+
12
+ attr_reader :install_path
13
+ attr_reader :link_path
14
+ attr_reader :template
15
+
16
+ # Initializes a job.
17
+ # @param [String] job_name The name of the job being set up, such as
18
+ # "nats".
19
+ # @param [Hash] template_spec A hash that came from the apply spec
20
+ # message. This hash contains information about the template that is
21
+ # to be setup in this job. It has keys such as:
22
+ # "name", "version", "sha1", "blobstore_id"
23
+ # @param [Bosh::Agent::Util::BindingHelper] config_binding A binding
24
+ # helper instance.
25
+ def initialize(job_name, template_name, template_spec,
26
+ config_binding = nil)
27
+ validate_spec(template_spec)
28
+
29
+ @base_dir = Bosh::Agent::Config.base_dir
30
+ @name = "#{job_name}.#{template_name}"
31
+ @template = template_name
32
+ @version = template_spec['version']
33
+ @checksum = template_spec['sha1']
34
+ @blobstore_id = template_spec['blobstore_id']
35
+ @config_binding = config_binding
36
+
37
+ @install_path = File.join(@base_dir, 'data', 'jobs',
38
+ @template, @version)
39
+ @link_path = File.join(@base_dir, 'jobs', @template)
40
+ end
41
+
42
+ def install
43
+ fetch_bits
44
+ bind_configuration
45
+ harden_permissions
46
+ rescue SystemCallError => e
47
+ install_failed("system call error: #{e.message}")
48
+ end
49
+
50
+ def configure(job_index)
51
+ run_post_install_hook
52
+ configure_monit(job_index)
53
+ rescue SystemCallError => e
54
+ config_failed("system call error: #{e.message}")
55
+ end
56
+
57
+ private
58
+
59
+ def bind_configuration
60
+ if @config_binding.nil?
61
+ install_failed("unable to bind configuration, " +
62
+ "no binding provided")
63
+ end
64
+
65
+ bin_dir = File.join(@install_path, "bin")
66
+ manifest_path = File.join(@install_path, "job.MF")
67
+
68
+ unless File.exists?(manifest_path)
69
+ install_failed("cannot find job manifest #{manifest_path}")
70
+ end
71
+
72
+ FileUtils.mkdir_p(bin_dir)
73
+
74
+ begin
75
+ manifest = Psych.load_file(manifest_path)
76
+ rescue Psych::SyntaxError
77
+ install_failed("malformed job manifest #{manifest_path}")
78
+ end
79
+
80
+ unless manifest.is_a?(Hash)
81
+ install_failed("invalid job manifest, " +
82
+ "Hash expected, #{manifest.class} given")
83
+ end
84
+
85
+ templates = manifest["templates"] || {}
86
+
87
+ unless templates.kind_of?(Hash)
88
+ install_failed("invalid value for templates in job manifest, " +
89
+ "Hash expected, #{templates.class} given")
90
+ end
91
+
92
+ templates.each_pair do |src, dst|
93
+ template_path = File.join(@install_path, "templates", src)
94
+ output_path = File.join(@install_path, dst)
95
+
96
+ unless File.exists?(template_path)
97
+ install_failed("template '#{src}' doesn't exist")
98
+ end
99
+
100
+ template = ERB.new(File.read(template_path))
101
+ template.filename = src
102
+ begin
103
+ result = template.result(@config_binding)
104
+ rescue Exception => e
105
+ # We are essentially running an arbitrary code,
106
+ # hence such a generic rescue clause
107
+ line_index = e.backtrace.index{ |l| l.include?(src) } || 0
108
+ line = e.backtrace[line_index].match(/:(\d+):/).captures.first
109
+ install_failed("failed to process configuration template " +
110
+ "'#{src}': " +
111
+ "line #{line}, error: #{e.message}")
112
+ end
113
+
114
+ FileUtils.mkdir_p(File.dirname(output_path))
115
+ File.open(output_path, "w") { |f| f.write(result) }
116
+
117
+ if File.basename(File.dirname(output_path)) == "bin"
118
+ FileUtils.chmod(0755, output_path)
119
+ end
120
+ end
121
+ end
122
+
123
+ def harden_permissions
124
+ return unless Bosh::Agent::Config.configure
125
+
126
+ FileUtils.chown_R("root", Bosh::Agent::BOSH_APP_USER, @install_path)
127
+ chmod_others = "chmod -R o-rwx #{@install_path} 2>&1"
128
+ chmod_group = "chmod g+rx #{@install_path} 2>&1"
129
+
130
+ out = %x(#{chmod_others})
131
+ unless $?.exitstatus == 0
132
+ install_failed("error executing '#{chmod_others}': #{out}")
133
+ end
134
+
135
+ out = %x(#{chmod_group})
136
+ unless $?.exitstatus == 0
137
+ install_failed("error executing '#{chmod_group}': #{out}")
138
+ end
139
+ end
140
+
141
+ def run_post_install_hook
142
+ Bosh::Agent::Util.run_hook("post_install", @template)
143
+ end
144
+
145
+ def configure_monit(job_index)
146
+ Dir.foreach(@install_path).each do |file|
147
+ full_path = File.expand_path(file, @install_path)
148
+
149
+ if file == "monit"
150
+ install_job_monitrc(full_path, @name, job_index)
151
+ elsif file =~ /(.*)\.monit$/
152
+ install_job_monitrc(full_path, "#{@name}_#{$1}", job_index)
153
+ end
154
+ end
155
+ end
156
+
157
+ def install_job_monitrc(template_path, label, job_index)
158
+ if @config_binding.nil?
159
+ config_failed("Unable to configure monit, " +
160
+ "no binding provided")
161
+ end
162
+
163
+ template = ERB.new(File.read(template_path))
164
+ formatted_job_index = "%04d" % job_index
165
+ out_file = File.join(@install_path, "#{formatted_job_index}_#{label}.monitrc")
166
+
167
+ begin
168
+ result = template.result(@config_binding)
169
+ rescue Exception => e
170
+ line = e.backtrace.first.match(/:(\d+):/).captures.first
171
+ config_failed("failed to process monit template " +
172
+ "'#{File.basename(template_path)}': " +
173
+ "line #{line}, error: #{e.message}")
174
+ end
175
+
176
+ File.open(out_file, "w") do |f|
177
+ f.write(add_modes(result))
178
+ end
179
+
180
+ # Monit will load all {base_dir}/monit/job/*.monitrc files,
181
+ # so we need to blow away this directory when we clean up.
182
+ link_path = File.join(@base_dir, "monit", "job", "#{formatted_job_index}_#{label}.monitrc")
183
+
184
+ FileUtils.mkdir_p(File.dirname(link_path))
185
+ Bosh::Agent::Util.create_symlink(out_file, link_path)
186
+ end
187
+
188
+ # HACK
189
+ # Force manual mode on all services which don't have mode already set.
190
+ # FIXME: this parser is very simple and thus generates space-delimited
191
+ # output. Can be improved to respect indentation for mode. Also it doesn't
192
+ # skip quoted tokens.
193
+ def add_modes(job_monitrc)
194
+ state = :out
195
+ need_mode = true
196
+ result = ""
197
+
198
+ tokens = job_monitrc.split(/\s+/)
199
+
200
+ return "" if tokens.empty?
201
+
202
+ while (t = tokens.shift)
203
+ if t == "check"
204
+ if state == :in && need_mode
205
+ result << "mode manual "
206
+ end
207
+ state = :in
208
+ need_mode = true
209
+
210
+ elsif t == "mode" && %w(passive manual active).include?(tokens[0])
211
+ need_mode = false
212
+ end
213
+
214
+ result << t << " "
215
+ end
216
+
217
+ if need_mode
218
+ result << "mode manual "
219
+ end
220
+
221
+ result.strip
222
+ end
223
+
224
+ def install_failed(message)
225
+ raise InstallationError, "Failed to install job '#{@name}': #{message}"
226
+ end
227
+
228
+ def config_failed(message)
229
+ raise ConfigurationError, "Failed to configure job " +
230
+ "'#{@name}': #{message}"
231
+ end
232
+
233
+ end
234
+ end
235
+ end