emissary 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'rubygems'
4
+ require 'rubygems/source_index'
5
+
6
+ require 'fileutils'
7
+
8
+ class Object
9
+ def blank?
10
+ nil? or empty?
11
+ end
12
+ end
13
+
14
+ if ARGV.length != 1
15
+ puts File.basename($0) << ': <config path>'
16
+ exit 0
17
+ end
18
+
19
+ config_path = ARGV[0].to_s
20
+
21
+ if not File.exists?(config_path)
22
+ puts "Directory [#{config_path}] doesn't exist - creating."
23
+ FileUtils.mkdir_p config_path
24
+ end
25
+
26
+ si = Gem::SourceIndex.from_gems_in(*Gem::SourceIndex.installed_spec_directories)
27
+
28
+ gem_spec = si.find_name('emissary', Gem::Requirement.default).last
29
+
30
+ emissary_etc_path = File.join(gem_spec.full_gem_path, 'etc')
31
+
32
+ if emissary_etc_path.blank?
33
+ puts "Unable to find gem 'emissary' in the default gem paths - are you sure you installed it?"
34
+ exit 1
35
+ end
36
+
37
+ puts "Emissary Path: '#{emissary_etc_path}'"
38
+ puts "Checking ETC files"
39
+
40
+ ['emissary', 'init.d', 'sysconfig'].each do |part|
41
+
42
+
43
+ src_path = File.join(emissary_etc_path, part)
44
+ dst_path = File.join(config_path, part)
45
+
46
+ puts "Configuration files for '#{part}' found at: #{src_path}"
47
+
48
+ if not File.exists?(dst_path)
49
+ puts " - Creating missing '#{dst_path}' directory."
50
+ FileUtils.mkdir_p [ dst_path ]
51
+ end
52
+
53
+ Dir.glob(src_path + '/*').each do |file|
54
+ src_file = File.join(src_path, File.basename(file))
55
+ dst_file = File.join(dst_path, File.basename(file))
56
+
57
+ if not File.exists?(dst_file)
58
+ puts " - Copying '#{src_file}' --> '#{dst_file}'"
59
+ FileUtils.cp src_file, dst_file
60
+ else
61
+ puts " - Skipping overwrite of pre-existing configuration file '#{dst_file}'"
62
+ end
63
+ end
64
+ end
65
+
66
+
67
+ print "Checking run directory - "
68
+ if not File.exists?('/var/run/emissary')
69
+ puts "missing!\n - Creating missing '/var/run/emissary' run directory"
70
+ FileUtils.mkdir_p ['/var/run/emissary']
71
+ else
72
+ puts "looks good."
73
+ end
74
+
75
+ puts "Setup done - please adjust your configuration files to meet your particular needs now."
@@ -0,0 +1,13 @@
1
+ [general]
2
+
3
+ # what operator types are monitoring for message events (comma seperated list)
4
+ operators = [ amqp ]
5
+
6
+ # pid_dir: Where to store the Process ID file which contains the id of the process
7
+ # and is used for stoping and reloading the service
8
+ pid_dir = /var/run
9
+
10
+ # log_level: the level of information to log. see 'man 3 syslog' for list of log levels
11
+ log_level = NOTICE
12
+
13
+ agents = [ all ]
@@ -0,0 +1,50 @@
1
+ #!/bin/sh
2
+ # $Id: emissary 496 2009-03-14 02:26:46Z ccorliss $
3
+ # emissary This shell script takes care of starting and stopping emissary.
4
+ #
5
+ # chkconfig: 2345 99 10
6
+ # description: emissary provides support for handling of network events.
7
+
8
+ CONFIG_FILE=/etc/emissary/config.ini
9
+ EMISSARY_EXECUTABLE="$(ruby -rrubygems -e 'puts Gem.bindir')/emissary"
10
+
11
+ [ -f "${CONFIG_FILE}" ] || exit 0
12
+
13
+ . /etc/rc.d/init.d/functions
14
+
15
+ if [ -f /etc/sysconfig/emissary ]; then
16
+ . /etc/sysconfig/emissary
17
+ fi
18
+
19
+ # See how we were called.
20
+ case "$1" in
21
+ start)
22
+ # Start daemon.
23
+ echo -n "Starting emissary: "
24
+ daemon ${EMISSARY_EXECUTABLE} -d --config-file ${CONFIG_FILE} start
25
+ touch /var/lock/subsys/emissary
26
+ echo
27
+ ;;
28
+ stop)
29
+ # Stop daemon.
30
+ echo -n "Shutting down emissary: "
31
+ daemon ${EMISSARY_EXECUTABLE} --config-file ${CONFIG_FILE} stop
32
+ echo
33
+ rm -f /var/lock/subsys/emissary
34
+ ;;
35
+ restart)
36
+ daemon ${EMISSARY_EXECUTABLE} --config-file ${CONFIG_FILE} restart
37
+ ;;
38
+ status)
39
+ daemon ${EMISSARY_EXECUTABLE} --config-file ${CONFIG_FILE} status
40
+ ;;
41
+ reconfig)
42
+ daemon ${EMISSARY_EXECUTABLE} --config-file ${CONFIG_FILE} reconfig
43
+ ;;
44
+ *)
45
+ echo "Usage: emissary {start|stop|restart|status|reconfig}"
46
+ exit 1
47
+ ;;
48
+ esac
49
+
50
+ exit 0
@@ -0,0 +1,223 @@
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 'rubygems'
17
+ require 'uuid'
18
+ require 'digest/md5'
19
+ require 'yaml'
20
+
21
+ begin
22
+ require 'thread'
23
+ require 'fastthread'
24
+ rescue LoadError
25
+ end
26
+
27
+ module Emissary
28
+ # :stopdoc:
29
+ LIBPATH = File.expand_path(File.dirname(File.expand_path(__FILE__))) + File::SEPARATOR
30
+ PATH = File.dirname(LIBPATH) + File::SEPARATOR
31
+ VERSION = ::YAML.load(File.read(File.join(PATH, 'VERSION.yml'))).values.join '.'
32
+
33
+ EXTERNALS_BASE = File.join(File::SEPARATOR, 'opt')
34
+ EXTERNAL_IDENTITIES = File.join(EXTERNALS_BASE, 'emissary', 'identities')
35
+ EXTERNAL_AGENTS = File.join(EXTERNALS_BASE, 'emissary', 'agents')
36
+ EXTERNAL_OPERATORS = File.join(EXTERNALS_BASE, 'emissary', 'operators')
37
+
38
+ DEFAULT_EXCHANGE = :direct
39
+ DAEMON_RECHECK_INTERVAL = 10
40
+ IP_CHECK_DOMAIN = 'checkip.dyndns.org'
41
+ IP_CHECK_URL = "http://#{IP_CHECK_DOMAIN}"
42
+ # :startdoc:
43
+
44
+ class << self
45
+ @@pid = nil
46
+ def PID
47
+ @@pid
48
+ end
49
+
50
+ # Returns the version string for the library.
51
+ #
52
+ def version
53
+ VERSION
54
+ end
55
+
56
+ # Returns the library path for the module. If any arguments are given,
57
+ # they will be joined to the end of the libray path using
58
+ # <tt>File.join</tt>.
59
+ #
60
+ def libpath( *args )
61
+ args.empty? ? LIBPATH : File.join(LIBPATH, args.flatten)
62
+ end
63
+
64
+ # Returns the path for the module. If any arguments are given,
65
+ # they will be joined to the end of the path using
66
+ # <tt>File.join</tt>.
67
+ #
68
+ def path( *args )
69
+ args.empty? ? PATH : File.join(PATH, args.flatten)
70
+ end
71
+
72
+ def sublib_path( *args )
73
+ args.empty? ? PATH : File.join(LIBPATH, 'emissary', args.flatten)
74
+ end
75
+
76
+ def klass_from_handler(klass = nil, handler = nil, *args)
77
+ klass = if handler and handler.is_a? Class and not handler.nil?
78
+ raise ArgumentError, "must provide module or subclass of #{klass.name}" unless klass >= handler
79
+ handler
80
+ elsif handler.is_a? Module
81
+ resource_name = "RESOURCE_#{handler.to_s.upcase.split('::').pop}"
82
+ begin
83
+ klass.const_get(resource_name)
84
+ rescue NameError
85
+ klass.const_set(resource_name, Class.new(klass) { include handler } )
86
+ end
87
+ elsif klass.nil?
88
+ raise ArgumentError, "klass must be a valid class constant for #{name}#klass_from_handler"
89
+ elsif handler.nil?
90
+ raise ArgumentError, "handler must be a valid class or module"
91
+ else
92
+ klass
93
+ end
94
+
95
+ arity = klass.instance_method(:initialize).arity
96
+ expected = arity >= 0 ? arity : -(arity + 1)
97
+ if (arity >= 0 and args.size != expected) or (arity < 0 and args.size < expected)
98
+ raise ArgumentError, "wrong number of arguments for #{klass}#initialize (#{args.size} for #{expected})"
99
+ end
100
+
101
+ klass
102
+ end
103
+
104
+ def klass_loaded?(class_name)
105
+ begin
106
+ !!klass_const(class_name)
107
+ rescue NameError
108
+ false
109
+ end
110
+ end
111
+
112
+ def klass_const(class_name, autoload = false)
113
+ return class_name if class_name.is_a? Class or class_name.is_a? Module
114
+
115
+ klass = Object
116
+
117
+ class_name.split('::').each do |c|
118
+ begin
119
+ klass = klass.const_get(c.to_sym)
120
+ rescue NameError => e
121
+ if autoload
122
+ require_klass( (klass == Object ? c : "#{klass.to_s}::#{c}") )
123
+ redo
124
+ end
125
+ end
126
+ end
127
+
128
+ klass.to_s == class_name ? klass : nil
129
+ end
130
+
131
+ def load_klass(class_name)
132
+ load libpath(*class_name.downcase.split(/::/)) + '.rb'
133
+ end
134
+
135
+ def require_klass(class_name)
136
+ require libpath(*class_name.downcase.split(/::/))
137
+ end
138
+
139
+ def logger
140
+ Emissary::Logger.instance
141
+ end
142
+
143
+ def generate_uuid
144
+ UUID.generate
145
+ end
146
+
147
+ def identity
148
+ @@identity ||= Emissary::Identity.instance
149
+ end
150
+
151
+ # Sets up a line of communication
152
+ #
153
+ def call handler, config, *args
154
+ unless handler.is_a? Class or handler.is_a? Module
155
+ klass_name = 'Emissary::Operator::' + handler.to_s.upcase
156
+ begin
157
+ require_klass klass_name
158
+ rescue LoadError => e
159
+ begin
160
+ require_klass Emissary::EXTERNAL_OPERATORS + '::' + handler.to_s.upcase
161
+ #rescue LoadError
162
+ # raise the original exception if we still haven't found the file
163
+ # raise e
164
+ end
165
+ end
166
+ handler = klass_const klass_name
167
+ end
168
+
169
+ klass = klass_from_handler(Operator, handler, config)
170
+
171
+ operator = klass.new(config, *args)
172
+ operator.validate_config!
173
+ block_given? and yield operator
174
+ operator
175
+ end
176
+
177
+ # Dispatches a message to the agent specified by the message
178
+ #
179
+ def dispatch message, config, *args
180
+ unless message.is_a? Emissary::Message
181
+ raise ArgumentError, "message is not an Emissary::Message"
182
+ end
183
+
184
+ begin
185
+ agent_type = 'Emissary::Agent::' + message.agent.to_s.capitalize
186
+ begin
187
+ require_klass agent_type
188
+ rescue LoadError => e
189
+ begin
190
+ require_klass Emissary::EXTERNAL_AGENTS + '::' + message.agent.to_s.capitalize
191
+ rescue LoadError
192
+ # raise the original exception if we still haven't found the file
193
+ raise e
194
+ end
195
+ end
196
+ rescue Exception => e
197
+ Emissary.logger.error "Dispatcher Error: #{e.class.name}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
198
+ message = message.error(e)
199
+ agent_type = 'Emissary::Agent::Error'
200
+ end
201
+
202
+ handler = klass_const agent_type, true
203
+
204
+ klass = klass_from_handler(Agent, handler, message, config, *args)
205
+
206
+ Emissary.logger.debug " [--] Dispatching message to: #{agent_type}"
207
+ agent = klass.new(message, config, *args)
208
+ block_given? and yield agent
209
+ agent
210
+ end
211
+ end
212
+
213
+ # autoload core object extensions
214
+ Dir[sublib_path('core_ext', '*.rb')].each do |core_extension|
215
+ require core_extension
216
+ end
217
+ end # module Emissary
218
+
219
+ $:.unshift Emissary::LIBPATH
220
+
221
+ [ :errors, :logger, :operator, :agent, :identity, :message, :config, :gem_helper ].each do |sublib|
222
+ require Emissary.sublib_path sublib.to_s
223
+ end
@@ -0,0 +1,61 @@
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 Agent
18
+ attr_reader :name, :message, :method, :config, :operator
19
+ attr_accessor :args
20
+
21
+ def initialize message, config, operator
22
+ @message = message
23
+ @operator = operator
24
+ @config = config
25
+
26
+ @method = message.method.to_sym rescue :__bad_method__
27
+ @args = message.args.clone
28
+
29
+ unless valid_methods.first == :any or valid_methods.include? @method
30
+ raise ArgumentError, "Invalid method '#{@method.to_s}' for agent '#{message.agent}'"
31
+ end
32
+
33
+ post_init
34
+ end
35
+
36
+ def post_init(); end
37
+
38
+ def valid_methods
39
+ raise StandardError, 'Not implemented'
40
+ end
41
+
42
+ def activate
43
+ catch(:skip_implicit_response) do
44
+ result = self.__send__(method, *args)
45
+ response = if not result.kind_of? ::Emissary::Message
46
+ response = message.response
47
+ response.status = [ :ok, (result == true || result.nil? ? 'Succeeded.' : result ) ]
48
+ response
49
+ else
50
+ result
51
+ end
52
+
53
+ send response
54
+ end
55
+ end
56
+
57
+ def send message
58
+ operator.send message
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,163 @@
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 'tempfile'
17
+ require 'fileutils'
18
+
19
+ module Emissary
20
+ class Agent::Emissary < Agent
21
+ INIT_DATA = [
22
+ ::Emissary.identity.name,
23
+ ::Emissary.identity.public_ip,
24
+ ::Emissary.identity.local_ip,
25
+ ::Emissary.identity.instance_id,
26
+ ::Emissary.identity.server_id,
27
+ ::Emissary.identity.cluster_id,
28
+ ::Emissary.identity.account_id,
29
+ ::Emissary.identity.queue_name,
30
+ ::Emissary.version
31
+ ]
32
+
33
+ def valid_methods
34
+ [ :reconfig, :selfupdate, :startup, :shutdown, :initdata, :reinit ]
35
+ end
36
+
37
+ def reconfig new_config
38
+ throw :skip_implicit_response if new_config.strip.empty?
39
+
40
+ if (test(?w, config[:agents][:emissary][:config_path]))
41
+ begin
42
+ ((tmp = Tempfile.new('new_config')) << new_config).flush
43
+ Emissary::Daemon.get_config(tmp.path)
44
+ rescue Exception => e
45
+ resonse = message.response
46
+ response.status_type = :error
47
+ response.status_note = e.message
48
+ return response
49
+ else
50
+ FileUtils.mv tmp.path, config[:agents][:emissary][:config_path]
51
+ # signal a USR1 to our parent, which will cause it to kill the
52
+ # children and restart them after rereading it's configuration
53
+ Process.kill('HUP', config[:parent_pid])
54
+ ensure
55
+ tmp.close
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ def selfupdate version = :latest
62
+ begin
63
+ emissary_gem = ::Emissary::GemHelper.new('emissary')
64
+ request_version, source_uri = emissary_gem.versions(version).flatten
65
+ current_version, _ = emissary_gem.versions(:current).flatten
66
+
67
+ unless not emissary_gem.installable? request_version
68
+ ::Emissary.logger.debug "Emissary SelfUpdate to version '#{request_version.to_s}' requested."
69
+ new_version = emissary_gem.update(request_version, source_uri)
70
+ ::Emissary.logger.debug "Emissary gem updated from '#{::Emissary.version}' to '#{new_version}'"
71
+ else
72
+ ::Emissary.logger.debug " -- SELFUPDATE -- [ VERSION: #{version}]"
73
+ ::Emissary.logger.debug " -- SELFUPDATE -- [ CURRENT_VERSION: #{current_version}]"
74
+ ::Emissary.logger.debug " -- SELFUPDATE -- [ REQUEST_VERSION: #{request_version}]"
75
+
76
+ notice = 'Emissary selfupdate skipped - ' + case true
77
+ when request_version.nil?
78
+ if version == :latest
79
+ "already at latest version."
80
+ else
81
+ "non-existent version '#{version}'"
82
+ end
83
+ when current_version == request_version
84
+ "already at specified version #{version}."
85
+ when current_version > request_version
86
+ "downgrade to version #{request_version} not allowed."
87
+ else
88
+ "unable to update from #{::Emissary.version} to requested version #{request_version}."
89
+ end
90
+ ::Emissary.logger.warn notice
91
+ response = message.response
92
+ response.status_note = notice
93
+ return response
94
+ end
95
+ rescue ::Gem::InstallError, ::Gem::GemNotFoundException => e
96
+ ::Emissary.logger.error "Emissary selfupdate failed with reason: #{e.message}"
97
+ return message.error(e)
98
+ else
99
+ ::Emissary.logger.debug "SelfUpdate: About to detach and run commands"
100
+ with_detached_process('emissary-selfupdate') do
101
+ %x{
102
+ emissary stop;
103
+ sleep 2;
104
+ ps uxa | grep -v grep | grep '(emissary|emop_)' | awk '{ print $2 }' | xargs kill -9;
105
+ sleep 1;
106
+ source /etc/cloudrc;
107
+ emissary start -d;
108
+ }
109
+ end
110
+ ::Emissary.logger.debug "SelfUpdate: Child detached"
111
+ throw :skip_implicit_response
112
+ end
113
+ end
114
+
115
+ def startup
116
+ message = initdata
117
+ message.recipient = config[:startup]
118
+ ::Emissary.logger.notice "Sending Startup message with args: #{message.args.inspect}"
119
+ message
120
+ end
121
+ alias :reinit :startup
122
+
123
+ def initdata
124
+ response = message.response
125
+ response.args = INIT_DATA
126
+ response
127
+ end
128
+
129
+ def shutdown
130
+ message.recipient = config[:shutdown]
131
+ message.args = [
132
+ ::Emissary.identity.server_id,
133
+ ::Emissary.identity.cluster_id,
134
+ ::Emissary.identity.account_id,
135
+ ::Emissary.identity.instance_id
136
+ ]
137
+ ::Emissary.logger.notice "Sending Shutdown message with args: #{message.args.inspect}"
138
+ message
139
+ end
140
+
141
+ private
142
+
143
+ def with_detached_process(name = nil)
144
+ raise Exception, 'Block missing for with_detached_process call' unless block_given?
145
+
146
+ # completely seperate from our parent process
147
+ pid = Kernel.fork do
148
+ Process.setsid
149
+ exit!(0) if fork
150
+ $0 = name unless name.nil?
151
+ Dir.chdir '/'
152
+ ::Emissary.logger.debug "SelfUpdate: Detached and running update command block now..."
153
+ yield
154
+ ::Emissary.logger.debug "SelfUpdate: Finished running update command block - exiting..."
155
+ exit!(0)
156
+ end
157
+
158
+ ::Emissary.logger.debug "SelfUpdate: Detaching child process now."
159
+ #don't worry about the child anymore - it's on it's own
160
+ Process.detach(pid)
161
+ end
162
+ end
163
+ end