rightscale-nanite 0.4.1.4 → 0.4.1.10

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -10,7 +10,7 @@ end
10
10
  require 'rake/clean'
11
11
  require 'lib/nanite'
12
12
 
13
- GEM = "nanite"
13
+ GEM = "rightscale-nanite"
14
14
  AUTHOR = "Ezra Zygmuntowicz"
15
15
  EMAIL = "ezra@engineyard.com"
16
16
  HOMEPAGE = "http://github.com/ezmobius/nanite"
@@ -74,3 +74,5 @@ task :docs => :rdoc do
74
74
  sh 'firefox rdoc/index.html'
75
75
  end
76
76
  end
77
+
78
+ require 'rightscale-nanite.rb'
data/lib/nanite/agent.rb CHANGED
@@ -86,24 +86,20 @@ module Nanite
86
86
  def initialize(opts)
87
87
  set_configuration(opts)
88
88
  @tags = []
89
- @tags << opts[:tag]
89
+ @tags << opts[:tag] if opts[:tag]
90
90
  @tags.flatten!
91
91
  @options.freeze
92
92
  end
93
93
 
94
94
  def run
95
- log_path = false
96
- if @options[:daemonize]
97
- log_path = (@options[:log_dir] || @options[:root] || Dir.pwd)
98
- end
99
- Log.init(@identity, log_path)
95
+ Log.init(@identity, @options[:log_path])
100
96
  Log.level = @options[:log_level] if @options[:log_level]
101
97
  @serializer = Serializer.new(@options[:format])
102
98
  @status_proc = lambda { parse_uptime(`uptime 2> /dev/null`) rescue 'no status' }
103
99
  pid_file = PidFile.new(@identity, @options)
104
100
  pid_file.check
105
101
  if @options[:daemonize]
106
- daemonize
102
+ daemonize(@identity, @options)
107
103
  pid_file.write
108
104
  at_exit { pid_file.remove }
109
105
  end
@@ -135,6 +131,16 @@ module Nanite
135
131
  @deny_token = deny_token
136
132
  end
137
133
 
134
+ # Update set of tags published by agent and notify mapper
135
+ # Add tags in 'new_tags' and remove tags in 'old_tags'
136
+ def update_tags(new_tags, old_tags)
137
+ @tags += (new_tags || [])
138
+ @tags -= (old_tags || [])
139
+ @tags.uniq!
140
+ tag_update = TagUpdate.new(identity, new_tags, old_tags)
141
+ amq.fanout('registration', :no_declare => options[:secure]).publish(serializer.dump(tag_update))
142
+ end
143
+
138
144
  protected
139
145
 
140
146
  def set_configuration(opts)
@@ -149,6 +155,11 @@ module Nanite
149
155
  opts.delete(:identity) unless opts[:identity]
150
156
  @options.update(custom_config.merge(opts))
151
157
  @options[:file_root] ||= File.join(@options[:root], 'files')
158
+ @options[:log_path] = false
159
+ if @options[:daemonize]
160
+ @options[:log_path] = (@options[:log_dir] || @options[:root] || Dir.pwd)
161
+ end
162
+
152
163
  return @identity = "nanite-#{@options[:identity]}" if @options[:identity]
153
164
  token = Identity.generate
154
165
  @identity = "nanite-#{token}"
@@ -160,6 +171,7 @@ module Nanite
160
171
  def load_actors
161
172
  return unless options[:root]
162
173
  actors_dir = @options[:actors_dir] || "#{@options[:root]}/actors"
174
+ Nanite::Log.warn("Actors dir #{actors_dir} does not exist or is not reachable") unless File.directory?(actors_dir)
163
175
  actors = @options[:actors]
164
176
  Dir["#{actors_dir}/*.rb"].each do |actor|
165
177
  next if actors && !actors.include?(File.basename(actor, ".rb"))
@@ -167,7 +179,11 @@ module Nanite
167
179
  require actor
168
180
  end
169
181
  init_path = @options[:initrb] || File.join(options[:root], 'init.rb')
170
- instance_eval(File.read(init_path), init_path) if File.exist?(init_path)
182
+ if File.exist?(init_path)
183
+ instance_eval(File.read(init_path), init_path)
184
+ else
185
+ Nanite::Log.warn("init.rb #{init_path} does not exist or is not reachable") unless File.exists?(init_path)
186
+ end
171
187
  end
