bosh_agent 1.5.0.pre.1113

Sign up to get free protection for your applications and to get access to all the features.
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