dtk-node-agent 0.5.10
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.
- checksums.yaml +15 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +18 -0
- data/README.md +42 -0
- data/bin/dtk-node-agent +16 -0
- data/dtk-node-agent.gemspec +38 -0
- data/lib/config/install.config +12 -0
- data/lib/dtk-node-agent/installer.rb +142 -0
- data/lib/dtk-node-agent/version.rb +3 -0
- data/mcollective_additions/plugins/README.md +1 -0
- data/mcollective_additions/plugins/v1.2/agent/discovery.rb +39 -0
- data/mcollective_additions/plugins/v1.2/agent/get_log_fragment.ddl +15 -0
- data/mcollective_additions/plugins/v1.2/agent/get_log_fragment.rb +79 -0
- data/mcollective_additions/plugins/v1.2/agent/git_access.ddl +9 -0
- data/mcollective_additions/plugins/v1.2/agent/git_access.rb +79 -0
- data/mcollective_additions/plugins/v1.2/agent/netstat.ddl +9 -0
- data/mcollective_additions/plugins/v1.2/agent/netstat.rb +34 -0
- data/mcollective_additions/plugins/v1.2/agent/puppet_apply.ddl +9 -0
- data/mcollective_additions/plugins/v1.2/agent/puppet_apply.rb +630 -0
- data/mcollective_additions/plugins/v1.2/agent/rpcutil.ddl +204 -0
- data/mcollective_additions/plugins/v1.2/agent/rpcutil.rb +101 -0
- data/mcollective_additions/plugins/v1.2/facts/pbuilder_facts.rb +35 -0
- data/mcollective_additions/plugins/v2.2/agent/dev_manager.ddl +9 -0
- data/mcollective_additions/plugins/v2.2/agent/dev_manager.rb +69 -0
- data/mcollective_additions/plugins/v2.2/agent/discovery.rb +39 -0
- data/mcollective_additions/plugins/v2.2/agent/dtk_node_agent_git_client.rb +94 -0
- data/mcollective_additions/plugins/v2.2/agent/execute_tests.ddl +9 -0
- data/mcollective_additions/plugins/v2.2/agent/execute_tests.rb +64 -0
- data/mcollective_additions/plugins/v2.2/agent/get_log_fragment.ddl +15 -0
- data/mcollective_additions/plugins/v2.2/agent/get_log_fragment.rb +79 -0
- data/mcollective_additions/plugins/v2.2/agent/git_access.ddl +9 -0
- data/mcollective_additions/plugins/v2.2/agent/git_access.rb +72 -0
- data/mcollective_additions/plugins/v2.2/agent/netstat.ddl +9 -0
- data/mcollective_additions/plugins/v2.2/agent/netstat.rb +34 -0
- data/mcollective_additions/plugins/v2.2/agent/ps.ddl +9 -0
- data/mcollective_additions/plugins/v2.2/agent/ps.rb +37 -0
- data/mcollective_additions/plugins/v2.2/agent/puppet_apply.ddl +9 -0
- data/mcollective_additions/plugins/v2.2/agent/puppet_apply.rb +633 -0
- data/mcollective_additions/plugins/v2.2/agent/puppet_cancel.ddl +10 -0
- data/mcollective_additions/plugins/v2.2/agent/puppet_cancel.rb +78 -0
- data/mcollective_additions/plugins/v2.2/agent/rpcutil.ddl +204 -0
- data/mcollective_additions/plugins/v2.2/agent/rpcutil.rb +101 -0
- data/mcollective_additions/plugins/v2.2/agent/sync_agent_code.ddl +10 -0
- data/mcollective_additions/plugins/v2.2/agent/sync_agent_code.rb +85 -0
- data/mcollective_additions/plugins/v2.2/agent/tail.ddl +11 -0
- data/mcollective_additions/plugins/v2.2/agent/tail.rb +67 -0
- data/mcollective_additions/plugins/v2.2/connector/r8stomp.rb +238 -0
- data/mcollective_additions/plugins/v2.2/connector/stomp.rb +349 -0
- data/mcollective_additions/plugins/v2.2/connector/stomp_em.rb +191 -0
- data/mcollective_additions/plugins/v2.2/facts/pbuilder_facts.rb +35 -0
- data/mcollective_additions/plugins/v2.2/security/sshkey.ddl +9 -0
- data/mcollective_additions/plugins/v2.2/security/sshkey.rb +362 -0
- data/mcollective_additions/server.cfg +22 -0
- data/puppet_additions/modules/r8/lib/puppet/type/r8_export_file.rb +53 -0
- data/puppet_additions/modules/r8/manifests/export_variable.rb +10 -0
- data/puppet_additions/puppet_lib_base/puppet/indirector/catalog/r8_storeconfig_backend.rb +48 -0
- data/puppet_additions/puppet_lib_base/puppet/indirector/r8_storeconfig_backend.rb +4 -0
- data/puppet_additions/puppet_lib_base/puppet/indirector/resource/r8_storeconfig_backend.rb +72 -0
- data/src/etc/init.d/ec2-run-user-data +95 -0
- data/src/etc/logrotate.d/mcollective +10 -0
- data/src/etc/logrotate.d/puppet +7 -0
- metadata +189 -0
@@ -0,0 +1,11 @@
|
|
1
|
+
metadata :name => "tailing log",
|
2
|
+
:description => "Agent to tail log",
|
3
|
+
:author => "Reactor8",
|
4
|
+
:license => "",
|
5
|
+
:version => "",
|
6
|
+
:url => "",
|
7
|
+
:timeout => 2
|
8
|
+
action "get_log", :description => "returns log content" do
|
9
|
+
end
|
10
|
+
action "grep", :description => "returns log content from multiple nodes" do
|
11
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module MCollective
|
2
|
+
module Agent
|
3
|
+
class Tail < RPC::Agent
|
4
|
+
|
5
|
+
# number of lines that will be returned on first request
|
6
|
+
BATCH_SIZE_OF_LOG = 50
|
7
|
+
|
8
|
+
action "get_log" do
|
9
|
+
begin
|
10
|
+
unless File.exists? request[:log_path]
|
11
|
+
reply[:data] = { :error => "File #{request[:log_path]} not found on given node."}
|
12
|
+
reply[:pbuilderid] = Facts["pbuilderid"]
|
13
|
+
reply[:status] = :ok
|
14
|
+
return
|
15
|
+
end
|
16
|
+
|
17
|
+
# returns total number of lines in file, one is to start next iteration with new line
|
18
|
+
last_line = `wc -l #{request[:log_path]} | awk '{print $1}'`.to_i + 1
|
19
|
+
# if there is start line from CLI request we use it, if not we take last BATCH_SIZE_OF_LOG lines
|
20
|
+
if request[:start_line].empty?
|
21
|
+
# If BATCH_SIZE_OF_LOG is bigger than last_line, then start line will be 0
|
22
|
+
start_line = (last_line > BATCH_SIZE_OF_LOG) ? last_line-BATCH_SIZE_OF_LOG : 0
|
23
|
+
else
|
24
|
+
start_line = request[:start_line]
|
25
|
+
end
|
26
|
+
|
27
|
+
# returns needed lines
|
28
|
+
if (request[:grep_option].nil? || request[:grep_option].empty?)
|
29
|
+
output = `tail -n +#{start_line} #{request[:log_path]}`
|
30
|
+
else
|
31
|
+
output = `tail -n +#{start_line} #{request[:log_path]} | grep #{request[:grep_option]}`
|
32
|
+
end
|
33
|
+
|
34
|
+
reply[:data] = { :output => output, :last_line => last_line }
|
35
|
+
reply[:pbuilderid] = Facts["pbuilderid"]
|
36
|
+
reply[:status] = :ok
|
37
|
+
rescue Exception => e
|
38
|
+
Log.error e
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
action "grep" do
|
43
|
+
begin
|
44
|
+
unless File.exists? request[:log_path]
|
45
|
+
reply[:data] = { :error => "File #{request[:log_path]} not found on given node."}
|
46
|
+
reply[:pbuilderid] = Facts["pbuilderid"]
|
47
|
+
reply[:status] = :ok
|
48
|
+
return
|
49
|
+
end
|
50
|
+
|
51
|
+
# returns needed lines
|
52
|
+
if (request[:stop_on_first_match].empty? || request[:stop_on_first_match].nil? || request[:stop_on_first_match].eql?('false'))
|
53
|
+
output = `more #{request[:log_path]} | grep #{request[:grep_pattern]}`
|
54
|
+
else
|
55
|
+
output = `more #{request[:log_path]} | grep #{request[:grep_pattern]} | tail -1`
|
56
|
+
end
|
57
|
+
reply[:data] = { :output => output}
|
58
|
+
reply[:pbuilderid] = Facts["pbuilderid"]
|
59
|
+
reply[:status] = :ok
|
60
|
+
rescue Exception => e
|
61
|
+
Log.error e
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,238 @@
|
|
1
|
+
require 'stomp'
|
2
|
+
|
3
|
+
module MCollective
|
4
|
+
module Connector
|
5
|
+
# Handles sending and receiving messages over the Stomp protocol
|
6
|
+
#
|
7
|
+
# This plugin supports version 1.1 or 1.1.6 and newer of the Stomp rubygem
|
8
|
+
# the versions between those had multi threading issues.
|
9
|
+
#
|
10
|
+
# For all versions you can configure it as follows:
|
11
|
+
#
|
12
|
+
# connector = stomp
|
13
|
+
# plugin.stomp.host = stomp.your.net
|
14
|
+
# plugin.stomp.port = 6163
|
15
|
+
# plugin.stomp.user = you
|
16
|
+
# plugin.stomp.password = secret
|
17
|
+
#
|
18
|
+
# All of these can be overriden per user using environment variables:
|
19
|
+
#
|
20
|
+
# STOMP_SERVER, STOMP_PORT, STOMP_USER, STOMP_PASSWORD
|
21
|
+
#
|
22
|
+
# Version 1.1.6 onward support supplying multiple connections and it will
|
23
|
+
# do failover between these servers, you can configure it as follows:
|
24
|
+
#
|
25
|
+
# connector = stomp
|
26
|
+
# plugin.stomp.pool.size = 2
|
27
|
+
#
|
28
|
+
# plugin.stomp.pool.host1 = stomp1.your.net
|
29
|
+
# plugin.stomp.pool.port1 = 6163
|
30
|
+
# plugin.stomp.pool.user1 = you
|
31
|
+
# plugin.stomp.pool.password1 = secret
|
32
|
+
# plugin.stomp.pool.ssl1 = true
|
33
|
+
#
|
34
|
+
# plugin.stomp.pool.host2 = stomp2.your.net
|
35
|
+
# plugin.stomp.pool.port2 = 6163
|
36
|
+
# plugin.stomp.pool.user2 = you
|
37
|
+
# plugin.stomp.pool.password2 = secret
|
38
|
+
# plugin.stomp.pool.ssl2 = false
|
39
|
+
#
|
40
|
+
# Using this method you can supply just STOMP_USER and STOMP_PASSWORD
|
41
|
+
# you have to supply the hostname for each pool member in the config.
|
42
|
+
# The port will default to 6163 if not specified.
|
43
|
+
#
|
44
|
+
# In addition you can set the following options but only when using
|
45
|
+
# pooled configuration:
|
46
|
+
#
|
47
|
+
# plugin.stomp.pool.initial_reconnect_delay = 0.01
|
48
|
+
# plugin.stomp.pool.max_reconnect_delay = 30.0
|
49
|
+
# plugin.stomp.pool.use_exponential_back_off = true
|
50
|
+
# plugin.stomp.pool.back_off_multiplier = 2
|
51
|
+
# plugin.stomp.pool.max_reconnect_attempts = 0
|
52
|
+
# plugin.stomp.pool.randomize = false
|
53
|
+
# plugin.stomp.pool.timeout = -1
|
54
|
+
#
|
55
|
+
# For versions of ActiveMQ that supports message priorities
|
56
|
+
# you can set a priority, this will cause a "priority" header
|
57
|
+
# to be emitted if present:
|
58
|
+
#
|
59
|
+
# plugin.stomp.priority = 4
|
60
|
+
#
|
61
|
+
class Stomp<Base
|
62
|
+
attr_reader :connection
|
63
|
+
|
64
|
+
#modifications so connect at initialization and there is not multiple connections per thread
|
65
|
+
def initialize
|
66
|
+
@config = Config.instance
|
67
|
+
@subscriptions = []
|
68
|
+
###BEGIN R8 CUSTOMIZATION
|
69
|
+
if Thread.current[:stomp_client]
|
70
|
+
@connection = Thread.current[:stomp_client]
|
71
|
+
else
|
72
|
+
connect()
|
73
|
+
Thread.current[:stomp_client] = @connection
|
74
|
+
end
|
75
|
+
###END R8 CUSTOMIZATION
|
76
|
+
end
|
77
|
+
|
78
|
+
def disconnect
|
79
|
+
Log.debug("Disconnecting from Stomp")
|
80
|
+
@connection.disconnect
|
81
|
+
end
|
82
|
+
|
83
|
+
# Connects to the Stomp middleware
|
84
|
+
def connect
|
85
|
+
if @connection
|
86
|
+
Log.debug("Already connection, not re-initializing connection")
|
87
|
+
return
|
88
|
+
end
|
89
|
+
begin
|
90
|
+
host = nil
|
91
|
+
port = nil
|
92
|
+
user = nil
|
93
|
+
password = nil
|
94
|
+
@base64 = get_bool_option("stomp.base64", false)
|
95
|
+
@msgpriority = get_option("stomp.priority", 0).to_i
|
96
|
+
|
97
|
+
# Maintain backward compat for older stomps
|
98
|
+
unless @config.pluginconf.include?("stomp.pool.size")
|
99
|
+
host = get_env_or_option("STOMP_SERVER", "stomp.host")
|
100
|
+
port = get_env_or_option("STOMP_PORT", "stomp.port", 6163).to_i
|
101
|
+
user = get_env_or_option("STOMP_USER", "stomp.user")
|
102
|
+
password = get_env_or_option("STOMP_PASSWORD", "stomp.password")
|
103
|
+
|
104
|
+
Log.debug("Connecting to #{host}:#{port}")
|
105
|
+
@connection = ::Stomp::Connection.new(user, password, host, port, true)
|
106
|
+
else
|
107
|
+
pools = @config.pluginconf["stomp.pool.size"].to_i
|
108
|
+
hosts = []
|
109
|
+
|
110
|
+
1.upto(pools) do |poolnum|
|
111
|
+
host = {}
|
112
|
+
|
113
|
+
host[:host] = get_option("stomp.pool.host#{poolnum}")
|
114
|
+
host[:port] = get_option("stomp.pool.port#{poolnum}", 6163).to_i
|
115
|
+
host[:login] = get_env_or_option("STOMP_USER", "stomp.pool.user#{poolnum}")
|
116
|
+
host[:passcode] = get_env_or_option("STOMP_PASSWORD", "stomp.pool.password#{poolnum}")
|
117
|
+
host[:ssl] = get_bool_option("stomp.pool.ssl#{poolnum}", false)
|
118
|
+
|
119
|
+
Log.debug("Adding #{host[:host]}:#{host[:port]} to the connection pool")
|
120
|
+
hosts << host
|
121
|
+
end
|
122
|
+
|
123
|
+
raise "No hosts found for the STOMP connection pool" if hosts.size == 0
|
124
|
+
|
125
|
+
connection = {:hosts => hosts}
|
126
|
+
|
127
|
+
# Various STOMP gem options, defaults here matches defaults for 1.1.6 the meaning of
|
128
|
+
# these can be guessed, the documentation isn't clear
|
129
|
+
connection[:initial_reconnect_delay] = get_option("stomp.pool.initial_reconnect_delay", 0.01).to_f
|
130
|
+
connection[:max_reconnect_delay] = get_option("stomp.pool.max_reconnect_delay", 30.0).to_f
|
131
|
+
connection[:use_exponential_back_off] = get_bool_option("stomp.pool.use_exponential_back_off", true)
|
132
|
+
connection[:back_off_multiplier] = get_bool_option("stomp.pool.back_off_multiplier", 2).to_i
|
133
|
+
connection[:max_reconnect_attempts] = get_option("stomp.pool.max_reconnect_attempts", 0)
|
134
|
+
connection[:randomize] = get_bool_option("stomp.pool.randomize", false)
|
135
|
+
connection[:backup] = get_bool_option("stomp.pool.backup", false)
|
136
|
+
connection[:timeout] = get_option("stomp.pool.timeout", -1).to_i
|
137
|
+
|
138
|
+
@connection = ::Stomp::Connection.new(connection)
|
139
|
+
end
|
140
|
+
rescue Exception => e
|
141
|
+
raise("Could not connect to Stomp Server: #{e}")
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Receives a message from the Stomp connection
|
146
|
+
def receive
|
147
|
+
Log.debug("Waiting for a message from Stomp")
|
148
|
+
msg = @connection.receive
|
149
|
+
|
150
|
+
# STOMP puts the payload in the body variable, pass that
|
151
|
+
# into the payload of MCollective::Request and discard all the
|
152
|
+
# other headers etc that stomp provides
|
153
|
+
if @base64
|
154
|
+
Request.new(SSL.base64_decode(msg.body))
|
155
|
+
else
|
156
|
+
Request.new(msg.body)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Sends a message to the Stomp connection
|
161
|
+
def send(target, msg)
|
162
|
+
Log.debug("Sending a message to Stomp target '#{target}'")
|
163
|
+
|
164
|
+
msg = SSL.base64_encode(msg) if @base64
|
165
|
+
|
166
|
+
# deal with deprecation warnings in newer stomp gems
|
167
|
+
if @connection.respond_to?("publish")
|
168
|
+
@connection.publish(target, msg, msgheaders)
|
169
|
+
else
|
170
|
+
@connection.send(target, msg, msgheaders)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Subscribe to a topic or queue
|
175
|
+
def subscribe(source)
|
176
|
+
unless @subscriptions.include?(source)
|
177
|
+
Log.debug("Subscribing to #{source}")
|
178
|
+
@connection.subscribe(source)
|
179
|
+
@subscriptions << source
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Subscribe to a topic or queue
|
184
|
+
def unsubscribe(source)
|
185
|
+
Log.debug("Unsubscribing from #{source}")
|
186
|
+
@connection.unsubscribe(source)
|
187
|
+
@subscriptions.delete(source)
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
def msgheaders
|
192
|
+
headers = {}
|
193
|
+
headers = {"priority" => @msgpriority} if (@msgpriority and @msgpriority > 0)
|
194
|
+
|
195
|
+
return headers
|
196
|
+
end
|
197
|
+
|
198
|
+
# looks in the environment first then in the config file
|
199
|
+
# for a specific option, accepts an optional default.
|
200
|
+
#
|
201
|
+
# raises an exception when it cant find a value anywhere
|
202
|
+
def get_env_or_option(env, opt, default=nil)
|
203
|
+
return ENV[env] if ENV.include?(env)
|
204
|
+
return @config.pluginconf[opt] if @config.pluginconf.include?(opt)
|
205
|
+
return default if default
|
206
|
+
|
207
|
+
raise("No #{env} environment or plugin.#{opt} configuration option given")
|
208
|
+
end
|
209
|
+
|
210
|
+
# looks for a config option, accepts an optional default
|
211
|
+
#
|
212
|
+
# raises an exception when it cant find a value anywhere
|
213
|
+
def get_option(opt, default=nil)
|
214
|
+
return @config.pluginconf[opt] if @config.pluginconf.include?(opt)
|
215
|
+
return default if default
|
216
|
+
|
217
|
+
raise("No plugin.#{opt} configuration option given")
|
218
|
+
end
|
219
|
+
|
220
|
+
# gets a boolean option from the config, supports y/n/true/false/1/0
|
221
|
+
def get_bool_option(opt, default)
|
222
|
+
return default unless @config.pluginconf.include?(opt)
|
223
|
+
|
224
|
+
val = @config.pluginconf[opt]
|
225
|
+
|
226
|
+
if val =~ /^1|yes|true/
|
227
|
+
return true
|
228
|
+
elsif val =~ /^0|no|false/
|
229
|
+
return false
|
230
|
+
else
|
231
|
+
return default
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# vi:tabstop=4:expandtab:ai
|
@@ -0,0 +1,349 @@
|
|
1
|
+
require 'stomp'
|
2
|
+
|
3
|
+
module MCollective
|
4
|
+
module Connector
|
5
|
+
# Handles sending and receiving messages over the Stomp protocol
|
6
|
+
#
|
7
|
+
# This plugin supports version 1.1 or 1.1.6 and newer of the Stomp rubygem
|
8
|
+
# the versions between those had multi threading issues.
|
9
|
+
#
|
10
|
+
# For all versions you can configure it as follows:
|
11
|
+
#
|
12
|
+
# connector = stomp
|
13
|
+
# plugin.stomp.host = stomp.your.net
|
14
|
+
# plugin.stomp.port = 6163
|
15
|
+
# plugin.stomp.user = you
|
16
|
+
# plugin.stomp.password = secret
|
17
|
+
#
|
18
|
+
# All of these can be overriden per user using environment variables:
|
19
|
+
#
|
20
|
+
# STOMP_SERVER, STOMP_PORT, STOMP_USER, STOMP_PASSWORD
|
21
|
+
#
|
22
|
+
# Version 1.1.6 onward support supplying multiple connections and it will
|
23
|
+
# do failover between these servers, you can configure it as follows:
|
24
|
+
#
|
25
|
+
# connector = stomp
|
26
|
+
# plugin.stomp.pool.size = 2
|
27
|
+
#
|
28
|
+
# plugin.stomp.pool.host1 = stomp1.your.net
|
29
|
+
# plugin.stomp.pool.port1 = 6163
|
30
|
+
# plugin.stomp.pool.user1 = you
|
31
|
+
# plugin.stomp.pool.password1 = secret
|
32
|
+
# plugin.stomp.pool.ssl1 = true
|
33
|
+
#
|
34
|
+
# plugin.stomp.pool.host2 = stomp2.your.net
|
35
|
+
# plugin.stomp.pool.port2 = 6163
|
36
|
+
# plugin.stomp.pool.user2 = you
|
37
|
+
# plugin.stomp.pool.password2 = secret
|
38
|
+
# plugin.stomp.pool.ssl2 = false
|
39
|
+
#
|
40
|
+
# Using this method you can supply just STOMP_USER and STOMP_PASSWORD
|
41
|
+
# you have to supply the hostname for each pool member in the config.
|
42
|
+
# The port will default to 6163 if not specified.
|
43
|
+
#
|
44
|
+
# In addition you can set the following options but only when using
|
45
|
+
# pooled configuration:
|
46
|
+
#
|
47
|
+
# plugin.stomp.pool.initial_reconnect_delay = 0.01
|
48
|
+
# plugin.stomp.pool.max_reconnect_delay = 30.0
|
49
|
+
# plugin.stomp.pool.use_exponential_back_off = true
|
50
|
+
# plugin.stomp.pool.back_off_multiplier = 2
|
51
|
+
# plugin.stomp.pool.max_reconnect_attempts = 0
|
52
|
+
# plugin.stomp.pool.randomize = false
|
53
|
+
# plugin.stomp.pool.timeout = -1
|
54
|
+
#
|
55
|
+
# You can set the initial connetion timeout - this is when your stomp server is simply
|
56
|
+
# unreachable - after which it would failover to the next in the pool:
|
57
|
+
#
|
58
|
+
# plugin.stomp.pool.connect_timeout = 30
|
59
|
+
#
|
60
|
+
# For versions of ActiveMQ that supports message priorities
|
61
|
+
# you can set a priority, this will cause a "priority" header
|
62
|
+
# to be emitted if present:
|
63
|
+
#
|
64
|
+
# plugin.stomp.priority = 4
|
65
|
+
#
|
66
|
+
class Stomp<Base
|
67
|
+
# Older stomp gems do not have these error classes, in order to be able to
|
68
|
+
# handle these exceptions if they are present and still support older gems
|
69
|
+
# we're assigning the constants to a dummy exception that will never be thrown
|
70
|
+
# by us. End result is that the code catching these exceptions become noops on
|
71
|
+
# older gems but on newer ones they become usable and handle those new errors
|
72
|
+
# intelligently
|
73
|
+
class DummyError<RuntimeError; end
|
74
|
+
|
75
|
+
::Stomp::Error = DummyError unless defined?(::Stomp::Error)
|
76
|
+
::Stomp::Error::NoCurrentConnection = DummyError unless defined?(::Stomp::Error::NoCurrentConnection)
|
77
|
+
::Stomp::Error::DuplicateSubscription = DummyError unless defined?(::Stomp::Error::DuplicateSubscription)
|
78
|
+
|
79
|
+
# Class for Stomp 1.1.9 callback based logging
|
80
|
+
class EventLogger
|
81
|
+
def on_connecting(params=nil)
|
82
|
+
Log.info("Connection attempt %d to %s" % [params[:cur_conattempts], stomp_url(params)])
|
83
|
+
rescue
|
84
|
+
end
|
85
|
+
|
86
|
+
def on_connected(params=nil)
|
87
|
+
Log.info("Conncted to #{stomp_url(params)}")
|
88
|
+
rescue
|
89
|
+
end
|
90
|
+
|
91
|
+
def on_disconnect(params=nil)
|
92
|
+
Log.info("Disconnected from #{stomp_url(params)}")
|
93
|
+
rescue
|
94
|
+
end
|
95
|
+
|
96
|
+
def on_connectfail(params=nil)
|
97
|
+
Log.info("Connection to #{stomp_url(params)} failed on attempt #{params[:cur_conattempts]}")
|
98
|
+
rescue
|
99
|
+
end
|
100
|
+
|
101
|
+
def on_miscerr(params, errstr)
|
102
|
+
Log.error("Unexpected error on connection #{stomp_url(params)}: #{errstr}")
|
103
|
+
rescue
|
104
|
+
end
|
105
|
+
|
106
|
+
def on_ssl_connecting(params)
|
107
|
+
Log.info("Performing SSL connection to #{stomp_url(params)}")
|
108
|
+
rescue
|
109
|
+
end
|
110
|
+
|
111
|
+
def on_ssl_connected(params)
|
112
|
+
Log.info("Connected SSL socket #{stomp_url(params)}")
|
113
|
+
rescue
|
114
|
+
end
|
115
|
+
|
116
|
+
def stomp_url(params)
|
117
|
+
"%s://%s@%s:%d" % [ params[:cur_ssl] ? "stomp+ssl" : "stomp", params[:cur_login], params[:cur_host], params[:cur_port]]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
attr_reader :connection
|
122
|
+
|
123
|
+
def initialize
|
124
|
+
Log.info("MCollective 2.2.x will be the last to fully support the 'stomp' connector, please migrate to the 'activemq' or 'rabbitmq' connector")
|
125
|
+
|
126
|
+
@config = Config.instance
|
127
|
+
@subscriptions = []
|
128
|
+
end
|
129
|
+
|
130
|
+
# Connects to the Stomp middleware
|
131
|
+
def connect(connector = ::Stomp::Connection)
|
132
|
+
if @connection
|
133
|
+
Log.debug("Already connection, not re-initializing connection")
|
134
|
+
return
|
135
|
+
end
|
136
|
+
|
137
|
+
begin
|
138
|
+
host = nil
|
139
|
+
port = nil
|
140
|
+
user = nil
|
141
|
+
password = nil
|
142
|
+
@base64 = get_bool_option("stomp.base64", false)
|
143
|
+
@msgpriority = get_option("stomp.priority", 0).to_i
|
144
|
+
|
145
|
+
# Maintain backward compat for older stomps
|
146
|
+
unless @config.pluginconf.include?("stomp.pool.size")
|
147
|
+
host = get_env_or_option("STOMP_SERVER", "stomp.host")
|
148
|
+
port = get_env_or_option("STOMP_PORT", "stomp.port", 6163).to_i
|
149
|
+
user = get_env_or_option("STOMP_USER", "stomp.user")
|
150
|
+
password = get_env_or_option("STOMP_PASSWORD", "stomp.password")
|
151
|
+
|
152
|
+
Log.debug("Connecting to #{host}:#{port}")
|
153
|
+
@connection = connector.new(user, password, host, port, true)
|
154
|
+
else
|
155
|
+
pools = @config.pluginconf["stomp.pool.size"].to_i
|
156
|
+
hosts = []
|
157
|
+
|
158
|
+
1.upto(pools) do |poolnum|
|
159
|
+
host = {}
|
160
|
+
|
161
|
+
host[:host] = get_option("stomp.pool.host#{poolnum}")
|
162
|
+
host[:port] = get_option("stomp.pool.port#{poolnum}", 6163).to_i
|
163
|
+
host[:login] = get_env_or_option("STOMP_USER", "stomp.pool.user#{poolnum}")
|
164
|
+
host[:passcode] = get_env_or_option("STOMP_PASSWORD", "stomp.pool.password#{poolnum}")
|
165
|
+
host[:ssl] = get_bool_option("stomp.pool.ssl#{poolnum}", false)
|
166
|
+
|
167
|
+
Log.debug("Adding #{host[:host]}:#{host[:port]} to the connection pool")
|
168
|
+
hosts << host
|
169
|
+
end
|
170
|
+
|
171
|
+
raise "No hosts found for the STOMP connection pool" if hosts.size == 0
|
172
|
+
|
173
|
+
connection = {:hosts => hosts}
|
174
|
+
|
175
|
+
# Various STOMP gem options, defaults here matches defaults for 1.1.6 the meaning of
|
176
|
+
# these can be guessed, the documentation isn't clear
|
177
|
+
connection[:initial_reconnect_delay] = get_option("stomp.pool.initial_reconnect_delay", 0.01).to_f
|
178
|
+
connection[:max_reconnect_delay] = get_option("stomp.pool.max_reconnect_delay", 30.0).to_f
|
179
|
+
connection[:use_exponential_back_off] = get_bool_option("stomp.pool.use_exponential_back_off", true)
|
180
|
+
connection[:back_off_multiplier] = get_bool_option("stomp.pool.back_off_multiplier", 2).to_i
|
181
|
+
connection[:max_reconnect_attempts] = get_option("stomp.pool.max_reconnect_attempts", 0).to_i
|
182
|
+
connection[:randomize] = get_bool_option("stomp.pool.randomize", false)
|
183
|
+
connection[:backup] = get_bool_option("stomp.pool.backup", false)
|
184
|
+
connection[:timeout] = get_option("stomp.pool.timeout", -1).to_i
|
185
|
+
connection[:connect_timeout] = Integer(get_option("stomp.pool.connect_timeout", 30))
|
186
|
+
connection[:reliable] = true
|
187
|
+
|
188
|
+
connection[:logger] = EventLogger.new
|
189
|
+
|
190
|
+
@connection = connector.new(connection)
|
191
|
+
end
|
192
|
+
rescue Exception => e
|
193
|
+
raise("Could not connect to Stomp Server: #{e}")
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Receives a message from the Stomp connection
|
198
|
+
def receive
|
199
|
+
Log.debug("Waiting for a message from Stomp")
|
200
|
+
|
201
|
+
# When the Stomp library > 1.2.0 is mid reconnecting due to its reliable connection
|
202
|
+
# handling it sets the connection to closed. If we happen to be receiving at just
|
203
|
+
# that time we will get an exception warning about the closed connection so handling
|
204
|
+
# that here with a sleep and a retry.
|
205
|
+
begin
|
206
|
+
msg = @connection.receive
|
207
|
+
rescue ::Stomp::Error::NoCurrentConnection
|
208
|
+
sleep 1
|
209
|
+
retry
|
210
|
+
end
|
211
|
+
|
212
|
+
Message.new(msg.body, msg, :base64 => @base64, :headers => msg.headers)
|
213
|
+
end
|
214
|
+
|
215
|
+
# Sends a message to the Stomp connection
|
216
|
+
def publish(msg)
|
217
|
+
msg.base64_encode! if @base64
|
218
|
+
|
219
|
+
raise "Cannot set specific reply to targets with the STOMP plugin" if msg.reply_to
|
220
|
+
|
221
|
+
if msg.type == :direct_request
|
222
|
+
msg.discovered_hosts.each do |node|
|
223
|
+
target = make_target(msg.agent, msg.type, msg.collective, node)
|
224
|
+
|
225
|
+
Log.debug("Sending a direct message to STOMP target '#{target}'")
|
226
|
+
|
227
|
+
publish_msg(target, msg.payload)
|
228
|
+
end
|
229
|
+
else
|
230
|
+
target = make_target(msg.agent, msg.type, msg.collective)
|
231
|
+
|
232
|
+
Log.debug("Sending a broadcast message to STOMP target '#{target}'")
|
233
|
+
|
234
|
+
publish_msg(target, msg.payload)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# Subscribe to a topic or queue
|
239
|
+
def subscribe(agent, type, collective)
|
240
|
+
source = make_target(agent, type, collective)
|
241
|
+
|
242
|
+
unless @subscriptions.include?(source)
|
243
|
+
Log.debug("Subscribing to #{source}")
|
244
|
+
@connection.subscribe(source)
|
245
|
+
@subscriptions << source
|
246
|
+
end
|
247
|
+
rescue ::Stomp::Error::DuplicateSubscription
|
248
|
+
Log.debug("Received subscription for #{source[:name]} but already had a subscription, ignoring")
|
249
|
+
end
|
250
|
+
|
251
|
+
# Actually sends the message to the middleware
|
252
|
+
def publish_msg(target, msg)
|
253
|
+
# deal with deprecation warnings in newer stomp gems
|
254
|
+
if @connection.respond_to?("publish")
|
255
|
+
@connection.publish(target, msg, msgheaders)
|
256
|
+
else
|
257
|
+
@connection.send(target, msg, msgheaders)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# Subscribe to a topic or queue
|
262
|
+
def unsubscribe(agent, type, collective)
|
263
|
+
source = make_target(agent, type, collective)
|
264
|
+
|
265
|
+
Log.debug("Unsubscribing from #{source}")
|
266
|
+
@connection.unsubscribe(source)
|
267
|
+
@subscriptions.delete(source)
|
268
|
+
end
|
269
|
+
|
270
|
+
# Disconnects from the Stomp connection
|
271
|
+
def disconnect
|
272
|
+
Log.debug("Disconnecting from Stomp")
|
273
|
+
@connection.disconnect
|
274
|
+
end
|
275
|
+
|
276
|
+
def msgheaders
|
277
|
+
headers = {}
|
278
|
+
headers = {"priority" => @msgpriority} if @msgpriority > 0
|
279
|
+
|
280
|
+
return headers
|
281
|
+
end
|
282
|
+
|
283
|
+
# looks in the environment first then in the config file
|
284
|
+
# for a specific option, accepts an optional default.
|
285
|
+
#
|
286
|
+
# raises an exception when it cant find a value anywhere
|
287
|
+
def get_env_or_option(env, opt, default=nil)
|
288
|
+
return ENV[env] if ENV.include?(env)
|
289
|
+
return @config.pluginconf[opt] if @config.pluginconf.include?(opt)
|
290
|
+
return default if default
|
291
|
+
|
292
|
+
raise("No #{env} environment or plugin.#{opt} configuration option given")
|
293
|
+
end
|
294
|
+
|
295
|
+
# looks for a config option, accepts an optional default
|
296
|
+
#
|
297
|
+
# raises an exception when it cant find a value anywhere
|
298
|
+
def get_option(opt, default=nil)
|
299
|
+
return @config.pluginconf[opt] if @config.pluginconf.include?(opt)
|
300
|
+
return default if default
|
301
|
+
|
302
|
+
raise("No plugin.#{opt} configuration option given")
|
303
|
+
end
|
304
|
+
|
305
|
+
# gets a boolean option from the config, supports y/n/true/false/1/0
|
306
|
+
def get_bool_option(opt, default)
|
307
|
+
return default unless @config.pluginconf.include?(opt)
|
308
|
+
|
309
|
+
val = @config.pluginconf[opt]
|
310
|
+
|
311
|
+
if val =~ /^1|yes|true/
|
312
|
+
return true
|
313
|
+
elsif val =~ /^0|no|false/
|
314
|
+
return false
|
315
|
+
else
|
316
|
+
return default
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def make_target(agent, type, collective, target_node=nil)
|
321
|
+
raise("Unknown target type #{type}") unless [:directed, :broadcast, :reply, :request, :direct_request].include?(type)
|
322
|
+
raise("Unknown collective '#{collective}' known collectives are '#{@config.collectives.join ', '}'") unless @config.collectives.include?(collective)
|
323
|
+
|
324
|
+
prefix = @config.topicprefix
|
325
|
+
|
326
|
+
case type
|
327
|
+
when :reply
|
328
|
+
suffix = :reply
|
329
|
+
when :broadcast
|
330
|
+
suffix = :command
|
331
|
+
when :request
|
332
|
+
suffix = :command
|
333
|
+
when :direct_request
|
334
|
+
agent = nil
|
335
|
+
prefix = @config.queueprefix
|
336
|
+
suffix = Digest::MD5.hexdigest(target_node)
|
337
|
+
when :directed
|
338
|
+
agent = nil
|
339
|
+
prefix = @config.queueprefix
|
340
|
+
# use a md5 since hostnames might have illegal characters that
|
341
|
+
# the middleware dont understand
|
342
|
+
suffix = Digest::MD5.hexdigest(@config.identity)
|
343
|
+
end
|
344
|
+
|
345
|
+
["#{prefix}#{collective}", agent, suffix].compact.join(@config.topicsep)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|