172
188
 
173
189
  def receive(packet)
data/lib/nanite/amqp.rb CHANGED
@@ -46,7 +46,11 @@ module Nanite
46
46
  :host => options[:host],
47
47
  :port => (options[:port] || ::AMQP::PORT).to_i,
48
48
  :insist => options[:insist] || false,
49
- :retry => options[:retry] || 5
49
+ :retry => options[:retry] || 5,
50
+ :connection_status => options[:connection_callback] || proc {|event|
51
+ Nanite::Log.debug("CONNECTED to MQ") if event == :connected
52
+ Nanite::Log.debug("DISCONNECTED from MQ") if event == :disconnected
53
+ }
50
54
  })
51
55
  MQ.new(connection)
52
56
  end
@@ -2,13 +2,14 @@ module Nanite
2
2
  class Cluster
3
3
  attr_reader :agent_timeout, :nanites, :reaper, :serializer, :identity, :amq, :redis, :mapper, :callbacks
4
4
 
5
- def initialize(amq, agent_timeout, identity, serializer, mapper, state_configuration=nil, callbacks = {})
5
+ def initialize(amq, agent_timeout, identity, serializer, mapper, state_configuration=nil, tag_store=nil, callbacks = {})
6
6
  @amq = amq
7
7
  @agent_timeout = agent_timeout
8
8
  @identity = identity
9
9
  @serializer = serializer
10
10
  @mapper = mapper
11
11
  @state = state_configuration
12
+ @tag_store = tag_store
12
13
  @security = SecurityProvider.get
13
14
  @callbacks = callbacks
14
15
  setup_state
@@ -19,7 +20,7 @@ module Nanite
19
20
  # determine which nanites should receive the given request
20
21
  def targets_for(request)
21
22
  return [request.target] if request.target
22
- __send__(request.selector, request.type, request.tags).collect {|name, state| name }
23
+ __send__(request.selector, request.from, request.type, request.tags).collect {|name, state| name }
23
24
  end
24
25
 
25
26
  # adds nanite to nanites map: key is nanite's identity
@@ -30,38 +31,51 @@ module Nanite
30
31
  when Register
31
32
  if @security.authorize_registration(reg)
32
33
  Nanite::Log.info("RECV #{reg.to_s}")
33
- nanites[reg.identity] = { :services => reg.services, :status => reg.status, :tags => reg.tags }
34
- reaper.timeout(reg.identity, agent_timeout + 1) { nanite_timed_out(reg.identity) }
34
+ nanites[reg.identity] = { :services => reg.services, :status => reg.status, :tags => reg.tags, :timestamp => Time.now.utc.to_i }
35
+ reaper.register(reg.identity, agent_timeout + 1) { nanite_timed_out(reg.identity) }
35
36
  callbacks[:register].call(reg.identity, mapper) if callbacks[:register]
36
37
  else
37
38
  Nanite::Log.warn("RECV NOT AUTHORIZED #{reg.to_s}")
38
39
  end
39
40
  when UnRegister
40
41
  Nanite::Log.info("RECV #{reg.to_s}")
42
+ reaper.unregister(reg.identity)
41
43
  nanites.delete(reg.identity)
42
44
  callbacks[:unregister].call(reg.identity, mapper) if callbacks[:unregister]
45
+ when TagUpdate
46
+ Nanite::Log.info("RECV #{reg.to_s}")
47
+ nanites.update_tags(reg.identity, reg.new_tags, reg.obsolete_tags)
43
48
  else
44
49
  Nanite::Log.warn("RECV [register] Invalid packet type: #{reg.class}")
45
50
  end
46
51
  end
47
52
 
48
53
  def nanite_timed_out(token)
49
- nanite = nanites.delete(token)
50
- callbacks[:timeout].call(token, mapper) if callbacks[:timeout]
54
+ nanite = nanites[token]
55
+ if nanite && timed_out?(nanite)
56
+ Nanite::Log.info("Nanite #{token} timed out")
57
+ nanite = nanites.delete(token)
58
+ callbacks[:timeout].call(token, mapper) if callbacks[:timeout]
59
+ true
60
+ end
51
61
  end
52
-
62
+
53
63
  def route(request, targets)
54
64
  EM.next_tick { targets.map { |target| publish(request, target) } }
55
65
  end
56
66
 
57
67
  def publish(request, target)
