emissary 1.3.0

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.
@@ -0,0 +1,64 @@
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 'net/http'
17
+ require 'socket'
18
+
19
+ module Emissary
20
+ class Identity::Ec2 < Identity
21
+ register :ec2, :priority => 25
22
+
23
+ QUERY_IP = '169.254.169.254'
24
+ INSTANCE_ID_PATH = '/latest/meta-data/instance-id'
25
+ LOCAL_IPV4_PATH = '/latest/meta-data/local-ipv4'
26
+ PUBLIC_IPV4_PATH = '/latest/meta-data/public-ipv4'
27
+
28
+ def initialize
29
+ @instance_id = nil
30
+ @local_ipv4 = nil
31
+ @public_ipv4 = nil
32
+ end
33
+
34
+ def instance_id
35
+ @instance_id ||= get(INSTANCE_ID_PATH)
36
+ end
37
+
38
+ alias :queue_name :instance_id
39
+
40
+ def local_ip
41
+ @local_ipv4 ||= get(LOCAL_IPV4_PATH)
42
+ end
43
+
44
+ def public_ip
45
+ @public_ipv4 ||= get(PUBLIC_IPV4_PATH)
46
+ end
47
+
48
+ private
49
+
50
+ def get uri
51
+ begin
52
+ http = Net::HTTP.new(QUERY_IP)
53
+ http.open_timeout = 0.5
54
+ http.read_timeout = 0.5
55
+ http.start do |http|
56
+ http.get(uri).body
57
+ end
58
+ rescue Exception => e
59
+ ::Emissary.logger.debug "Passing identifier request to next identity handler due to failed HTTP#get request: #{e.class.name}: #{e.message}"
60
+ throw :pass
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,67 @@
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
+ module Emissary
17
+ class Identity::Unix < Identity
18
+ # defaults to priority 0 (which means, it's the last option if nothing else takes it)
19
+ register :unix
20
+
21
+ def name
22
+ @hostname ||= `hostname`.strip
23
+ end
24
+
25
+ alias :queue_name :name
26
+
27
+ def roles
28
+ ENV['ROLES'] || ''
29
+ end
30
+
31
+ def instance_id
32
+ ENV['INSTANCE_ID'] || -1
33
+ end
34
+
35
+ def server_id
36
+ ENV['SERVER_ID'] || -1
37
+ end
38
+
39
+ def cluster_id
40
+ ENV['CLUSTER_ID'] || -1
41
+ end
42
+
43
+ def account_id
44
+ ENV['ACCOUNT_ID'] || -1
45
+ end
46
+
47
+ def local_ip
48
+ @ip_local ||= begin
49
+ # turn off reverse DNS resolution temporarily
50
+ orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
51
+ @ip_local = UDPSocket.open { |s| s.connect IP_CHECK_DOMAIN, 1; s.addr.last }
52
+ rescue
53
+ nil
54
+ ensure
55
+ Socket.do_not_reverse_lookup = orig rescue nil
56
+ end
57
+ end
58
+
59
+ def public_ip
60
+ @ip_public ||= begin
61
+ Net::HTTP.get(URI.parse(IP_CHECK_URL)).gsub(/.*?((\d{1,3}\.){3}\d{1,3}).*/m, '\1')
62
+ rescue
63
+ nil
64
+ end
65
+ end
66
+ end
67
+ end
@@ -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.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])
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