bosh_agent 1.5.0.pre.1113
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +0 -0
- data/bin/bosh_agent +102 -0
- data/lib/bosh_agent/alert.rb +191 -0
- data/lib/bosh_agent/alert_processor.rb +96 -0
- data/lib/bosh_agent/apply_plan/helpers.rb +30 -0
- data/lib/bosh_agent/apply_plan/job.rb +235 -0
- data/lib/bosh_agent/apply_plan/package.rb +58 -0
- data/lib/bosh_agent/apply_plan/plan.rb +96 -0
- data/lib/bosh_agent/bootstrap.rb +341 -0
- data/lib/bosh_agent/config.rb +5 -0
- data/lib/bosh_agent/configuration.rb +102 -0
- data/lib/bosh_agent/disk_util.rb +103 -0
- data/lib/bosh_agent/errors.rb +25 -0
- data/lib/bosh_agent/ext.rb +48 -0
- data/lib/bosh_agent/file_aggregator.rb +78 -0
- data/lib/bosh_agent/file_matcher.rb +45 -0
- data/lib/bosh_agent/handler.rb +440 -0
- data/lib/bosh_agent/heartbeat.rb +74 -0
- data/lib/bosh_agent/heartbeat_processor.rb +45 -0
- data/lib/bosh_agent/http_handler.rb +135 -0
- data/lib/bosh_agent/infrastructure/aws/registry.rb +177 -0
- data/lib/bosh_agent/infrastructure/aws/settings.rb +59 -0
- data/lib/bosh_agent/infrastructure/aws.rb +17 -0
- data/lib/bosh_agent/infrastructure/dummy.rb +24 -0
- data/lib/bosh_agent/infrastructure/openstack/registry.rb +220 -0
- data/lib/bosh_agent/infrastructure/openstack/settings.rb +76 -0
- data/lib/bosh_agent/infrastructure/openstack.rb +17 -0
- data/lib/bosh_agent/infrastructure/vsphere/settings.rb +135 -0
- data/lib/bosh_agent/infrastructure/vsphere.rb +16 -0
- data/lib/bosh_agent/infrastructure.rb +25 -0
- data/lib/bosh_agent/message/apply.rb +184 -0
- data/lib/bosh_agent/message/base.rb +38 -0
- data/lib/bosh_agent/message/compile_package.rb +250 -0
- data/lib/bosh_agent/message/drain.rb +195 -0
- data/lib/bosh_agent/message/list_disk.rb +25 -0
- data/lib/bosh_agent/message/logs.rb +108 -0
- data/lib/bosh_agent/message/migrate_disk.rb +55 -0
- data/lib/bosh_agent/message/mount_disk.rb +102 -0
- data/lib/bosh_agent/message/ssh.rb +109 -0
- data/lib/bosh_agent/message/state.rb +47 -0
- data/lib/bosh_agent/message/unmount_disk.rb +29 -0
- data/lib/bosh_agent/monit.rb +354 -0
- data/lib/bosh_agent/monit_client.rb +158 -0
- data/lib/bosh_agent/mounter.rb +42 -0
- data/lib/bosh_agent/ntp.rb +32 -0
- data/lib/bosh_agent/platform/centos/disk.rb +27 -0
- data/lib/bosh_agent/platform/centos/network.rb +39 -0
- data/lib/bosh_agent/platform/centos/templates/centos-ifcfg.erb +9 -0
- data/lib/bosh_agent/platform/centos/templates/dhclient_conf.erb +56 -0
- data/lib/bosh_agent/platform/centos/templates/logrotate.erb +8 -0
- data/lib/bosh_agent/platform/centos.rb +4 -0
- data/lib/bosh_agent/platform/dummy/templates/dummy_template.erb +1 -0
- data/lib/bosh_agent/platform/linux/adapter.rb +36 -0
- data/lib/bosh_agent/platform/linux/disk.rb +121 -0
- data/lib/bosh_agent/platform/linux/logrotate.rb +32 -0
- data/lib/bosh_agent/platform/linux/network.rb +124 -0
- data/lib/bosh_agent/platform/linux/password.rb +22 -0
- data/lib/bosh_agent/platform/linux.rb +4 -0
- data/lib/bosh_agent/platform/ubuntu/network.rb +59 -0
- data/lib/bosh_agent/platform/ubuntu/templates/dhclient_conf.erb +56 -0
- data/lib/bosh_agent/platform/ubuntu/templates/interfaces.erb +14 -0
- data/lib/bosh_agent/platform/ubuntu/templates/logrotate.erb +8 -0
- data/lib/bosh_agent/platform/ubuntu.rb +4 -0
- data/lib/bosh_agent/platform.rb +26 -0
- data/lib/bosh_agent/remote_exception.rb +62 -0
- data/lib/bosh_agent/runner.rb +36 -0
- data/lib/bosh_agent/settings.rb +61 -0
- data/lib/bosh_agent/sigar_box.rb +26 -0
- data/lib/bosh_agent/smtp_server.rb +96 -0
- data/lib/bosh_agent/state.rb +100 -0
- data/lib/bosh_agent/syslog_monitor.rb +53 -0
- data/lib/bosh_agent/template.rb +50 -0
- data/lib/bosh_agent/util.rb +190 -0
- data/lib/bosh_agent/version.rb +8 -0
- data/lib/bosh_agent.rb +92 -0
- 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
|