58
- # We need to initialize the 'target' field of the request object so that the serializer has
59
- # access to it.
68
+ # We need to initialize the 'target' field of the request object so that the serializer and
69
+ # the security provider have access to it.
60
70
  begin
61
71
  old_target = request.target
62
72
  request.target = target unless target == 'mapper-offline'
63
- Nanite::Log.info("SEND #{request.to_s([:from, :tags, :target])}")
64
- amq.queue(target).publish(serializer.dump(request), :persistent => request.persistent)
73
+ if @security.authorize_request(request)
74
+ Nanite::Log.info("SEND #{request.to_s([:from, :on_behalf, :tags, :target])}")
75
+ amq.queue(target).publish(serializer.dump(request), :persistent => request.persistent)
76
+ else
77
+ Nanite::Log.warn("RECV NOT AUTHORIZED #{request.to_s}")
78
+ end
65
79
  ensure
66
80
  request.target = old_target
67
81
  end
@@ -74,8 +88,8 @@ module Nanite
74
88
  def handle_ping(ping)
75
89
  begin
76
90
  if nanite = nanites[ping.identity]
77
- nanite[:status] = ping.status
78
- reaper.reset_with_autoregister_hack(ping.identity, agent_timeout + 1) { nanite_timed_out(ping.identity) }
91
+ nanites.update_status(ping.identity, ping.status)
92
+ reaper.update(ping.identity, agent_timeout + 1) { nanite_timed_out(ping.identity) }
79
93
  else
80
94
  packet = Advertise.new
81
95
  Nanite::Log.info("SEND #{packet.to_s} to #{ping.identity}")
@@ -83,58 +97,54 @@ module Nanite
83
97
  end
84
98
  end
85
99
  end
86
-
100
+
87
101
  # forward request coming from agent
88
102
  def handle_request(request)
89
- if @security.authorize_request(request)
90
- Nanite::Log.info("RECV #{request.to_s([:from, :target, :tags])}") unless Nanite::Log.level == :debug
91
- Nanite::Log.debug("RECV #{request.to_s}")
92
- case request
93
- when Push
94
- mapper.send_push(request)
95
- else
96
- intm_handler = lambda do |result, job|
97
- result = IntermediateMessage.new(request.token, job.request.from, mapper.identity, nil, result)
98
- forward_response(result, request.persistent)
99
- end
100
-
101
- result = Result.new(request.token, request.from, nil, mapper.identity)
102
- ok = mapper.send_request(request, :intermediate_handler => intm_handler) do |res|
103
- result.results = res
104
- forward_response(result, request.persistent)
105
- end
106
-
107
- if ok == false
108
- forward_response(result, request.persistent)
109
- end
110
- end
103
+ Nanite::Log.info("RECV #{request.to_s([:from, :on_behalf, :target, :tags])}") unless Nanite::Log.level == :debug
104
+ Nanite::Log.debug("RECV #{request.to_s}")
105
+ case request
106
+ when Push
107
+ mapper.send_push(request)
111
108
  else
112
- Nanite::Log.warn("RECV NOT AUTHORIZED #{request.to_s}")
109
+ intm_handler = lambda do |result, job|
110
+ result = IntermediateMessage.new(request.token, job.request.from, mapper.identity, nil, result)
111
+ forward_response(result, request.persistent)
112
+ end
113
+
114
+ result = Result.new(request.token, request.from, nil, mapper.identity)
115
+ ok = mapper.send_request(request, :intermediate_handler => intm_handler) do |res|
116
+ result.results = res
117
+ forward_response(result, request.persistent)
118
+ end
119
+
120
+ if ok == false
121
+ forward_response(result, request.persistent)
122
+ end
113
123
  end
114
124
  end
115
-
125
+
116
126
  # forward response back to agent that originally made the request
117
127
  def forward_response(res, persistent)
118
128
  Nanite::Log.info("SEND #{res.to_s([:to])}")
119
129
  amq.queue(res.to).publish(serializer.dump(res), :persistent => persistent)
120
130
  end
121
-
131
+
122
132
  # returns least loaded nanite that provides given service
123
- def least_loaded(service, tags=[])
124
- candidates = nanites_providing(service,tags)
133
+ def least_loaded(from, service, tags=[])
134
+ candidates = nanites_providing(from, service, tags)
125
135
  return [] if candidates.empty?
126
136
 
127
137
  [candidates.min { |a,b| a[1][:status] <=> b[1][:status] }]
