choria-mcorpc-support 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +7 -0
  2. data/bin/mco +64 -0
  3. data/lib/mcollective.rb +63 -0
  4. data/lib/mcollective/agent.rb +5 -0
  5. data/lib/mcollective/agents.rb +149 -0
  6. data/lib/mcollective/aggregate.rb +85 -0
  7. data/lib/mcollective/aggregate/average.ddl +33 -0
  8. data/lib/mcollective/aggregate/average.rb +29 -0
  9. data/lib/mcollective/aggregate/base.rb +40 -0
  10. data/lib/mcollective/aggregate/result.rb +9 -0
  11. data/lib/mcollective/aggregate/result/base.rb +25 -0
  12. data/lib/mcollective/aggregate/result/collection_result.rb +19 -0
  13. data/lib/mcollective/aggregate/result/numeric_result.rb +13 -0
  14. data/lib/mcollective/aggregate/sum.ddl +33 -0
  15. data/lib/mcollective/aggregate/sum.rb +18 -0
  16. data/lib/mcollective/aggregate/summary.ddl +33 -0
  17. data/lib/mcollective/aggregate/summary.rb +53 -0
  18. data/lib/mcollective/application.rb +365 -0
  19. data/lib/mcollective/application/completion.rb +104 -0
  20. data/lib/mcollective/application/describe_filter.rb +87 -0
  21. data/lib/mcollective/application/facts.rb +62 -0
  22. data/lib/mcollective/application/find.rb +23 -0
  23. data/lib/mcollective/application/help.rb +28 -0
  24. data/lib/mcollective/application/inventory.rb +344 -0
  25. data/lib/mcollective/application/ping.rb +82 -0
  26. data/lib/mcollective/application/plugin.rb +369 -0
  27. data/lib/mcollective/application/rpc.rb +111 -0
  28. data/lib/mcollective/applications.rb +134 -0
  29. data/lib/mcollective/cache.rb +145 -0
  30. data/lib/mcollective/client.rb +353 -0
  31. data/lib/mcollective/config.rb +245 -0
  32. data/lib/mcollective/connector.rb +18 -0
  33. data/lib/mcollective/connector/base.rb +26 -0
  34. data/lib/mcollective/data.rb +91 -0
  35. data/lib/mcollective/data/agent_data.ddl +22 -0
  36. data/lib/mcollective/data/agent_data.rb +17 -0
  37. data/lib/mcollective/data/base.rb +67 -0
  38. data/lib/mcollective/data/collective_data.ddl +20 -0
  39. data/lib/mcollective/data/collective_data.rb +9 -0
  40. data/lib/mcollective/data/fact_data.ddl +28 -0
  41. data/lib/mcollective/data/fact_data.rb +55 -0
  42. data/lib/mcollective/data/fstat_data.ddl +89 -0
  43. data/lib/mcollective/data/fstat_data.rb +56 -0
  44. data/lib/mcollective/data/result.rb +45 -0
  45. data/lib/mcollective/ddl.rb +113 -0
  46. data/lib/mcollective/ddl/agentddl.rb +253 -0
  47. data/lib/mcollective/ddl/base.rb +217 -0
  48. data/lib/mcollective/ddl/dataddl.rb +56 -0
  49. data/lib/mcollective/ddl/discoveryddl.rb +52 -0
  50. data/lib/mcollective/ddl/validatorddl.rb +6 -0
  51. data/lib/mcollective/discovery.rb +143 -0
  52. data/lib/mcollective/discovery/flatfile.ddl +11 -0
  53. data/lib/mcollective/discovery/flatfile.rb +48 -0
  54. data/lib/mcollective/discovery/mc.ddl +11 -0
  55. data/lib/mcollective/discovery/mc.rb +30 -0
  56. data/lib/mcollective/discovery/stdin.ddl +11 -0
  57. data/lib/mcollective/discovery/stdin.rb +68 -0
  58. data/lib/mcollective/exceptions.rb +28 -0
  59. data/lib/mcollective/facts.rb +39 -0
  60. data/lib/mcollective/facts/base.rb +100 -0
  61. data/lib/mcollective/facts/yaml_facts.rb +65 -0
  62. data/lib/mcollective/generators.rb +7 -0
  63. data/lib/mcollective/generators/agent_generator.rb +51 -0
  64. data/lib/mcollective/generators/base.rb +46 -0
  65. data/lib/mcollective/generators/data_generator.rb +51 -0
  66. data/lib/mcollective/generators/templates/action_snippet.erb +13 -0
  67. data/lib/mcollective/generators/templates/data_input_snippet.erb +7 -0
  68. data/lib/mcollective/generators/templates/ddl.erb +8 -0
  69. data/lib/mcollective/generators/templates/plugin.erb +7 -0
  70. data/lib/mcollective/log.rb +118 -0
  71. data/lib/mcollective/logger.rb +5 -0
  72. data/lib/mcollective/logger/base.rb +77 -0
  73. data/lib/mcollective/logger/console_logger.rb +61 -0
  74. data/lib/mcollective/logger/file_logger.rb +53 -0
  75. data/lib/mcollective/logger/syslog_logger.rb +53 -0
  76. data/lib/mcollective/matcher.rb +224 -0
  77. data/lib/mcollective/matcher/parser.rb +128 -0
  78. data/lib/mcollective/matcher/scanner.rb +241 -0
  79. data/lib/mcollective/message.rb +248 -0
  80. data/lib/mcollective/monkey_patches.rb +152 -0
  81. data/lib/mcollective/optionparser.rb +197 -0
  82. data/lib/mcollective/pluginmanager.rb +180 -0
  83. data/lib/mcollective/pluginpackager.rb +98 -0
  84. data/lib/mcollective/pluginpackager/agent_definition.rb +94 -0
  85. data/lib/mcollective/pluginpackager/debpackage_packager.rb +237 -0
  86. data/lib/mcollective/pluginpackager/modulepackage_packager.rb +127 -0
  87. data/lib/mcollective/pluginpackager/ospackage_packager.rb +59 -0
  88. data/lib/mcollective/pluginpackager/rpmpackage_packager.rb +180 -0
  89. data/lib/mcollective/pluginpackager/standard_definition.rb +69 -0
  90. data/lib/mcollective/pluginpackager/templates/debian/Makefile.erb +7 -0
  91. data/lib/mcollective/pluginpackager/templates/debian/changelog.erb +5 -0
  92. data/lib/mcollective/pluginpackager/templates/debian/compat.erb +1 -0
  93. data/lib/mcollective/pluginpackager/templates/debian/control.erb +15 -0
  94. data/lib/mcollective/pluginpackager/templates/debian/copyright.erb +8 -0
  95. data/lib/mcollective/pluginpackager/templates/debian/rules.erb +6 -0
  96. data/lib/mcollective/pluginpackager/templates/module/Modulefile.erb +5 -0
  97. data/lib/mcollective/pluginpackager/templates/module/README.md.erb +37 -0
  98. data/lib/mcollective/pluginpackager/templates/module/_manifest.pp.erb +9 -0
  99. data/lib/mcollective/pluginpackager/templates/redhat/rpm_spec.erb +63 -0
  100. data/lib/mcollective/registration/base.rb +91 -0
  101. data/lib/mcollective/rpc.rb +182 -0
  102. data/lib/mcollective/rpc/actionrunner.rb +158 -0
  103. data/lib/mcollective/rpc/agent.rb +374 -0
  104. data/lib/mcollective/rpc/audit.rb +38 -0
  105. data/lib/mcollective/rpc/client.rb +1066 -0
  106. data/lib/mcollective/rpc/helpers.rb +321 -0
  107. data/lib/mcollective/rpc/progress.rb +63 -0
  108. data/lib/mcollective/rpc/reply.rb +87 -0
  109. data/lib/mcollective/rpc/request.rb +86 -0
  110. data/lib/mcollective/rpc/result.rb +90 -0
  111. data/lib/mcollective/rpc/stats.rb +294 -0
  112. data/lib/mcollective/runnerstats.rb +90 -0
  113. data/lib/mcollective/security.rb +26 -0
  114. data/lib/mcollective/security/base.rb +244 -0
  115. data/lib/mcollective/shell.rb +126 -0
  116. data/lib/mcollective/ssl.rb +285 -0
  117. data/lib/mcollective/util.rb +579 -0
  118. data/lib/mcollective/validator.rb +85 -0
  119. data/lib/mcollective/validator/array_validator.ddl +7 -0
  120. data/lib/mcollective/validator/array_validator.rb +9 -0
  121. data/lib/mcollective/validator/ipv4address_validator.ddl +7 -0
  122. data/lib/mcollective/validator/ipv4address_validator.rb +16 -0
  123. data/lib/mcollective/validator/ipv6address_validator.ddl +7 -0
  124. data/lib/mcollective/validator/ipv6address_validator.rb +16 -0
  125. data/lib/mcollective/validator/length_validator.ddl +7 -0
  126. data/lib/mcollective/validator/length_validator.rb +11 -0
  127. data/lib/mcollective/validator/regex_validator.ddl +7 -0
  128. data/lib/mcollective/validator/regex_validator.rb +9 -0
  129. data/lib/mcollective/validator/shellsafe_validator.ddl +7 -0
  130. data/lib/mcollective/validator/shellsafe_validator.rb +13 -0
  131. data/lib/mcollective/validator/typecheck_validator.ddl +7 -0
  132. data/lib/mcollective/validator/typecheck_validator.rb +28 -0
  133. metadata +215 -0
