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