128
138
  end
129
139
 
130
140
  # returns all nanites that provide given service
131
- def all(service, tags=[])
132
- nanites_providing(service,tags)
141
+ def all(from, service, tags=[])
142
+ nanites_providing(from, service,tags)
133
143
  end
134
144
 
135
145
  # returns a random nanite
136
- def random(service, tags=[])
137
- candidates = nanites_providing(service,tags)
146
+ def random(from, service, tags=[])
147
+ candidates = nanites_providing(from, service,tags)
138
148
  return [] if candidates.empty?
139
149
 
140
150
  [candidates[rand(candidates.size)]]
@@ -142,10 +152,10 @@ module Nanite
142
152
 
143
153
  # selects next nanite that provides given service
144
154
  # using round robin rotation
145
- def rr(service, tags=[])
155
+ def rr(from, service, tags=[])
146
156
  @last ||= {}
147
157
  @last[service] ||= 0
148
- candidates = nanites_providing(service,tags)
158
+ candidates = nanites_providing(from, service,tags)
149
159
  return [] if candidates.empty?
150
160
  @last[service] = 0 if @last[service] >= candidates.size
151
161
  candidate = candidates[@last[service]]
@@ -153,9 +163,18 @@ module Nanite
153
163
  [candidate]
154
164
  end
155
165
 
166
+ def timed_out?(nanite)
167
+ nanite[:timestamp].to_i < (Time.now.utc - agent_timeout).to_i
168
+ end
169
+
156
170
  # returns all nanites that provide the given service
157
- def nanites_providing(service, *tags)
158
- nanites.nanites_for(service, *tags)
171
+ def nanites_providing(from, service, tags)
172
+ nanites.nanites_for(from, service, tags).delete_if do |nanite|
173
+ if res = timed_out?(nanite[1])
174
+ Nanite::Log.debug("Ignoring timed out nanite #{nanite[0]} in target selection - last seen at #{nanite[1][:timestamp]}")
175
+ end
176
+ res
177
+ end
159
178
  end
160
179
 
161
180
  def setup_queues
@@ -172,6 +191,7 @@ module Nanite
172
191
  handle_ping(ping)
173
192
  rescue Exception => e
174
193
  Nanite::Log.error("RECV [ping] #{e.message}")
194
+ callbacks[:exception].call(e, msg, mapper) rescue nil if callbacks[:exception]
175
195
  end
176
196
  end
177
197
  hb_fanout = amq.fanout('heartbeat', :durable => true)
@@ -188,6 +208,7 @@ module Nanite
188
208
  register(serializer.load(msg))
189
209
  rescue Exception => e
190
210
  Nanite::Log.error("RECV [register] #{e.message}")
211
+ callbacks[:exception].call(e, msg, mapper) rescue nil if callbacks[:exception]
191
212
  end
192
213
  end
193
214
  reg_fanout = amq.fanout('registration', :durable => true)
@@ -197,13 +218,14 @@ module Nanite
197
218
  amq.queue("registration-#{identity}", :exclusive => true).bind(reg_fanout).subscribe &handler
198
219
  end
199
220
  end
200
-
221
+
201
222
  def setup_request_queue
202
223
  handler = lambda do |msg|
203
224
  begin
204
225
  handle_request(serializer.load(msg))
205
226
  rescue Exception => e
206
227
  Nanite::Log.error("RECV [request] #{e.message}")
228
+ callbacks[:exception].call(e, msg, mapper) rescue nil if callbacks[:exception]
207
229
  end
208
230
  end
209
231
  req_fanout = amq.fanout('request', :durable => true)
@@ -219,16 +241,14 @@ module Nanite
219
241
  when String
220
242
  # backwards compatibility, we assume redis if the configuration option
221
243
  # was a string
222
- Nanite::Log.info("[setup] using redis for state storage")
223
244
  require 'nanite/state'
224
- @nanites = Nanite::State.new(@state)
225
- when Hash
245
+ @nanites = Nanite::State.new(@state, @tag_store)
226
246
  else
227
247
  require 'nanite/local_state'
228
248
  @nanites = Nanite::LocalState.new
229
249
  end
230
250
  end
231
-
251
+
232
252
  def shared_state?
233
253
  !@state.nil?
234
254
  end
@@ -1,13 +1,13 @@
1
1
  module Nanite
2
2
  module DaemonizeHelper
