emissary 1.3.20 → 1.3.21
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +1 -0
- data/LICENSE +203 -0
- data/Manifest.txt +38 -0
- data/README.txt +54 -0
- data/Rakefile +98 -0
- data/VERSION.yml +4 -0
- data/lib/emissary.rb +226 -0
- data/lib/emissary/agent.rb +61 -0
- data/lib/emissary/agent/emissary.rb +163 -0
- data/lib/emissary/agent/error.rb +26 -0
- data/lib/emissary/agent/file.rb +26 -0
- data/lib/emissary/agent/gem.rb +42 -0
- data/lib/emissary/agent/mysql.rb +230 -0
- data/lib/emissary/agent/ping.rb +37 -0
- data/lib/emissary/agent/proxy.rb +26 -0
- data/lib/emissary/agent/rabbitmq.rb +233 -0
- data/lib/emissary/agent/sshkeys.rb +152 -0
- data/lib/emissary/agent/stats.rb +96 -0
- data/lib/emissary/agent/test.rb +40 -0
- data/lib/emissary/config.rb +231 -0
- data/lib/emissary/core_ext/blank.rb +60 -0
- data/lib/emissary/core_ext/misc_object.rb +21 -0
- data/lib/emissary/core_ext/symbolize.rb +33 -0
- data/lib/emissary/daemon.rb +404 -0
- data/lib/emissary/errors.rb +106 -0
- data/lib/emissary/gem_helper.rb +183 -0
- data/lib/emissary/identity.rb +183 -0
- data/lib/emissary/identity/ec2.rb +64 -0
- data/lib/emissary/identity/unix.rb +67 -0
- data/lib/emissary/inifile.rb +148 -0
- data/lib/emissary/logger.rb +130 -0
- data/lib/emissary/message.rb +217 -0
- data/lib/emissary/operator.rb +275 -0
- data/lib/emissary/operator/amqp.rb +205 -0
- data/lib/emissary/server.rb +98 -0
- data/lib/emissary/servolux.rb +75 -0
- metadata +42 -6
@@ -0,0 +1,148 @@
|
|
1
|
+
#
|
2
|
+
# This class represents the INI file and can be used to parse INI files.
|
3
|
+
# Derived from IniFile gem, found on http://rubyforge.org/projects/inifile/
|
4
|
+
#
|
5
|
+
class IniFile
|
6
|
+
|
7
|
+
#
|
8
|
+
# call-seq:
|
9
|
+
# IniFile.load( filename )
|
10
|
+
# IniFile.load( filename, options )
|
11
|
+
#
|
12
|
+
# Open the given _filename_ and load the contents of the INI file.
|
13
|
+
# The following _options_ can be passed to this method:
|
14
|
+
#
|
15
|
+
# :comment => ';' The line comment character(s)
|
16
|
+
# :parameter => '=' The parameter / value separator
|
17
|
+
#
|
18
|
+
def self.load( filename, opts = {} )
|
19
|
+
if filename
|
20
|
+
new(filename, opts)
|
21
|
+
else
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize( filename, opts = {} )
|
27
|
+
@fn = filename
|
28
|
+
@comment = opts[:comment] || '#'
|
29
|
+
@param = opts[:parameter] || '='
|
30
|
+
@ini = Hash.new {|h,k| h[k] = Hash.new}
|
31
|
+
|
32
|
+
@rgxp_comment = /^\s*$|^\s*[#{@comment}]/
|
33
|
+
@rgxp_section = /^\s*\[([^\]]+)\]/
|
34
|
+
@rgxp_param = /^([^#{@param}]+)#{@param}(.*)$/
|
35
|
+
|
36
|
+
parse
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# call-seq:
|
41
|
+
# each {|section, parameter, value| block}
|
42
|
+
#
|
43
|
+
# Yield each _section_, _parameter_, _value_ in turn to the given
|
44
|
+
# _block_. The method returns immediately if no block is supplied.
|
45
|
+
#
|
46
|
+
def each
|
47
|
+
return unless block_given?
|
48
|
+
@ini.each do |section,hash|
|
49
|
+
hash.each do |param,val|
|
50
|
+
yield section, param, val
|
51
|
+
end
|
52
|
+
end
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# call-seq:
|
58
|
+
# each_section {|section| block}
|
59
|
+
#
|
60
|
+
# Yield each _section_ in turn to the given _block_. The method returns
|
61
|
+
# immediately if no block is supplied.
|
62
|
+
#
|
63
|
+
def each_section
|
64
|
+
return unless block_given?
|
65
|
+
@ini.each_key {|section| yield section}
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# call-seq:
|
71
|
+
# ini_file[section]
|
72
|
+
#
|
73
|
+
# Get the hash of parameter/value pairs for the given _section_.
|
74
|
+
#
|
75
|
+
def []( section )
|
76
|
+
return nil if section.nil?
|
77
|
+
@ini[section.to_s]
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# call-seq:
|
82
|
+
# has_section?( section )
|
83
|
+
#
|
84
|
+
# Returns +true+ if the named _section_ exists in the INI file.
|
85
|
+
#
|
86
|
+
def has_section?( section )
|
87
|
+
@ini.has_key? section.to_s
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# call-seq:
|
92
|
+
# sections
|
93
|
+
#
|
94
|
+
# Returns an array of the section names.
|
95
|
+
#
|
96
|
+
def sections
|
97
|
+
@ini.keys
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def cleanup(str)
|
103
|
+
str = str.strip
|
104
|
+
first = str[0..0]; last = str[-1..-1]
|
105
|
+
str = str[1..-2] if first == last && (first == '"' || first == "'")
|
106
|
+
end
|
107
|
+
#
|
108
|
+
# call-seq
|
109
|
+
# parse
|
110
|
+
#
|
111
|
+
# Parse the ini file contents.
|
112
|
+
#
|
113
|
+
def parse
|
114
|
+
return unless ::Kernel.test ?f, @fn
|
115
|
+
section = nil
|
116
|
+
|
117
|
+
::File.open(@fn, 'r') do |f|
|
118
|
+
while line = f.gets
|
119
|
+
line = line.chomp
|
120
|
+
|
121
|
+
case line
|
122
|
+
# ignore blank lines and comment lines
|
123
|
+
when @rgxp_comment: next
|
124
|
+
|
125
|
+
# this is a section declaration
|
126
|
+
when @rgxp_section: section = @ini[$1.strip.downcase]
|
127
|
+
|
128
|
+
# otherwise we have a parameter
|
129
|
+
when @rgxp_param
|
130
|
+
begin
|
131
|
+
val = $2.strip
|
132
|
+
val = val[1..-2] if val[0..0] == "'" || val[-1..-1] == '"'
|
133
|
+
section[$1.strip.downcase.to_sym] = val
|
134
|
+
rescue NoMethodError
|
135
|
+
raise "Bad configuration - inifile parameter encountered before first section"
|
136
|
+
end
|
137
|
+
|
138
|
+
else
|
139
|
+
raise "Bad configuration -- inifile could not parse line '#{line}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# Copyright 2010 The New York Times
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
#
|
15
|
+
#
|
16
|
+
#
|
17
|
+
# $Id $
|
18
|
+
# author: Carl P. corliss
|
19
|
+
# Copyright 2009, The New York Times
|
20
|
+
#
|
21
|
+
require 'syslog'
|
22
|
+
require 'fastthread'
|
23
|
+
|
24
|
+
module Emissary
|
25
|
+
class Logger
|
26
|
+
|
27
|
+
LOG_SYSLOG = 0x01
|
28
|
+
LOG_STDOUT = 0x02
|
29
|
+
LOG_STDERR = 0x03
|
30
|
+
|
31
|
+
EMERGENCY = Syslog::Constants::LOG_EMERG
|
32
|
+
EMERG = Syslog::Constants::LOG_EMERG
|
33
|
+
ALERT = Syslog::Constants::LOG_ALERT
|
34
|
+
FATAL = Syslog::Constants::LOG_CRIT
|
35
|
+
CRITICAL = Syslog::Constants::LOG_CRIT
|
36
|
+
CRIT = Syslog::Constants::LOG_CRIT
|
37
|
+
ERROR = Syslog::Constants::LOG_ERR
|
38
|
+
ERR = Syslog::Constants::LOG_ERR
|
39
|
+
WARNING = Syslog::Constants::LOG_WARNING
|
40
|
+
WARN = Syslog::Constants::LOG_WARNING
|
41
|
+
NOTICE = Syslog::Constants::LOG_NOTICE
|
42
|
+
INFO = Syslog::Constants::LOG_INFO
|
43
|
+
DEBUG = Syslog::Constants::LOG_DEBUG
|
44
|
+
|
45
|
+
CONSTANT_ID_MAP = {
|
46
|
+
EMERGENCY => [ :emergency, :emerg ],
|
47
|
+
ALERT => [ :alert ],
|
48
|
+
CRITICAL => [ :fatal, :critical, :crit ],
|
49
|
+
ERROR => [ :error, :err ],
|
50
|
+
WARNING => [ :warning, :warn ],
|
51
|
+
NOTICE => [ :notice ],
|
52
|
+
INFO => [ :info ],
|
53
|
+
DEBUG => [ :debug ]
|
54
|
+
}
|
55
|
+
|
56
|
+
CONSTANT_NAME_MAP = CONSTANT_ID_MAP.inject({}) do |mappings,(id,names)|
|
57
|
+
names.each { |name| mappings.merge!({ name => id, name.to_s.upcase => id, name.to_s.downcase => id }) }
|
58
|
+
mappings
|
59
|
+
end
|
60
|
+
|
61
|
+
attr_accessor :mode, :level, :name
|
62
|
+
|
63
|
+
def self.instance(mode = LOG_STDERR, log_level = NOTICE, name = nil)
|
64
|
+
@@logger ||= Emissary::Logger.new mode, log_level
|
65
|
+
end
|
66
|
+
|
67
|
+
private :initialize
|
68
|
+
|
69
|
+
def initialize mode, log_level, name = nil
|
70
|
+
@mode = mode || LOG_STDERR
|
71
|
+
@level = log_level || INFO
|
72
|
+
@name = name
|
73
|
+
@mutex = Mutex.new
|
74
|
+
end
|
75
|
+
|
76
|
+
def name=(p) @name = n || File.basename($0); end
|
77
|
+
|
78
|
+
def normalize log_level
|
79
|
+
return log_level unless not log_level.kind_of? Fixnum
|
80
|
+
return CONSTANT_NAME_MAP[log_level.to_s.downcase] rescue INFO
|
81
|
+
return INFO
|
82
|
+
end
|
83
|
+
|
84
|
+
def level_to_sym
|
85
|
+
CONSTANT_ID_MAP[@level].first
|
86
|
+
end
|
87
|
+
|
88
|
+
def level_to_s
|
89
|
+
level_to_sym.to_s.upcase
|
90
|
+
end
|
91
|
+
|
92
|
+
def loggable?(log_level)
|
93
|
+
log_level <= normalize(@level)
|
94
|
+
end
|
95
|
+
|
96
|
+
def syslogger
|
97
|
+
@mutex.synchronize {
|
98
|
+
Syslog.open(name, Syslog::LOG_PID | Syslog::LOG_NDELAY, Syslog::LOG_DAEMON) do |s|
|
99
|
+
s.LOG_UPTO(level)
|
100
|
+
yield s
|
101
|
+
end
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
def log(log_level, message, *args)
|
106
|
+
case @mode
|
107
|
+
when Logger::LOG_SYSLOG
|
108
|
+
messages = "#{CONSTANT_ID_MAP[log_level].first.to_s.upcase}: #{message}".gsub(/\t/, ' '*8).split(/\n/)
|
109
|
+
# some syslog daemons have a hard time with newlines, so split into multiple lines
|
110
|
+
messages.each do |message|
|
111
|
+
syslogger { |s| s.log(log_level, message, *args) }
|
112
|
+
end
|
113
|
+
when Logger::LOG_STDOUT
|
114
|
+
$stdout << sprintf(message << "\n", *args) unless not loggable? log_level
|
115
|
+
when Logger::LOG_STDERR
|
116
|
+
$stderr << sprintf(message << "\n", *args) unless not loggable? log_level
|
117
|
+
end
|
118
|
+
|
119
|
+
self
|
120
|
+
end
|
121
|
+
|
122
|
+
CONSTANT_ID_MAP.values.flatten.each do |log_level|
|
123
|
+
class_eval %(
|
124
|
+
def #{log_level}(message, *args)
|
125
|
+
log(#{'::Emissary::Logger::' + log_level.to_s.upcase}, message, *args)
|
126
|
+
end
|
127
|
+
)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
# Copyright 2010 The New York Times
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
#
|
15
|
+
#
|
16
|
+
require 'uuid'
|
17
|
+
require 'bert'
|
18
|
+
|
19
|
+
module Emissary
|
20
|
+
class Message
|
21
|
+
|
22
|
+
attr_accessor :sender, :recipient, :replyto, :errors
|
23
|
+
attr_accessor :status, :operation, :thread, :time
|
24
|
+
attr_accessor :account, :agent, :method, :args
|
25
|
+
attr_reader :uuid, :originator
|
26
|
+
|
27
|
+
def initialize(payload = {})
|
28
|
+
@recipient = ''
|
29
|
+
@sender = Emissary.identity.queue_name
|
30
|
+
@replyto = Emissary.identity.queue_name
|
31
|
+
@originator = @sender.dup
|
32
|
+
|
33
|
+
@status = [ :ok, '' ] # tuple of (status type, status message)
|
34
|
+
@operation = -1
|
35
|
+
@thread = -1
|
36
|
+
@uuid = UUID.new.generate
|
37
|
+
|
38
|
+
@errors = []
|
39
|
+
|
40
|
+
@agent = @method = nil
|
41
|
+
@account = Emissary.identity.account_id
|
42
|
+
@args = []
|
43
|
+
|
44
|
+
@time = {
|
45
|
+
:received => nil,
|
46
|
+
:sent => nil,
|
47
|
+
}
|
48
|
+
|
49
|
+
payload = {
|
50
|
+
:headers => {
|
51
|
+
:recipient => @recipient,
|
52
|
+
:sender => @sender,
|
53
|
+
:replyto => @replyto,
|
54
|
+
:originator => @originator,
|
55
|
+
:status => @status,
|
56
|
+
:operation => @operation,
|
57
|
+
:thread => @thread,
|
58
|
+
:uuid => @uuid,
|
59
|
+
:time => @time.merge((payload[:time].symbolize rescue {})),
|
60
|
+
}.merge((payload[:headers].symbolize rescue {})),
|
61
|
+
:data => {
|
62
|
+
:account => @account,
|
63
|
+
:agent => @agent,
|
64
|
+
:method => @method,
|
65
|
+
:args => @args
|
66
|
+
}.merge((payload[:data].symbolize! rescue {})),
|
67
|
+
:errors => [ ] + (payload[:errors].symbolize rescue [])
|
68
|
+
}
|
69
|
+
|
70
|
+
payload[:headers].merge(payload[:data]).each do |k,v|
|
71
|
+
send("#{k}=".to_sym, v) rescue nil
|
72
|
+
end
|
73
|
+
|
74
|
+
payload[:errors].each do |e|
|
75
|
+
exception = ::Emissary.klass_const(e[:type]).new(e[:message]) rescue StandardError.new("#{e[:type]}: #{e[:message]}")
|
76
|
+
exception.set_backtrace(e[:backtrace])
|
77
|
+
errors << exception
|
78
|
+
end
|
79
|
+
|
80
|
+
@agent = @agent.to_sym rescue nil
|
81
|
+
@method = @method.to_sym rescue nil
|
82
|
+
@args = @args || [] rescue []
|
83
|
+
end
|
84
|
+
|
85
|
+
def headers()
|
86
|
+
{
|
87
|
+
:recipient => recipient,
|
88
|
+
:sender => sender,
|
89
|
+
:replyto => replyto,
|
90
|
+
:originator => originator,
|
91
|
+
:status => status,
|
92
|
+
:operation => operation,
|
93
|
+
:thread => thread,
|
94
|
+
:time => time,
|
95
|
+
:uuid => uuid
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
def data()
|
100
|
+
return { :account => account, :agent => agent, :method => method, :args => args }
|
101
|
+
end
|
102
|
+
|
103
|
+
def errors type = :default
|
104
|
+
case type
|
105
|
+
when :hashes
|
106
|
+
@errors.collect do |e|
|
107
|
+
{ :type => e.class.name, :message => e.message, :backtrace => e.backtrace }
|
108
|
+
end
|
109
|
+
else
|
110
|
+
@errors
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def status_type=(t) status[0] = t.to_sym; end
|
115
|
+
def status_type() status[0] || :ok rescue :ok; end
|
116
|
+
|
117
|
+
def status_note=(n) status[1] = n; end
|
118
|
+
def status_note() status[1] || '' rescue '' ; end
|
119
|
+
|
120
|
+
def route who = :recipient
|
121
|
+
headers[who].split(':') || [] rescue []
|
122
|
+
end
|
123
|
+
|
124
|
+
def routing_key who = :recipient
|
125
|
+
route(who)[0] || nil rescue nil
|
126
|
+
end
|
127
|
+
|
128
|
+
def exchange who = :recipient
|
129
|
+
[ exchange_type(who), exchange_name(who) ]
|
130
|
+
end
|
131
|
+
|
132
|
+
def exchange_type who = :recipient
|
133
|
+
route(who)[1].to_sym || :direct rescue :direct
|
134
|
+
end
|
135
|
+
|
136
|
+
def exchange_name who = :recipient
|
137
|
+
key, type, name = route(who) rescue [ nil, :direct, 'amq.direct' ]
|
138
|
+
name || case type.to_sym
|
139
|
+
when :fanout, :topic, :matches, :headers
|
140
|
+
"amq.#{type}"
|
141
|
+
else
|
142
|
+
'amq.direct'
|
143
|
+
end
|
144
|
+
rescue
|
145
|
+
'amq.direct'
|
146
|
+
end
|
147
|
+
|
148
|
+
def canonical_route who = :recipient
|
149
|
+
"#{routing_key(who)}:#{exchange(who).join(':')}"
|
150
|
+
end
|
151
|
+
|
152
|
+
def will_loop?
|
153
|
+
canonical_route(:recipient) == canonical_route(:originator)
|
154
|
+
end
|
155
|
+
|
156
|
+
def encode
|
157
|
+
BERT.encode({ :headers => headers, :data => data, :errors => errors(:hashes) })
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.decode payload
|
161
|
+
begin
|
162
|
+
self.new BERT.decode(payload)
|
163
|
+
rescue StandardError => e
|
164
|
+
raise e unless e.message =~ /bad magic/i
|
165
|
+
Emissary.logger.error "Unable to decode message - maybe it wasn't encoded with BERT..?"
|
166
|
+
raise ::Emissary::Error::InvalidMessageFormat, "Unable to decode message - maybe it wasn't encoded with BERT? Message: #{payload.inspect}"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def stamp_sent!
|
171
|
+
time[:sent] = Time.now.to_f
|
172
|
+
self
|
173
|
+
end
|
174
|
+
|
175
|
+
def stamp_received!
|
176
|
+
time[:received] = Time.now.to_f
|
177
|
+
self
|
178
|
+
end
|
179
|
+
|
180
|
+
def trip_time
|
181
|
+
(time[:sent].to_f - time[:received].to_f rescue 0.0) || 0.0
|
182
|
+
end
|
183
|
+
|
184
|
+
def response
|
185
|
+
header = {
|
186
|
+
:recipient => replyto || sender,
|
187
|
+
:status => [ :ok, '' ],
|
188
|
+
:operation => operation,
|
189
|
+
:thread => thread,
|
190
|
+
:time => {
|
191
|
+
:received => nil,
|
192
|
+
:sent => nil
|
193
|
+
}
|
194
|
+
}
|
195
|
+
return Message.new({ :headers => header, :data => data, :errors => errors(:hashes) })
|
196
|
+
end
|
197
|
+
|
198
|
+
def bounce(message = nil)
|
199
|
+
message ||= 'Message failed due to missing handler.'
|
200
|
+
bounced = self.response
|
201
|
+
bounced.status = [ :bounced, message ]
|
202
|
+
return bounced
|
203
|
+
end
|
204
|
+
|
205
|
+
def error(message = nil)
|
206
|
+
message ||= 'Message failed due to unspecified error.'
|
207
|
+
error = self.response
|
208
|
+
error.status = [ :errored, message.to_s ]
|
209
|
+
if message.is_a? Exception
|
210
|
+
error.errors << message
|
211
|
+
else
|
212
|
+
::Emissary.logger.warning "#{message.class.name} is not an exception..."
|
213
|
+
end
|
214
|
+
return error
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|