Class | MCollective::Message |
In: |
lib/mcollective/message.rb
|
Parent: | Object |
container for a message, its headers, agent, collective and other meta data
VALIDTYPES | = | [:message, :request, :direct_request, :reply] |
agent | [RW] | |
collective | [RW] | |
discovered_hosts | [RW] | |
expected_msgid | [R] | |
filter | [RW] | |
headers | [RW] | |
message | [R] | |
msgtime | [R] | |
options | [RW] | |
payload | [R] | |
reply_to | [R] | |
request | [R] | |
requestid | [RW] | |
ttl | [RW] | |
type | [R] | |
validated | [R] |
payload - the message body without headers etc, just the text message - the original message received from the middleware options[:base64] - if the body base64 encoded? options[:agent] - the agent the message is for/from options[:collective] - the collective its for/from options[:headers] - the message headers options[:type] - an indicator about the type of message, :message, :request, :direct_request or :reply options[:request] - if this is a reply this should old the message we are replying to options[:filter] - for requests, the filter to encode into the message options[:options] - the normal client options hash options[:ttl] - the maximum amount of seconds this message can be valid for options[:expected_msgid] - in the case of replies this is the msgid it is expecting in the replies
# File lib/mcollective/message.rb, line 22 22: def initialize(payload, message, options = {}) 23: options = {:base64 => false, 24: :agent => nil, 25: :headers => {}, 26: :type => :message, 27: :request => nil, 28: :filter => Util.empty_filter, 29: :options => {}, 30: :ttl => 60, 31: :expected_msgid => nil, 32: :collective => nil}.merge(options) 33: 34: @payload = payload 35: @message = message 36: @requestid = nil 37: @discovered_hosts = nil 38: @reply_to = nil 39: 40: @type = options[:type] 41: @headers = options[:headers] 42: @base64 = options[:base64] 43: @filter = options[:filter] 44: @expected_msgid = options[:expected_msgid] 45: @options = options[:options] 46: 47: @ttl = @options[:ttl] || Config.instance.ttl 48: @msgtime = 0 49: 50: @validated = false 51: 52: if options[:request] 53: @request = options[:request] 54: @agent = request.agent 55: @collective = request.collective 56: @type = :reply 57: else 58: @agent = options[:agent] 59: @collective = options[:collective] 60: end 61: 62: base64_decode! 63: end
# File lib/mcollective/message.rb, line 113 113: def base64_decode! 114: return unless @base64 115: 116: @payload = SSL.base64_decode(@payload) 117: @base64 = false 118: end
# File lib/mcollective/message.rb, line 120 120: def base64_encode! 121: return if @base64 122: 123: @payload = SSL.base64_encode(@payload) 124: @base64 = true 125: end
# File lib/mcollective/message.rb, line 233 233: def create_reqid 234: Digest::MD5.hexdigest("#{Config.instance.identity}-#{Time.now.to_f}-#{agent}-#{collective}") 235: end
# File lib/mcollective/message.rb, line 177 177: def decode! 178: raise "Cannot decode message type #{type}" unless [:request, :reply].include?(type) 179: 180: @payload = PluginManager["security_plugin"].decodemsg(self) 181: 182: if type == :request 183: raise 'callerid in request is not valid, surpressing reply to potentially forged request' unless PluginManager["security_plugin"].valid_callerid?(payload[:callerid]) 184: end 185: 186: [:collective, :agent, :filter, :requestid, :ttl, :msgtime].each do |prop| 187: instance_variable_set("@#{prop}", payload[prop]) if payload.include?(prop) 188: end 189: end
# File lib/mcollective/message.rb, line 131 131: def encode! 132: case type 133: when :reply 134: raise "Cannot encode a reply message if no request has been associated with it" unless request 135: raise 'callerid in original request is not valid, surpressing reply to potentially forged request' unless PluginManager["security_plugin"].valid_callerid?(request.payload[:callerid]) 136: 137: @requestid = request.payload[:requestid] 138: @payload = PluginManager["security_plugin"].encodereply(agent, payload, requestid, request.payload[:callerid]) 139: when :request, :direct_request 140: validate_compount_filter(@filter["compound"]) unless @filter["compound"].empty? 141: 142: @requestid = create_reqid 143: @payload = PluginManager["security_plugin"].encoderequest(Config.instance.identity, payload, requestid, filter, agent, collective, ttl) 144: else 145: raise "Cannot encode #{type} messages" 146: end 147: end
in the case of reply messages we are expecting replies to a previously created message. This stores a hint to that previously sent message id and can be used by other classes like the security plugins as a means of optimizing their behavior like by ignoring messages not directed at us.
# File lib/mcollective/message.rb, line 108 108: def expected_msgid=(msgid) 109: raise "Can only store the expected msgid for reply messages" unless @type == :reply 110: @expected_msgid = msgid 111: end
publish a reply message by creating a target name and sending it
# File lib/mcollective/message.rb, line 215 215: def publish 216: Timeout.timeout(2) do 217: # If we've been specificaly told about hosts that were discovered 218: # use that information to do P2P calls if appropriate else just 219: # send it as is. 220: if @discovered_hosts && Config.instance.direct_addressing 221: if @discovered_hosts.size <= Config.instance.direct_addressing_threshold 222: self.type = :direct_request 223: Log.debug("Handling #{requestid} as a direct request") 224: end 225: 226: PluginManager["connector_plugin"].publish(self) 227: else 228: PluginManager["connector_plugin"].publish(self) 229: end 230: end 231: end
Sets a custom reply-to target for requests. The connector plugin should inspect this when constructing requests and set this header ensuring replies will go to the custom target otherwise the connector should just do what it usually does
# File lib/mcollective/message.rb, line 97 97: def reply_to=(target) 98: raise "Custom reply targets can only be set on requests" unless [:request, :direct_request].include?(@type) 99: 100: @reply_to = target 101: end
Sets the message type to one of the known types. In the case of :direct_request the list of hosts to communicate with should have been set with discovered_hosts else an exception will be raised. This is for extra security, we never accidentally want to send a direct request without a list of hosts or something weird like that as it might result in a filterless broadcast being sent.
Additionally you simply cannot set :direct_request if direct_addressing was not enabled this is to force a workflow that doesnt not yield in a mistake when someone might assume direct_addressing is enabled when its not.
# File lib/mcollective/message.rb, line 74 74: def type=(type) 75: raise "Unknown message type #{type}" unless VALIDTYPES.include?(type) 76: 77: if type == :direct_request 78: raise "Direct requests is not enabled using the direct_addressing config option" unless Config.instance.direct_addressing 79: 80: unless @discovered_hosts && !@discovered_hosts.empty? 81: raise "Can only set type to :direct_request if discovered_hosts have been set" 82: end 83: 84: # clear out the filter, custom discovery sources might interpret the filters 85: # different than the remote mcollectived and in directed mode really the only 86: # filter that matters is the agent filter 87: @filter = Util.empty_filter 88: @filter["agent"] << @agent 89: end 90: 91: @type = type 92: end
Perform validation against the message by checking filters and ttl
# File lib/mcollective/message.rb, line 192 192: def validate 193: raise "Can only validate request messages" unless type == :request 194: 195: msg_age = Time.now.utc.to_i - msgtime 196: 197: if msg_age > ttl 198: cid = "" 199: cid += payload[:callerid] + "@" if payload.include?(:callerid) 200: cid += payload[:senderid] 201: 202: if msg_age > ttl 203: PluginManager["global_stats"].ttlexpired 204: 205: raise(MsgTTLExpired, "Message #{requestid} from #{cid} created at #{msgtime} is #{msg_age} seconds old, TTL is #{ttl}") 206: end 207: end 208: 209: raise(NotTargettedAtUs, "Received message is not targetted to us") unless PluginManager["security_plugin"].validate_filter?(payload[:filter]) 210: 211: @validated = true 212: end
# File lib/mcollective/message.rb, line 149 149: def validate_compount_filter(compound_filter) 150: compound_filter.each do |filter| 151: filter.each do |statement| 152: if statement["fstatement"] 153: functionname = statement["fstatement"]["name"] 154: pluginname = Data.pluginname(functionname) 155: value = statement["fstatement"]["value"] 156: 157: begin 158: ddl = DDL.new(pluginname, :data) 159: rescue 160: raise DDLValidationError, "Could not find DDL for data plugin #{pluginname}, cannot use #{functionname}() in discovery" 161: end 162: 163: # parses numbers and booleans entered as strings into proper 164: # types of data so that DDL validation will pass 165: statement["fstatement"]["params"] = Data.ddl_transform_input(ddl, statement["fstatement"]["params"]) 166: 167: Data.ddl_validate(ddl, statement["fstatement"]["params"]) 168: 169: unless value && Data.ddl_has_output?(ddl, value) 170: raise DDLValidationError, "#{functionname}() does not return a #{value} value" 171: end 172: end 173: end 174: end 175: end