3
- def daemonize
3
+ def daemonize(identity, options = {})
4
4
  exit if fork
5
5
  Process.setsid
6
6
  exit if fork
7
- File.umask 0000
8
7
  STDIN.reopen "/dev/null"
9
- STDOUT.reopen "/dev/null", "a"
10
- STDERR.reopen STDOUT
8
+ STDOUT.reopen "#{options[:log_path]}/nanite.#{identity}.out", "a"
9
+ STDERR.reopen "#{options[:log_path]}/nanite.#{identity}.err", "a"
10
+ File.umask 0000
11
11
  end
12
12
  end
13
13
  end
@@ -14,7 +14,7 @@ module Nanite
14
14
  all(:tags)
15
15
  end
16
16
 
17
- def nanites_for(service, *tags)
17
+ def nanites_for(from, service, tags)
18
18
  tags = tags.dup.flatten
19
19
  nanites = select { |name, state| state[:services].include?(service) }
20
20
  unless tags.empty?
@@ -24,6 +24,10 @@ module Nanite
24
24
  end.to_a
25
25
  end
26
26
 
27
+ def update_status(name, status)
28
+ self[name].update(:status => status, :timestamp => Time.now.utc.to_i)
29
+ end
30
+
27
31
  private
28
32
 
29
33
  def all(key)
data/lib/nanite/log.rb CHANGED
@@ -35,7 +35,7 @@ module Nanite
35
35
  # Throws an ArgumentError if you feed it a bogus log level (that is not
36
36
  # one of :debug, :info, :warn, :error, :fatal or the corresponding strings or a valid Logger level)
37
37
  def level=(loglevel)
38
- init() unless @logger
38
+ init unless @logger
39
39
  lvl = case loglevel
40
40
  when String then loglevel.intern
41
41
  when Integer then LEVELS.invert[loglevel]
data/lib/nanite/mapper.rb CHANGED
@@ -76,10 +76,15 @@ module Nanite
76
76
  # secure : use Security features of rabbitmq to restrict nanites to themselves
77
77
  #
78
78
  # prefetch : Sets prefetch (only supported in RabbitMQ >= 1.6)
79
- # callbacks : A set of callbacks to have code executed on specific events, supported events are :register,
80
- # :unregister and :timeout. Parameter must be a hash with the corresponding events as keys and
81
- # a block as value. The block will get the corresponding nanite's identity and a copy of the
82
- # mapper
79
+ #
80
+ # callbacks : A set of callbacks to be executed on specific events. Supported events are :register,
81
+ # :unregister, :timeout and :exception. This option must be a hash with event names as
82
+ # as keys and Procs as values. The Proc's arity (number of parameters) depends on the
83
+ # type of callback:
84
+ # exception -- the exception, the message being processed, a reference to the mapper
85
+ # all others -- the corresponding nanite's identity, a reference to the mapper
86
+ #
87
+ # tag_store : Name of class which implements tag store backend interface, RedisTagStore by default
83
88
  #
84
89
  # Connection options:
85
90
  #
@@ -116,6 +121,10 @@ module Nanite
116
121
  @options.update(custom_config.merge(options))
117
122
  @identity = "mapper-#{@options[:identity]}"
118
123
  @options[:file_root] ||= File.join(@options[:root], 'files')
124
+ @options[:log_path] = false
125
+ if @options[:daemonize]
126
+ @options[:log_path] = (@options[:log_dir] || @options[:root] || Dir.pwd)
127
+ end
119
128
  @options.freeze
120
129
  end
121
130
 
@@ -125,7 +134,7 @@ module Nanite
125
134
  pid_file = PidFile.new(@identity, @options)
126
135
  pid_file.check
127
136
  if @options[:daemonize]
128
- daemonize
137
+ daemonize(@identity, @options)
129
138
  pid_file.write
130
139
  at_exit { pid_file.remove }
131
140
  else
@@ -288,21 +297,18 @@ module Nanite
288
297
  job_warden.process(msg)
289
298
  rescue Exception => e
290
299
  Nanite::Log.error("RECV [result] #{e.message}")
300
+ callbacks[:exception].call(e, msg, mapper) rescue nil if callbacks[:exception]
291
301
  end
292
302
  end
293
303
  end
294
304
 
295
305
  def setup_logging
