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.
- 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
|