@@ -0,0 +1,111 @@
1
+ class MCollective::Application::Rpc<MCollective::Application
2
+ description "Generic RPC agent client application"
3
+
4
+ usage "mco rpc [options] [filters] --agent <agent> --action <action> [--argument <key=val> --argument ...]"
5
+ usage "mco rpc [options] [filters] <agent> <action> [<key=val> <key=val> ...]"
6
+
7
+ option :show_results,
8
+ :description => "Do not process results, just send request",
9
+ :arguments => ["--no-results", "--nr"],
10
+ :default => true,
11
+ :type => :bool
12
+
13
+ option :agent,
14
+ :description => "Agent to call",
15
+ :arguments => ["-a", "--agent AGENT"]
16
+
17
+ option :action,
18
+ :description => "Action to call",
19
+ :arguments => ["--action ACTION"]
20
+
21
+ option :arguments,
22
+ :description => "Arguments to pass to agent",
23
+ :arguments => ["--arg", "--argument ARGUMENT"],
24
+ :type => :array,
25
+ :default => [],
26
+ :validate => Proc.new {|val| val.match(/^(.+?)=(.+)$/) ? true : "Could not parse --arg #{val} should be of the form key=val" }
27
+
28
+ def post_option_parser(configuration)
29
+ # handle the alternative format that optparse cant parse
30
+ unless (configuration.include?(:agent) && configuration.include?(:action))
31
+ if ARGV.length >= 2
32
+ configuration[:agent] = ARGV[0]
33
+ ARGV.delete_at(0)
34
+
35
+ configuration[:action] = ARGV[0]
36
+ ARGV.delete_at(0)
37
+
38
+ ARGV.each do |v|
39
+ if v =~ /^(.+?)=(.+)$/
40
+ configuration[:arguments] = [] unless configuration.include?(:arguments)
41
+ configuration[:arguments] << v
42
+ else
43
+ STDERR.puts("Could not parse --arg #{v}")
44
+ exit(1)
45
+ end
46
+ end
47
+ else
48
+ STDERR.puts("No agent, action and arguments specified")
49
+ exit(1)
50
+ end
51
+ end
52
+
53
+ # convert arguments to symbols for keys to comply with simplerpc conventions
54
+ args = configuration[:arguments].clone
55
+ configuration[:arguments] = {}
56
+
57
+ args.each do |v|
58
+ if v =~ /^(.+?)=(.+)$/
59
+ configuration[:arguments][$1.to_sym] = $2
60
+ end
61
+ end
62
+ end
63
+
64
+ def string_to_ddl_type(arguments, ddl)
65
+ return if ddl.empty?
66
+
67
+ arguments.keys.each do |key|
68
+ if ddl[:input].keys.include?(key)
69
+ case ddl[:input][key][:type]
70
+ when :boolean
71
+ arguments[key] = MCollective::DDL.string_to_boolean(arguments[key])
72
+
73
+ when :number, :integer, :float
74
+ arguments[key] = MCollective::DDL.string_to_number(arguments[key])
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ def main
81
+ mc = rpcclient(configuration[:agent])
82
+
83
+ mc.agent_filter(configuration[:agent])
84
+
85
+ string_to_ddl_type(configuration[:arguments], mc.ddl.action_interface(configuration[:action])) if mc.ddl
86
+
87
+ mc.validate_request(configuration[:action], configuration[:arguments])
88
+
89
+ if mc.reply_to
90
+ configuration[:arguments][:process_results] = true
91
+
92
+ puts "Request sent with id: " + mc.send(configuration[:action], configuration[:arguments]) + " replies to #{mc.reply_to}"
93
+ elsif !configuration[:show_results]
94
+ configuration[:arguments][:process_results] = false
95
+
96
+ puts "Request sent with id: " + mc.send(configuration[:action], configuration[:arguments])
97
+ else
98
+ discover_args = {:verbose => true}
99
+
100
+ mc.detect_and_set_stdin_discovery
101
+
102
+ mc.discover discover_args
103
+
104
+ printrpc mc.send(configuration[:action], configuration[:arguments])
105
+
106
+ printrpcstats :summarize => true, :caption => "#{configuration[:agent]}##{configuration[:action]} call stats" if mc.discover.size > 0
107
+
108
+ halt mc.stats
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,134 @@
1
+ module MCollective
2
+ class Applications
3
+ def self.[](appname)
4
+ load_application(appname)
5
+ PluginManager["#{appname}_application"]
6
+ end
7
+
8
+ def self.run(appname)
9
+ load_config
10
+
11
+ begin
12
+ load_application(appname)
13
+ rescue Exception => e
14
+ e.backtrace.first << Util.colorize(:red, " <----")
15
+ STDERR.puts "Application '#{appname}' failed to load:"
16
+ STDERR.puts
17
+ STDERR.puts Util.colorize(:red, " #{e} (#{e.class})")
18
+ STDERR.puts
19
+ STDERR.puts " %s" % [e.backtrace.join("\n ")]
20
+ exit 1
21
+ end
22
+
23
+ PluginManager["#{appname}_application"].run
24
+ end
25
+
26
+ def self.load_application(appname)
27
+ return if PluginManager.include?("#{appname}_application")
28
+
29
+ load_config
30
+
31
+ PluginManager.loadclass "MCollective::Application::#{appname.capitalize}"
32
+ PluginManager << {:type => "#{appname}_application", :class => "MCollective::Application::#{appname.capitalize}"}
33
+ end
34
+
35
+ # Returns an array of applications found in the lib dirs
36
+ def self.list
37
+ load_config
38
+
39
+ PluginManager.find("application")
40
+ rescue SystemExit
41
+ exit 1
42
+ rescue Exception => e
43
+ STDERR.puts "Failed to generate application list: #{e.class}: #{e}"
44
+ exit 1
45
+ end
46
+
47
+ # Filters a string of opts out using Shellwords
48
+ # keeping only things related to --config and -c
49
+ def self.filter_extra_options(opts)
50
+ res = ""
51
+ words = Shellwords.shellwords(opts)
52
+ words.each_with_index do |word,idx|
53
+ if word == "-c"
54
+ return "--config=#{words[idx + 1]}"
55
+ elsif word == "--config"
56
+ return "--config=#{words[idx + 1]}"
57
+ elsif word =~ /\-c=/
58
+ return word
59
+ elsif word =~ /\-\-config=/
60
+ return word
61
+ end
62
+ end
63
+
64
+ return ""
65
+ end
66
+
67
+ # We need to know the config file in order to know the libdir
68
+ # so that we can find applications.
69
+ #
70
+ # The problem is the CLI might be stuffed with options only the
71
+ # app in the libdir might understand so we have a chicken and
72
+ # egg situation.
73
+ #
74
+ # We're parsing and filtering MCOLLECTIVE_EXTRA_OPTS removing
75
+ # all but config related options and parsing the options looking
76
+ # just for the config file.
77
+ #
78
+ # We're handling failures gracefully and finally restoring the
79
+ # ARG and MCOLLECTIVE_EXTRA_OPTS to the state they were before
80
+ # we started parsing.
81
+ #
82
+ # This is mostly a hack, when we're redoing how config works
83
+ # this stuff should be made less sucky
84
+ def self.load_config
85
+ return if Config.instance.configured
86
+
87
+ original_argv = ARGV.clone
88
+ original_extra_opts = ENV["MCOLLECTIVE_EXTRA_OPTS"].clone rescue nil
89
+ configfile = nil
90
+
91
+ parser = OptionParser.new
92
+ parser.on("--config CONFIG", "-c", "Config file") do |f|
93
+ configfile = f
94
+ end
95
+
96
+ parser.program_name = $0
97
+
98
+ parser.on("--help")
99
+
100
+ # avoid option parsers own internal version handling that sux
101
+ parser.on("-v", "--verbose")
102
+
103
+ if original_extra_opts
104
+ begin
105
+ # optparse will parse the whole ENV in one go and refuse
106
+ # to play along with the retry trick I do below so in
107
+ # order to handle unknown options properly I parse out
108
+ # only -c and --config deleting everything else and
109
+ # then restore the environment variable later when I
110
+ # am done with it
111
+ ENV["MCOLLECTIVE_EXTRA_OPTS"] = filter_extra_options(ENV["MCOLLECTIVE_EXTRA_OPTS"].clone)
112
+ parser.environment("MCOLLECTIVE_EXTRA_OPTS")
113
+ rescue Exception => e
114
+ Log.error("Failed to parse MCOLLECTIVE_EXTRA_OPTS: #{e}")
115
+ end
116
+
117
+ ENV["MCOLLECTIVE_EXTRA_OPTS"] = original_extra_opts.clone
118
+ end
119
+
120
+ begin
121
+ parser.parse!
122
+ rescue OptionParser::InvalidOption => e
123
+ retry
124
+ end
125
+
126
+ ARGV.clear
127
+ original_argv.each {|a| ARGV << a}
128
+
129
+ configfile = Util.config_file_for_user unless configfile
130
+
131
+ Config.instance.loadconfig(configfile)
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,145 @@
1
+ module MCollective
2
+ # A class to manage a number of named caches. Each cache can have a unique
3
+ # timeout for keys in it and each cache has an independent Mutex protecting
4
+ # access to it.
5
+ #
6
+ # This cache is setup early in the process of loading the mcollective
7
+ # libraries, before any threads are created etc making it suitable as a
8
+ # cross thread cache or just a store for Mutexes you need to use across
9
+ # threads like in an Agent or something.
10
+ #
11
+ # # sets up a new cache, noop if it already exist
12
+ # Cache.setup(:ddl, 600)
13
+ # => true
14
+ #
15
+ # # writes an item to the :ddl cache, this item will
16
+ # # be valid on the cache for 600 seconds
17
+ # Cache.write(:ddl, :something, "value")
18
+ # => "value"
19
+ #
20
+ # # reads from the cache, read failures due to non existing
21
+ # # data or expired data will raise an exception
22
+ # Cache.read(:ddl, :something)
23
+ # => "value"
24
+ #
25
+ # # time left till expiry, raises if nothing is found
26
+ # Cache.ttl(:ddl, :something)
27
+ # => 500
28
+ #
29
+ # # forcibly evict something from the cache
30
+ # Cache.invalidate!(:ddl, :something)
31
+ # => "value"
32
+ #
33
+ # # deletes an entire named cache and its mutexes
34
+ # Cache.delete!(:ddl)
35
+ # => true
36
+ #
37
+ # # you can also just use this cache as a global mutex store
38
+ # Cache.setup(:mylock)
39
+ #
40
+ # Cache.synchronize(:mylock) do
41
+ # do_something()
42
+ # end
43
+ #
44
+ module Cache
45
+ # protects access to @cache_locks and top level @cache
46
+ @locks_mutex = Mutex.new
47
+
48
+ # stores a mutex per named cache
49
+ @cache_locks = {}
50
+
51
+ # the named caches protected by items in @cache_locks
52
+ @cache = {}
53
+
54
+ def self.setup(cache_name, ttl=300)
55
+ @locks_mutex.synchronize do
56
+ break if @cache_locks.include?(cache_name)
57
+
58
+ @cache_locks[cache_name] = Mutex.new
59
+
60
+ @cache_locks[cache_name].synchronize do
61
+ @cache[cache_name] = {:max_age => Float(ttl)}
62
+ end
63
+ end
64
+
65
+ true
66
+ end
67
+
68
+ def self.has_cache?(cache_name)
69
+ @locks_mutex.synchronize do
70
+ @cache.include?(cache_name)
71
+ end
72
+ end
73
+
74
+ def self.delete!(cache_name)
75
+ @locks_mutex.synchronize do
76
+ raise("No cache called '%s'" % cache_name) unless @cache_locks.include?(cache_name)
77
+
78
+ @cache_locks.delete(cache_name)
79
+ @cache.delete(cache_name)
80
+ end
81
+
82
+ true
83
+ end
84
+
85
+ def self.write(cache_name, key, value)
86
+ raise("No cache called '%s'" % cache_name) unless @cache.include?(cache_name)
87
+
88
+ @cache_locks[cache_name].synchronize do
89
+ @cache[cache_name][key] ||= {}
90
+ @cache[cache_name][key][:cache_create_time] = Time.now
91
+ @cache[cache_name][key][:value] = value
92
+ end
93
+
94
+ value
95
+ end
96
+
97
+ def self.read(cache_name, key)
98
+ raise("No cache called '%s'" % cache_name) unless @cache.include?(cache_name)
99
+
100
+ unless ttl(cache_name, key) > 0
101
+ Log.debug("Cache expired on '%s' key '%s'" % [cache_name, key])
102
+ raise("Cache for item '%s' on cache '%s' has expired" % [key, cache_name])
103
+ end
104
+
105
+ Log.debug("Cache hit on '%s' key '%s'" % [cache_name, key])
106
+
107
+ @cache_locks[cache_name].synchronize do
108
+ @cache[cache_name][key][:value]
109
+ end
110
+ end
111
+
112
+ def self.ttl(cache_name, key)
113
+ raise("No cache called '%s'" % cache_name) unless @cache.include?(cache_name)
114
+
115
+ @cache_locks[cache_name].synchronize do
116
+ unless @cache[cache_name].include?(key)
117
+ Log.debug("Cache miss on '%s' key '%s'" % [cache_name, key])
118
+ raise("No item called '%s' for cache '%s'" % [key, cache_name])
119
+ end
120
+
121
+ @cache[cache_name][:max_age] - (Time.now - @cache[cache_name][key][:cache_create_time])
122
+ end
123
+ end
124
+
125
+ def self.synchronize(cache_name)
126
+ raise("No cache called '%s'" % cache_name) unless @cache.include?(cache_name)
127
+
128
+ raise ("No block supplied to synchronize") unless block_given?
129
+
130
+ @cache_locks[cache_name].synchronize do
131
+ yield
132
+ end
133
+ end
134
+
135
+ def self.invalidate!(cache_name, key)
136
+ raise("No cache called '%s'" % cache_name) unless @cache.include?(cache_name)
137
+
138
+ @cache_locks[cache_name].synchronize do
139
+ return false unless @cache[cache_name].include?(key)
140
+
141
+ @cache[cache_name].delete(key)
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,353 @@
1
+ module MCollective
2
+ # Helpers for writing clients that can talk to agents, do discovery and so forth
3
+ class Client
4
+ attr_accessor :options, :stats, :discoverer, :connection_timeout
5
+
6
+ def initialize(options)
7
+ @config = Config.instance
8
+ @options = nil
9
+
10
+ if options.is_a?(String)
11
+ # String is the path to a config file
12
+ @config.loadconfig(options) unless @config.configured
13
+ elsif options.is_a?(Hash)
14
+ @config.loadconfig(options[:config]) unless @config.configured
15
+ @options = options
16
+ @connection_timeout = options[:connection_timeout]
17
+ else
18
+ raise "Invalid parameter passed to Client constructor. Valid types are Hash or String"
19
+ end
20
+
21
+ @connection_timeout ||= @config.connection_timeout
22
+
23
+ @connection = PluginManager["connector_plugin"]
24
+ @security = PluginManager["security_plugin"]
25
+
26
+ @security.initiated_by = :client
27
+ @subscriptions = {}
28
+
29
+ @discoverer = Discovery.new(self)
30
+
31
+ # Time box the connection if a timeout has been specified
32
+ # connection_timeout defaults to nil which means it will try forever if
33
+ # not specified
34
+ begin
35
+ Timeout::timeout(@connection_timeout, ClientTimeoutError) do
36
+ @connection.connect
37
+ end
38
+ rescue ClientTimeoutError => e
39
+ Log.error("Timeout occured while trying to connect to middleware")
40
+ raise e
41
+ end
42
+ end
43
+
44
+ @@request_sequence = 0
45
+ def self.request_sequence
46
+ @@request_sequence
47
+ end
48
+
49
+ # Returns the configured main collective if no
50
+ # specific collective is specified as options
51
+ def collective
52
+ if @options[:collective].nil?
53
+ @config.main_collective
54
+ else
55
+ @options[:collective]
56
+ end
57
+ end
58
+
59
+ # Disconnects cleanly from the middleware
60
+ def disconnect
61
+ Log.debug("Disconnecting from the middleware")
62
+ @connection.disconnect
63
+ end
64
+
65
+ # Sends a request and returns the generated request id, doesn't wait for
66
+ # responses and doesn't execute any passed in code blocks for responses
67
+ def sendreq(msg, agent, filter = {})
68
+ request = createreq(msg, agent, filter)
69
+ publish(request)
70
+ request.requestid
71
+ end
72
+
73
+ def createreq(msg, agent, filter ={})
74
+ if msg.is_a?(Message)
75
+ request = msg
76
+ agent = request.agent
77
+ else
78
+ ttl = @options[:ttl] || @config.ttl
79
+ request = Message.new(msg, nil, {:agent => agent, :type => :request, :collective => collective, :filter => filter, :ttl => ttl})
80
+ request.reply_to = @options[:reply_to] if @options[:reply_to]
81
+ end
82
+
83
+ @@request_sequence += 1
84
+
85
+ request.encode!
86
+ subscribe(agent, :reply) unless request.reply_to
87
+ request
88
+ end
89
+
90
+ def subscribe(agent, type)
91
+ unless @subscriptions.include?(agent)
92
+ subscription = Util.make_subscriptions(agent, type, collective)
93
+ Log.debug("Subscribing to #{type} target for agent #{agent}")
94
+
95
+ Util.subscribe(subscription)
96
+ @subscriptions[agent] = 1
97
+ end
98
+ end
99
+
100
+ def unsubscribe(agent, type)
101
+ if @subscriptions.include?(agent)
102
+ subscription = Util.make_subscriptions(agent, type, collective)
103
+ Log.debug("Unsubscribing #{type} target for #{agent}")
104
+
105
+ Util.unsubscribe(subscription)
106
+ @subscriptions.delete(agent)
107
+ end
108
+ end
109
+ # Blocking call that waits for ever for a message to arrive.
110
+ #
111
+ # If you give it a requestid this means you've previously send a request
112
+ # with that ID and now you just want replies that matches that id, in that
113
+ # case the current connection will just ignore all messages not directed at it
114
+ # and keep waiting for more till it finds a matching message.
115
+ def receive(requestid = nil)
116
+ reply = nil
117
+
118
+ begin
119
+ reply = @connection.receive
120
+ reply.type = :reply
121
+ reply.expected_msgid = requestid
122
+
123
+ reply.decode!
124
+
125
+ unless reply.requestid == requestid
126
+ raise(MsgDoesNotMatchRequestID, "Message reqid #{reply.requestid} does not match our reqid #{requestid}")
127
+ end
128
+
129
+ Log.debug("Received reply to #{reply.requestid} from #{reply.payload[:senderid]}")
130
+ rescue SecurityValidationFailed => e
131
+ Log.warn("Ignoring a message that did not pass security validations")
132
+ retry
133
+ rescue MsgDoesNotMatchRequestID => e
134
+ Log.debug("Ignoring a message for some other client : #{e.message}")
135
+ retry
136
+ end
137
+
138
+ reply
139
+ end
140
+
141
+ # Performs a discovery of nodes matching the filter passed
142
+ # returns an array of nodes
143
+ #
144
+ # An integer limit can be supplied this will have the effect
145
+ # of the discovery being cancelled soon as it reached the
146
+ # requested limit of hosts
147
+ def discover(filter, timeout, limit=0)
148
+ @discoverer.discover(filter.merge({'collective' => collective}), timeout, limit)
149
+ end
150
+
151
+ # Send a request, performs the passed block for each response
152
+ #
153
+ # times = req("status", "mcollectived", options, client) {|resp|
154
+ # pp resp
155
+ # }
156
+ #
157
+ # It returns a hash of times and timeouts for discovery and total run is taken from the options
158
+ # hash which in turn is generally built using MCollective::Optionparser
159
+ def req(body, agent=nil, options=false, waitfor=[], &block)
160
+ if body.is_a?(Message)
161
+ agent = body.agent
162
+ waitfor = body.discovered_hosts || []
163
+ @options = body.options
164
+ end
165
+
166
+ @options = options if options
167
+ threaded = @options[:threaded]
168
+ timeout = @discoverer.discovery_timeout(@options[:timeout], @options[:filter])
169
+ request = createreq(body, agent, @options[:filter])
170
+ publish_timeout = @options[:publish_timeout] || @config.publish_timeout
171
+ stat = {:starttime => Time.now.to_f, :discoverytime => 0, :blocktime => 0, :totaltime => 0}
172
+ STDOUT.sync = true
173
+ hosts_responded = 0
174
+
175
+ begin
176
+ if threaded
177
+ hosts_responded = threaded_req(request, publish_timeout, timeout, waitfor, &block)
178
+ else
179
+ hosts_responded = unthreaded_req(request, publish_timeout, timeout, waitfor, &block)
180
+ end
181
+ rescue Interrupt => e
182
+ ensure
183
+ unsubscribe(agent, :reply)
184
+ end
185
+
186
+ return update_stat(stat, hosts_responded, request.requestid)
187
+ end
188
+
189
+ # Starts the client receiver and publisher unthreaded.
190
+ # This is the default client behaviour.
191
+ def unthreaded_req(request, publish_timeout, timeout, waitfor, &block)
192
+ start_publisher(request, publish_timeout)
193
+ start_receiver(request.requestid, waitfor, timeout, &block)
194
+ end
195
+
196
+ # Starts the client receiver and publisher in threads.
197
+ # This is activated when the 'threader_client' configuration
198
+ # option is set.
199
+ def threaded_req(request, publish_timeout, timeout, waitfor, &block)
200
+ Log.debug("Starting threaded client")
201
+ publisher = Thread.new do
202
+ start_publisher(request, publish_timeout)
203
+ end
204
+
205
+ # When the client is threaded we add the publishing timeout to
206
+ # the agent timeout so that the receiver doesn't time out before
207
+ # publishing has finished in cases where publish_timeout >= timeout.
208
+ total_timeout = publish_timeout + timeout
209
+ hosts_responded = 0
210
+
211
+ receiver = Thread.new do
212
+ hosts_responded = start_receiver(request.requestid, waitfor, total_timeout, &block)
213
+ end
214
+
215
+ receiver.join
216
+ hosts_responded
217
+ end
218
+
219
+ # Starts the request publishing routine
220
+ def start_publisher(request, publish_timeout)
221
+ Log.debug("Starting publishing with publish timeout of #{publish_timeout}")
222
+ begin
223
+ Timeout.timeout(publish_timeout) do
224
+ publish(request)
225
+ end
226
+ rescue Timeout::Error => e
227
+ Log.warn("Could not publish all messages. Publishing timed out.")
228
+ end
229
+ end
230
+
231
+ def publish(request)
232
+ Log.info("Sending request #{request.requestid} for agent '#{request.agent}' with ttl #{request.ttl} in collective '#{request.collective}'")
233
+ request.publish
234
+ end
235
+
236
+ # Starts the response receiver routine
237
+ # Expected to return the amount of received responses.
238
+ def start_receiver(requestid, waitfor, timeout, &block)
239
+ Log.debug("Starting response receiver with timeout of #{timeout}")
240
+ hosts_responded = 0
241
+
242
+ if (waitfor.is_a?(Array))
243
+ unfinished = Hash.new(0)
244
+ waitfor.each {|w| unfinished[w] += 1}
245
+ else
246
+ unfinished = []
247
+ end
248
+
249
+ begin
250
+ Timeout.timeout(timeout) do
251
+ loop do
252
+ resp = receive(requestid)
253
+
254
+ if block.arity == 2
255
+ yield resp.payload, resp
256
+ else
257
+ yield resp.payload
258
+ end
259
+
260
+ hosts_responded += 1
261
+
262
+ if (waitfor.is_a?(Array))
263
+ sender = resp.payload[:senderid]
264
+ if unfinished[sender] <= 1
265
+ unfinished.delete(sender)
266
+ else
267
+ unfinished[sender] -= 1
268
+ end
269
+
270
+ break if !waitfor.empty? && unfinished.empty?
271
+ else
272
+ break unless waitfor == 0 || hosts_responded < waitfor
273
+ end
274
+ end
275
+ end
276
+ rescue Timeout::Error => e
277
+ if waitfor.is_a?(Array)
278
+ if !unfinished.empty?
279
+ Log.warn("Could not receive all responses. Did not receive responses from #{unfinished.keys.join(', ')}")
280
+ end
281
+ elsif (waitfor > hosts_responded)
282
+ Log.warn("Could not receive all responses. Expected : #{waitfor}. Received : #{hosts_responded}")
283
+ end
284
+ end
285
+
286
+ hosts_responded
287
+ end
288
+
289
+ def update_stat(stat, hosts_responded, requestid)
290
+ stat[:totaltime] = Time.now.to_f - stat[:starttime]
291
+ stat[:blocktime] = stat[:totaltime] - stat[:discoverytime]
292
+ stat[:responses] = hosts_responded
293
+ stat[:noresponsefrom] = []
294
+ stat[:unexpectedresponsefrom] = []
295
+ stat[:requestid] = requestid
296
+
297
+ @stats = stat
298
+ end
299
+
300
+ def discovered_req(body, agent, options=false)
301
+ raise "Client#discovered_req has been removed, please port your agent and client to the SimpleRPC framework"
302
+ end
303
+
304
+ # Prints out the stats returns from req and discovered_req in a nice way
305
+ def display_stats(stats, options=false, caption="stomp call summary")
306
+ options = @options unless options
307
+
308
+ if options[:verbose]
309
+ puts("\n---- #{caption} ----")
310
+
311
+ if stats[:discovered]
312
+ puts(" Nodes: #{stats[:discovered]} / #{stats[:responses]}")
313
+ else
314
+ puts(" Nodes: #{stats[:responses]}")
315
+ end
316
+
317
+ printf(" Start Time: %s\n", Time.at(stats[:starttime]))
318
+ printf(" Discovery Time: %.2fms\n", stats[:discoverytime] * 1000)
319
+ printf(" Agent Time: %.2fms\n", stats[:blocktime] * 1000)
320
+ printf(" Total Time: %.2fms\n", stats[:totaltime] * 1000)
321
+
322
+ else
323
+ if stats[:discovered]
324
+ printf("\nFinished processing %d / %d hosts in %.2f ms\n\n", stats[:responses], stats[:discovered], stats[:blocktime] * 1000)
325
+ else
326
+ printf("\nFinished processing %d hosts in %.2f ms\n\n", stats[:responses], stats[:blocktime] * 1000)
327
+ end
328
+ end
329
+
330
+ if stats[:noresponsefrom].size > 0
331
+ puts("\nNo response from:\n")
332
+
333
+ stats[:noresponsefrom].each do |c|
334
+ puts if c % 4 == 1
335
+ printf("%30s", c)
336
+ end
337
+
338
+ puts
339
+ end
340
+
341
+ if stats[:unexpectedresponsefrom].size > 0
342
+ puts("\nUnexpected response from:\n")
343
+
344
+ stats[:unexpectedresponsefrom].each do |c|
345
+ puts if c % 4 == 1
346
+ printf("%30s", c)
347
+ end
348
+
349
+ puts
350
+ end
351
+ end
352
+ end
353
+ end