296
- log_path = false
297
- if @options[:daemonize]
298
- log_path = (@options[:log_dir] || @options[:root] || Dir.pwd)
299
- end
300
- Nanite::Log.init(@identity, log_path)
306
+ Nanite::Log.init(@identity, @options[:log_path])
301
307
  Nanite::Log.level = @options[:log_level] if @options[:log_level]
302
308
  end
303
309
 
304
310
  def setup_cluster
305
- @cluster = Cluster.new(@amq, @options[:agent_timeout], @options[:identity], @serializer, self, @options[:redis], @options[:callbacks])
311
+ @cluster = Cluster.new(@amq, @options[:agent_timeout], @options[:identity], @serializer, self, @options[:redis], @options[:tag_store], @options[:callbacks])
306
312
  end
307
313
  end
308
314
  end
@@ -49,7 +49,15 @@ module Nanite
49
49
  push.persistent = opts.key?(:persistent) ? opts[:persistent] : options[:persistent]
50
50
  Nanite::Log.info("SEND #{push.to_s([:tags, :target])}")
51
51
  amqp.fanout('request', :no_declare => options[:secure]).publish(serializer.dump(push))
52
- end
52
+ end
53
+
54
+ # Update tags registered by mapper for agent
55
+ def update_tags(new_tags, obsolete_tags)
56
+ raise "Mapper proxy not initialized" unless identity && options
57
+ update = TagUpdate.new(identity, new_tags, obsolete_tags)
58
+ Nanite::Log.info("SEND #{update.to_s}")
59
+ amqp.fanout('registration', :no_declare => options[:secure]).publish(serializer.dump(update))
60
+ end
53
61
 
54
62
  # Handle intermediary result
55
63
  def handle_intermediate_result(res)
File without changes
@@ -112,15 +112,16 @@ module Nanite
112
112
  # payload is arbitrary data that is transferred from mapper to actor
113
113
  #
114
114
  # Options:
115
- # from is sender identity
116
- # token is a generated request id that mapper uses to identify replies
117
- # reply_to is identity of the node actor replies to, usually a mapper itself
118
- # selector is the selector used to route the request
119
- # target is the target nanite for the request
115
+ # from is sender identity
116
+ # on_behalf is agent identity that should be used to authorize request
117
+ # token is a generated request id that mapper uses to identify replies
118
+ # reply_to is identity of the node actor replies to, usually a mapper itself
119
+ # selector is the selector used to route the request
120
+ # target is the target nanite for the request
120
121
  # persistent signifies if this request should be saved to persistent storage by the AMQP broker
121
122
  class Request < Packet
122
123
 
123
- attr_accessor :from, :payload, :type, :token, :reply_to, :selector, :target, :persistent, :tags
124
+ attr_accessor :from, :on_behalf, :payload, :type, :token, :reply_to, :selector, :target, :persistent, :tags
124
125
 
125
126
  DEFAULT_OPTIONS = {:selector => :least_loaded}
126
127
 
@@ -130,6 +131,7 @@ module Nanite
130
131
  @payload = payload
131
132
  @size = size
132
133
  @from = opts[:from]
134
+ @on_behalf = opts[:on_behalf]
133
135
  @token = opts[:token]
134
136
  @reply_to = opts[:reply_to]
135
137
  @selector = opts[:selector]
@@ -140,15 +142,17 @@ module Nanite
140
142
 
141
143
  def self.json_create(o)
142
144
  i = o['data']
143
- new(i['type'], i['payload'], { :from => i['from'], :token => i['token'],
144
- :reply_to => i['reply_to'], :selector => i['selector'],
145
- :target => i['target'], :persistent => i['persistent'],
146
- :tags => i['tags'] }, o['size'])
145
+ new(i['type'], i['payload'], { :from => i['from'], :on_behalf => i['on_behalf'],
146
+ :token => i['token'], :reply_to => i['reply_to'],
147
+ :selector => i['selector'], :target => i['target'],
148
+ :persistent => i['persistent'], :tags => i['tags'] },
149
+ o['size'])
147
150
  end
148
151
 
149
152
  def to_s(filter=nil)
150
153
  log_msg = "#{super} <#{token}> #{type}"
151
154
  log_msg += " from #{id_to_s(from)}" if filter.nil? || filter.include?(:from)
155
+ log_msg += " on behalf of #{id_to_s(on_behalf)}" if on_behalf && (filter.nil? || filter.include?(:on_behalf))
152
156
  log_msg += " to #{id_to_s(target)}" if target && (filter.nil? || filter.include?(:target))
153
157
  log_msg += ", reply_to #{id_to_s(reply_to)}" if reply_to && (filter.nil? || filter.include?(:reply_to))
154
158
  log_msg += ", tags #{tags.inspect}" if tags && !tags.empty? && (filter.nil? || filter.include?(:tags))
@@ -165,14 +169,15 @@ module Nanite
165
169
  # payload is arbitrary data that is transferred from mapper to actor
166
170
  #
167
171
  # Options:
168
- # from is sender identity
169
- # token is a generated request id that mapper uses to identify replies
170
- # selector is the selector used to route the request
171
- # target is the target nanite for the request
172
+ # from is sender identity
173
+ # on_behalf is agent identity that should be used to authorize request
174
+ # token is a generated request id that mapper uses to identify replies
175
+ # selector is the selector used to route the request
176
+ # target is the target nanite for the request
172
177
  # persistent signifies if this request should be saved to persistent storage by the AMQP broker
173
178
  class Push < Packet
174
179
 
175
- attr_accessor :from, :payload, :type, :token, :selector, :target, :persistent, :tags
180
+ attr_accessor :from, :on_behalf, :payload, :type, :token, :selector, :target, :persistent, :tags
176
181
 
177
182
  DEFAULT_OPTIONS = {:selector => :least_loaded}
178
183
 
@@ -182,6 +187,7 @@ module Nanite
182
187
  @payload = payload
183
188
  @size = size
184
189
  @from = opts[:from]
190
+ @on_behalf = opts[:on_behalf]
185
191
  @token = opts[:token]
186
192
  @selector = opts[:selector]
187
193
  @target = opts[:target]
@@ -191,14 +197,17 @@ module Nanite
191
197
 
192
198
  def self.json_create(o)
193
199
  i = o['data']
194
- new(i['type'], i['payload'], { :from => i['from'], :token => i['token'],
195
- :selector => i['selector'], :target => i['target'],
196
- :persistent => i['persistent'], :tags => i['tags'] }, o['size'])
200
+ new(i['type'], i['payload'], { :from => i['from'], :on_behalf => i['on_behalf'],
201
+ :token => i['token'], :selector => i['selector'],
202
+ :target => i['target'], :persistent => i['persistent'],
203
+ :tags => i['tags'] },
204
+ o['size'])
197
205
  end
198
206
 
199
207
  def to_s(filter=nil)
200
208
  log_msg = "#{super} <#{token}> #{type}"
201
209
  log_msg += " from #{id_to_s(from)}" if filter.nil? || filter.include?(:from)
210
+ log_msg += " on behalf of #{id_to_s(on_behalf)}" if on_behalf && (filter.nil? || filter.include?(:on_behalf))
202
211
  log_msg += ", target #{id_to_s(target)}" if target && (filter.nil? || filter.include?(:target))
203
212
  log_msg += ", tags #{tags.inspect}" if tags && !tags.empty? && (filter.nil? || filter.include?(:tags))
204
213
  log_msg += ", payload #{payload.inspect}" if filter.nil? || filter.include?(:payload)
@@ -360,6 +369,33 @@ module Nanite
360
369
  end
361
370
 
362
371
  end
363
-
372
+
373
+ # packet that is sent by agents to the mapper
374
+ # to update their tags
375
+ class TagUpdate < Packet
376
+
377
+ attr_accessor :identity, :new_tags, :obsolete_tags
378
+
379
+ def initialize(identity, new_tags, obsolete_tags, size=nil)
380
+ @identity = identity
381
+ @new_tags = new_tags
382
+ @obsolete_tags = obsolete_tags
383
+ @size = size
384
+ end
385
+
386
+ def self.json_create(o)
387
+ i = o['data']
388
+ new(i['identity'], i['new_tags'], i['obsolete_tags'], o['size'])
389
+ end
390
+
391
+ def to_s
392
+ log_msg = "#{super} #{id_to_s(identity)}"
393
+ log_msg += ", new tags: #{new_tags.join(', ')}" if new_tags && !new_tags.empty?
394
+ log_msg += ", obsolete tags: #{obsolete_tags.join(', ')}" if obsolete_tags && !obsolete_tags.empty?
395
+ log_msg
396
+ end
397
+
398
+ end
399
+
364
400
  end
365
401