nanite 0.4.1.10 → 0.4.1.13

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.
data/Rakefile CHANGED
@@ -35,6 +35,7 @@ spec = Gem::Specification.new do |s|
35
35
  s.executables = %w( nanite-agent nanite-mapper nanite-admin )
36
36
 
37
37
  s.add_dependency('amqp', '>= 0.6.0')
38
+ s.add_dependency('json', '>= 1.1.7')
38
39
 
39
40
  s.require_path = 'lib'
40
41
  s.files = %w(LICENSE README.rdoc Rakefile TODO) + Dir.glob("{lib,bin,specs}/**/*")
@@ -42,6 +42,9 @@ opts = OptionParser.new do |opts|
42
42
  options[:threadpool_size] = tps
43
43
  end
44
44
 
45
+ opts.on("--prefetch COUNT", Integer, "The number of messages stuffed into the queue at anytime. Set this to a value of 1 or so for longer running jobs (1 or more seconds), so the agent does not get overwhelmed. Default is unlimited.") do |pref|
46
+ options[:prefetch] = pref
47
+ end
45
48
  end
46
49
 
47
50
  opts.parse!
@@ -39,7 +39,7 @@ require 'nanite/security/static_certificate_store'
39
39
  require 'nanite/serializer'
40
40
 
41
41
  module Nanite
42
- VERSION = '0.4.1.10' unless defined?(Nanite::VERSION)
42
+ VERSION = '0.4.1.13' unless defined?(Nanite::VERSION)
43
43
 
44
44
  class MapperNotRunning < StandardError; end
45
45
 
@@ -54,6 +54,11 @@ module Nanite
54
54
  @mapper = Nanite::Mapper.start(options)
55
55
  end
56
56
 
57
+ def start_mapper_proxy(options = {})
58
+ identity = options[:identity] || Nanite::Identity.generate
59
+ @mapper = Nanite::MapperProxy.new(identity, options)
60
+ end
61
+
57
62
  def request(*args, &blk)
58
63
  ensure_mapper
59
64
  @mapper.request(*args, &blk)
@@ -55,6 +55,9 @@ module Nanite
55
55
  # services : list of services provided by this agent, by default
56
56
  # all methods exposed by actors are listed
57
57
  #
58
+ # prefetch : Sets prefetch (only supported in RabbitMQ >= 1.6). Use value of 1 for long
59
+ # running jobs (greater than a second) to avoid slamming/stalling your agent.
60
+ #
58
61
  # single_threaded: Run all operations in one thread
59
62
  #
60
63
  # threadpool_size: Number of threads to run operations in
@@ -86,7 +89,7 @@ module Nanite
86
89
  def initialize(opts)
87
90
  set_configuration(opts)
88
91
  @tags = []
89
- @tags << opts[:tag] if opts[:tag]
92
+ @tags << opts[:tag]
90
93
  @tags.flatten!
91
94
  @options.freeze
92
95
  end
@@ -131,16 +134,6 @@ module Nanite
131
134
  @deny_token = deny_token
132
135
  end
133
136
 
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
-
144
137
  protected
145
138
 
146
139
  def set_configuration(opts)
@@ -218,6 +211,9 @@ module Nanite
218
211
  end
219
212
 
220
213
  def setup_queue
214
+ if amq.respond_to?(:prefetch) && @options.has_key?(:prefetch)
215
+ amq.prefetch(@options[:prefetch])
216
+ end
221
217
  amq.queue(identity, :durable => true).subscribe(:ack => true) do |info, msg|
222
218
  begin
223
219
  info.ack
@@ -2,14 +2,13 @@ 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, tag_store=nil, callbacks = {})
5
+ def initialize(amq, agent_timeout, identity, serializer, mapper, state_configuration=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
13
12
  @security = SecurityProvider.get
14
13
  @callbacks = callbacks
15
14
  setup_state
@@ -20,7 +19,7 @@ module Nanite
20
19
  # determine which nanites should receive the given request
21
20
  def targets_for(request)
22
21
  return [request.target] if request.target
23
- __send__(request.selector, request.from, request.type, request.tags).collect {|name, state| name }
22
+ __send__(request.selector, request.type, request.tags).collect {|name, state| name }
24
23
  end
25
24
 
26
25
  # adds nanite to nanites map: key is nanite's identity
@@ -30,7 +29,7 @@ module Nanite
30
29
  case reg
31
30
  when Register
32
31
  if @security.authorize_registration(reg)
33
- Nanite::Log.info("RECV #{reg.to_s}")
32
+ Nanite::Log.debug("RECV #{reg.to_s}")
34
33
  nanites[reg.identity] = { :services => reg.services, :status => reg.status, :tags => reg.tags, :timestamp => Time.now.utc.to_i }
35
34
  reaper.register(reg.identity, agent_timeout + 1) { nanite_timed_out(reg.identity) }
36
35
  callbacks[:register].call(reg.identity, mapper) if callbacks[:register]
@@ -42,9 +41,6 @@ module Nanite
42
41
  reaper.unregister(reg.identity)
43
42
  nanites.delete(reg.identity)
44
43
  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)
48
44
  else
49
45
  Nanite::Log.warn("RECV [register] Invalid packet type: #{reg.class}")
50
46
  end
@@ -59,23 +55,19 @@ module Nanite
59
55
  true
60
56
  end
61
57
  end
62
-
58
+
63
59
  def route(request, targets)
64
60
  EM.next_tick { targets.map { |target| publish(request, target) } }
65
61
  end
66
62
 
67
63
  def publish(request, target)
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.
64
+ # We need to initialize the 'target' field of the request object so that the serializer has
65
+ # access to it.
70
66
  begin
71
67
  old_target = request.target
72
68
  request.target = target unless target == 'mapper-offline'
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
69
+ Nanite::Log.debug("SEND #{request.to_s([:from, :tags, :target])}")
70
+ amq.queue(target).publish(serializer.dump(request), :persistent => request.persistent)
79
71
  ensure
80
72
  request.target = old_target
81
73
  end
@@ -92,59 +84,62 @@ module Nanite
92
84
  reaper.update(ping.identity, agent_timeout + 1) { nanite_timed_out(ping.identity) }
93
85
  else
94
86
  packet = Advertise.new
95
- Nanite::Log.info("SEND #{packet.to_s} to #{ping.identity}")
87
+ Nanite::Log.debug("SEND #{packet.to_s} to #{ping.identity}")
96
88
  amq.queue(ping.identity).publish(serializer.dump(packet))
97
89
  end
98
90
  end
99
91
  end
100
-
92
+
101
93
  # forward request coming from agent
102
94
  def handle_request(request)
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)
108
- else
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)
95
+ if @security.authorize_request(request)
96
+ Nanite::Log.debug("RECV #{request.to_s}")
97
+ case request
98
+ when Push
99
+ mapper.send_push(request)
100
+ else
101
+ intm_handler = lambda do |result, job|
102
+ result = IntermediateMessage.new(request.token, job.request.from, mapper.identity, nil, result)
103
+ forward_response(result, request.persistent)
104
+ end
105
+
106
+ result = Result.new(request.token, request.from, nil, mapper.identity)
107
+ ok = mapper.send_request(request, :intermediate_handler => intm_handler) do |res|
108
+ result.results = res
109
+ forward_response(result, request.persistent)
110
+ end
111
+
112
+ if ok == false
113
+ forward_response(result, request.persistent)
114
+ end
122
115
  end
116
+ else
117
+ Nanite::Log.warn("RECV NOT AUTHORIZED #{request.to_s}")
123
118
  end
124
119
  end
125
-
120
+
126
121
  # forward response back to agent that originally made the request
127
122
  def forward_response(res, persistent)
128
- Nanite::Log.info("SEND #{res.to_s([:to])}")
123
+ Nanite::Log.debug("SEND #{res.to_s([:to])}")
129
124
  amq.queue(res.to).publish(serializer.dump(res), :persistent => persistent)
130
125
  end
131
-
126
+
132
127
  # returns least loaded nanite that provides given service
133
- def least_loaded(from, service, tags=[])
134
- candidates = nanites_providing(from, service, tags)
128
+ def least_loaded(service, tags=[])
129
+ candidates = nanites_providing(service,tags)
135
130
  return [] if candidates.empty?
136
131
 
137
132
  [candidates.min { |a,b| a[1][:status] <=> b[1][:status] }]
138
133
  end
139
134
 
140
135
  # returns all nanites that provide given service
141
- def all(from, service, tags=[])
142
- nanites_providing(from, service,tags)
136
+ def all(service, tags=[])
137
+ nanites_providing(service,tags)
143
138
  end
144
139
 
145
140
  # returns a random nanite
146
- def random(from, service, tags=[])
147
- candidates = nanites_providing(from, service,tags)
141
+ def random(service, tags=[])
142
+ candidates = nanites_providing(service,tags)
148
143
  return [] if candidates.empty?
149
144
 
150
145
  [candidates[rand(candidates.size)]]
@@ -152,28 +147,30 @@ module Nanite
152
147
 
153
148
  # selects next nanite that provides given service
154
149
  # using round robin rotation
155
- def rr(from, service, tags=[])
150
+ def rr(service, tags=[])
156
151
  @last ||= {}
157
152
  @last[service] ||= 0
158
- candidates = nanites_providing(from, service,tags)
153
+ candidates = nanites_providing(service,tags)
159
154
  return [] if candidates.empty?
160
155
  @last[service] = 0 if @last[service] >= candidates.size
161
156
  candidate = candidates[@last[service]]
162
157
  @last[service] += 1
163
158
  [candidate]
164
159
  end
165
-
160
+
166
161
  def timed_out?(nanite)
167
162
  nanite[:timestamp].to_i < (Time.now.utc - agent_timeout).to_i
168
163
  end
169
164
 
170
165
  # returns all nanites that provide the given service
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]}")
166
+ def nanites_providing(service, *tags)
167
+ nanites.nanites_for(service, *tags).delete_if do |nanite|
168
+ nanite_id, nanite_attributes = nanite
169
+ if timed_out?(nanite_attributes)
170
+ reaper.unregister(nanite_id)
171
+ nanites.delete(nanite_id)
172
+ Nanite::Log.debug("Nanite #{nanite_id} timed out - ignoring in target selection and deleting from state - last seen at #{nanite_attributes[:timestamp]}")
175
173
  end
176
- res
177
174
  end
178
175
  end
179
176
 
@@ -191,7 +188,6 @@ module Nanite
191
188
  handle_ping(ping)
192
189
  rescue Exception => e
193
190
  Nanite::Log.error("RECV [ping] #{e.message}")
194
- callbacks[:exception].call(e, msg, mapper) rescue nil if callbacks[:exception]
195
191
  end
196
192
  end
197
193
  hb_fanout = amq.fanout('heartbeat', :durable => true)
@@ -208,7 +204,6 @@ module Nanite
208
204
  register(serializer.load(msg))
209
205
  rescue Exception => e
210
206
  Nanite::Log.error("RECV [register] #{e.message}")
211
- callbacks[:exception].call(e, msg, mapper) rescue nil if callbacks[:exception]
212
207
  end
213
208
  end
214
209
  reg_fanout = amq.fanout('registration', :durable => true)
@@ -218,14 +213,13 @@ module Nanite
218
213
  amq.queue("registration-#{identity}", :exclusive => true).bind(reg_fanout).subscribe &handler
219
214
  end
220
215
  end
221
-
216
+
222
217
  def setup_request_queue
223
218
  handler = lambda do |msg|
224
219
  begin
225
220
  handle_request(serializer.load(msg))
226
221
  rescue Exception => e
227
222
  Nanite::Log.error("RECV [request] #{e.message}")
228
- callbacks[:exception].call(e, msg, mapper) rescue nil if callbacks[:exception]
229
223
  end
230
224
  end
231
225
  req_fanout = amq.fanout('request', :durable => true)
@@ -241,14 +235,16 @@ module Nanite
241
235
  when String
242
236
  # backwards compatibility, we assume redis if the configuration option
243
237
  # was a string
238
+ Nanite::Log.info("[setup] using redis for state storage")
244
239
  require 'nanite/state'
245
- @nanites = Nanite::State.new(@state, @tag_store)
240
+ @nanites = Nanite::State.new(@state)
241
+ when Hash
246
242
  else
247
243
  require 'nanite/local_state'
248
244
  @nanites = Nanite::LocalState.new
249
245
  end
250
246
  end
251
-
247
+
252
248
  def shared_state?
253
249
  !@state.nil?
254
250
  end
@@ -31,7 +31,7 @@ module Nanite
31
31
  opts.on("--offline-failsafe", "Store messages in an offline queue when all the nanites are offline. Messages will be redelivered when nanites come online. Can be overriden on a per-message basis using the request methods.") do
32
32
  options[:offline_failsafe] = true
33
33
  end
34
-
34
+
35
35
  opts.on("--redis HOST_PORT", "Use redis as the agent state storage in the mapper: --redis 127.0.0.1:6379; missing host and/or port will be filled with defaults if colon is present") do |redis|
36
36
  redishost, redisport = redis.split(':')
37
37
  redishost = '127.0.0.1' if (redishost.nil? || redishost.empty?)
@@ -14,7 +14,7 @@ module Nanite
14
14
  all(:tags)
15
15
  end
16
16
 
17
- def nanites_for(from, service, tags)
17
+ def nanites_for(service, *tags)
18
18
  tags = tags.dup.flatten
19
19
  nanites = select { |name, state| state[:services].include?(service) }
20
20
  unless tags.empty?
@@ -76,15 +76,10 @@ 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
- #
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
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
88
83
  #
89
84
  # Connection options:
90
85
  #
@@ -250,7 +245,7 @@ module Nanite
250
245
  private
251
246
 
252
247
  def build_deliverable(deliverable_type, type, payload, opts)
253
- deliverable = deliverable_type.new(type, payload, opts)
248
+ deliverable = deliverable_type.new(type, payload, nil, opts)
254
249
  deliverable.from = identity
255
250
  deliverable.token = Identity.generate
256
251
  deliverable.persistent = opts.key?(:persistent) ? opts[:persistent] : options[:persistent]
@@ -293,11 +288,9 @@ module Nanite
293
288
  begin
294
289
  msg = serializer.load(msg)
295
290
  Nanite::Log.debug("RECV #{msg.to_s}")
296
- Nanite::Log.info("RECV #{msg.to_s([:from])}") unless Nanite::Log.level == :debug
297
291
  job_warden.process(msg)
298
292
  rescue Exception => e
299
293
  Nanite::Log.error("RECV [result] #{e.message}")
300
- callbacks[:exception].call(e, msg, mapper) rescue nil if callbacks[:exception]
301
294
  end
302
295
  end
303
296
  end
@@ -308,7 +301,7 @@ module Nanite
308
301
  end
309
302
 
310
303
  def setup_cluster
311
- @cluster = Cluster.new(@amq, @options[:agent_timeout], @options[:identity], @serializer, self, @options[:redis], @options[:tag_store], @options[:callbacks])
304
+ @cluster = Cluster.new(@amq, @options[:agent_timeout], @options[:identity], @serializer, self, @options[:redis], @options[:callbacks])
312
305
  end
313
306
  end
314
307
  end
@@ -31,33 +31,25 @@ module Nanite
31
31
  # Send request to given agent through the mapper
32
32
  def request(type, payload = '', opts = {}, &blk)
33
33
  raise "Mapper proxy not initialized" unless identity && options
34
- request = Request.new(type, payload, opts)
34
+ request = Request.new(type, payload, nil, opts)
35
35
  request.from = identity
36
36
  request.token = Identity.generate
37
37
  request.persistent = opts.key?(:persistent) ? opts[:persistent] : options[:persistent]
38
38
  pending_requests[request.token] =
39
39
  { :intermediate_handler => opts[:intermediate_handler], :result_handler => blk }
40
- Nanite::Log.info("SEND #{request.to_s([:tags, :target])}")
40
+ Nanite::Log.debug("SEND #{request.to_s([:tags, :target])}")
41
41
  amqp.fanout('request', :no_declare => options[:secure]).publish(serializer.dump(request))
42
42
  end
43
43
 
44
44
  def push(type, payload = '', opts = {})
45
45
  raise "Mapper proxy not initialized" unless identity && options
46
- push = Push.new(type, payload, opts)
46
+ push = Push.new(type, payload, nil, opts)
47
47
  push.from = identity
48
48
  push.token = Identity.generate
49
49
  push.persistent = opts.key?(:persistent) ? opts[:persistent] : options[:persistent]
50
- Nanite::Log.info("SEND #{push.to_s([:tags, :target])}")
50
+ Nanite::Log.debug("SEND #{push.to_s([:tags, :target])}")
51
51
  amqp.fanout('request', :no_declare => options[:secure]).publish(serializer.dump(push))
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
52
+ end
61
53
 
62
54
  # Handle intermediary result
63
55
  def handle_intermediate_result(res)
@@ -89,7 +89,7 @@ module Nanite
89
89
 
90
90
  attr_accessor :chunk, :token
91
91
 
92
- def initialize(token, chunk=nil, size=nil)
92
+ def initialize(token, size=nil, chunk=nil)
93
93
  @chunk = chunk
94
94
  @token = token
95
95
  @size = size
@@ -97,7 +97,7 @@ module Nanite
97
97
 
98
98
  def self.json_create(o)
99
99
  i = o['data']
100
- new(i['token'], i['chunk'], o['size'])
100
+ new(i['token'], o['size'], i['chunk'])
101
101
  end
102
102
 
103
103
  def to_s
@@ -112,26 +112,24 @@ 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
- # 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
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
121
120
  # persistent signifies if this request should be saved to persistent storage by the AMQP broker
122
121
  class Request < Packet
123
122
 
124
- attr_accessor :from, :on_behalf, :payload, :type, :token, :reply_to, :selector, :target, :persistent, :tags
123
+ attr_accessor :from, :payload, :type, :token, :reply_to, :selector, :target, :persistent, :tags
125
124
 
126
125
  DEFAULT_OPTIONS = {:selector => :least_loaded}
127
126
 
128
- def initialize(type, payload, opts={}, size=nil)
127
+ def initialize(type, payload, size=nil, opts={})
129
128
  opts = DEFAULT_OPTIONS.merge(opts)
130
129
  @type = type
131
130
  @payload = payload
132
131
  @size = size
133
132
  @from = opts[:from]
134
- @on_behalf = opts[:on_behalf]
135
133
  @token = opts[:token]
136
134
  @reply_to = opts[:reply_to]
137
135
  @selector = opts[:selector]
@@ -142,17 +140,15 @@ module Nanite
142
140
 
143
141
  def self.json_create(o)
144
142
  i = o['data']
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'])
143
+ new(i['type'], i['payload'], o['size'], { :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'] })
150
147
  end
151
148
 
152
149
  def to_s(filter=nil)
153
150
  log_msg = "#{super} <#{token}> #{type}"
154
151
  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))
156
152
  log_msg += " to #{id_to_s(target)}" if target && (filter.nil? || filter.include?(:target))
157
153
  log_msg += ", reply_to #{id_to_s(reply_to)}" if reply_to && (filter.nil? || filter.include?(:reply_to))
158
154
  log_msg += ", tags #{tags.inspect}" if tags && !tags.empty? && (filter.nil? || filter.include?(:tags))
@@ -169,25 +165,23 @@ module Nanite
169
165
  # payload is arbitrary data that is transferred from mapper to actor
170
166
  #
171
167
  # Options:
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
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
177
172
  # persistent signifies if this request should be saved to persistent storage by the AMQP broker
178
173
  class Push < Packet
179
174
 
180
- attr_accessor :from, :on_behalf, :payload, :type, :token, :selector, :target, :persistent, :tags
175
+ attr_accessor :from, :payload, :type, :token, :selector, :target, :persistent, :tags
181
176
 
182
177
  DEFAULT_OPTIONS = {:selector => :least_loaded}
183
178
 
184
- def initialize(type, payload, opts={}, size=nil)
179
+ def initialize(type, payload, size=nil, opts={})
185
180
  opts = DEFAULT_OPTIONS.merge(opts)
186
181
  @type = type
187
182
  @payload = payload
188
183
  @size = size
189
184
  @from = opts[:from]
190
- @on_behalf = opts[:on_behalf]
191
185
  @token = opts[:token]
192
186
  @selector = opts[:selector]
193
187
  @target = opts[:target]
@@ -197,17 +191,14 @@ module Nanite
197
191
 
198
192
  def self.json_create(o)
199
193
  i = o['data']
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'])
194
+ new(i['type'], i['payload'], o['size'], { :from => i['from'], :token => i['token'],
195
+ :selector => i['selector'], :target => i['target'],
196
+ :persistent => i['persistent'], :tags => i['tags'] })
205
197
  end
206
198
 
207
199
  def to_s(filter=nil)
208
200
  log_msg = "#{super} <#{token}> #{type}"
209
201
  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))
211
202
  log_msg += ", target #{id_to_s(target)}" if target && (filter.nil? || filter.include?(:target))
212
203
  log_msg += ", tags #{tags.inspect}" if tags && !tags.empty? && (filter.nil? || filter.include?(:tags))
213
204
  log_msg += ", payload #{payload.inspect}" if filter.nil? || filter.include?(:payload)
@@ -369,33 +360,6 @@ module Nanite
369
360
  end
370
361
 
371
362
  end
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
-
363
+
400
364
  end
401
365
 
@@ -1,135 +1,168 @@
1
1
  require 'redis'
2
- require 'redis_tag_store'
3
2
 
4
3
  module Nanite
5
4
  class State
6
5
  include Enumerable
7
6
 
8
- # This class encapsulates the state of a nanite system using redis as the
9
- # data store and a provided tag store. For a nanite with the identity
10
- # 'nanite-foobar' we store the following:
7
+ # this class encapsulates the state of a nanite system using redis as the
8
+ # data store. here is the schema, for each agent we store a number of items,
9
+ # for a nanite with the identity: nanite-foobar we store the following things:
11
10
  #
12
- # nanite-foobar: 0.72 # load average or 'status'
11
+ # nanite-foobar: 0.72 # load average or 'status'
12
+ # s-nanite-foobar: { /foo/bar, /foo/nik } # a SET of the provided services
13
+ # tg-nanite-foobar: { foo-42, customer-12 } # a SET of the tags for this agent
13
14
  # t-nanite-foobar: 123456789 # unix timestamp of the last state update
14
15
  #
15
- # The tag store is used to store the associated services and tags.
16
+ # also we do an inverted index for quick lookup of agents providing a certain
17
+ # service, so for each service the agent provides, we add the nanite to a SET
18
+ # of all the nanites that provide said service:
16
19
  #
17
- # A tag store should provide the following methods:
18
- # - initialize(redis): Initialize tag store, may use provided redis handle
19
- # - services(nanite): Retrieve services implemented by given agent
20
- # - tags(nanite): Retrieve tags implemented by given agent
21
- # - all_services: Retrieve all services implemented by all agents
22
- # - all_tags: Retrieve all tags exposed by all agents
23
- # - store(nanite, services, tags): Store agent's services and tags
24
- # - update(name, new_tags,obsolete_tags): Update agent's tags
25
- # - delete(nanite): Delete all entries associated with given agent
26
- # - nanites_for(service, tags): Retrieve agents implementing given service
27
- # and exposing given tags
20
+ # foo/bar: { nanite-foobar, nanite-nickelbag, nanite-another } # redis SET
28
21
  #
29
- # The default implementation for the tag store reuses Redis.
30
-
31
- def initialize(redis, tag_store=nil)
32
- host, port, tag_store_type = redis.split(':')
22
+ # we do that same thing for tags:
23
+ # some-tag: { nanite-foobar, nanite-nickelbag, nanite-another } # redis SET
24
+ #
25
+ # This way we can do a lookup of what nanites provide a set of services and tags based
26
+ # on redis SET intersection:
27
+ #
28
+ # nanites_for('/gems/list', 'some-tag')
29
+ # => returns a nested array of nanites and their state that provide the intersection
30
+ # of these two service tags
31
+
32
+ def initialize(redis)
33
+ Nanite::Log.info("[setup] initializing redis state: #{redis}")
34
+ host, port = redis.split(':')
33
35
  host ||= '127.0.0.1'
34
36
  port ||= '6379'
35
- tag_store||= 'Nanite::RedisTagStore'
36
- @redis = Redis.new(:host => host, :port => port)
37
- @tag_store = tag_store.to_const.new(@redis)
38
- Nanite::Log.info("[setup] Initializing redis state using host '#{host}', port '#{port}' and tag store #{tag_store}")
37
+ @redis = Redis.new :host => host, :port => port
39
38
  end
40
-
41
- # Retrieve given agent services, tags, status and timestamp
39
+
40
+ def log_redis_error(meth,&blk)
41
+ blk.call
42
+ rescue Exception => e
43
+ Nanite::Log.info("redis error in method: #{meth}")
44
+ raise e
45
+ end
46
+
42
47
  def [](nanite)
43
- log_redis_error do
44
- status = @redis[nanite]
48
+ log_redis_error("[]") do
49
+ status = @redis[nanite]
45
50
  timestamp = @redis["t-#{nanite}"]
46
- services = @tag_store.services(nanite)
47
- tags = @tag_store.tags(nanite)
51
+ services = @redis.set_members("s-#{nanite}")
52
+ tags = @redis.set_members("tg-#{nanite}")
48
53
  return nil unless status && timestamp && services
49
54
  {:services => services, :status => status, :timestamp => timestamp.to_i, :tags => tags}
50
55
  end
51
56
  end
52
-
53
- # Set given attributes for given agent
54
- # Attributes may include services, tags and status
57
+
55
58
  def []=(nanite, attributes)
56
- @tag_store.store(nanite, attributes[:services], attributes[:tags])
57
- update_status(nanite, attributes[:status])
59
+ log_redis_error("[]=") do
60
+ update_state(nanite, attributes[:status], attributes[:services], attributes[:tags])
61
+ end
58
62
  end
59
-
60
- # Delete all information related to given agent
63
+
61
64
  def delete(nanite)
62
- @tag_store.delete(nanite)
63
- log_redis_error do
64
- @redis.delete(nanite)
65
- @redis.delete("t-#{nanite}")
65
+ log_redis_error("delete") do
66
+ (@redis.set_members("s-#{nanite}")||[]).each do |srv|
67
+ @redis.set_delete(srv, nanite)
68
+ if @redis.set_count(srv) == 0
69
+ @redis.delete(srv)
70
+ @redis.set_delete("naniteservices", srv)
71
+ end
72
+ end
73
+ (@redis.set_members("tg-#{nanite}")||[]).each do |tag|
74
+ @redis.set_delete(tag, nanite)
75
+ if @redis.set_count(tag) == 0
76
+ @redis.delete(tag)
77
+ @redis.set_delete("nanitetags", tag)
78
+ end
79
+ end
80
+ @redis.delete nanite
81
+ @redis.delete "s-#{nanite}"
82
+ @redis.delete "t-#{nanite}"
83
+ @redis.delete "tg-#{nanite}"
66
84
  end
67
85
  end
68
-
69
- # Return all services exposed by all agents
86
+
70
87
  def all_services
71
- @tag_store.all_services
88
+ log_redis_error("all_services") do
89
+ @redis.set_members("naniteservices")
90
+ end
72
91
  end
73
92
 
74
- # Return all tags exposed by all agents
75
93
  def all_tags
76
- @tag_store.all_tags
94
+ log_redis_error("all_tags") do
95
+ @redis.set_members("nanitetags")
96
+ end
77
97
  end
78
-
79
- # Update status and timestamp for given agent
80
- def update_status(name, status)
81
- log_redis_error do
82
- @redis[name] = status
83
- @redis["t-#{name}"] = Time.now.utc.to_i
98
+
99
+ def update_state(name, status, services, tags)
100
+ old_services = @redis.set_members("s-#{name}")
101
+ if old_services
102
+ (old_services - services).each do |s|
103
+ @redis.set_delete(s, name)
104
+ @redis.set_delete("naniteservices", s)
105
+ end
106
+ end
107
+ old_tags = @redis.set_members("tg-#{name}")
108
+ if old_tags
109
+ (old_tags - tags).each do |t|
110
+ @redis.set_delete(t, name)
111
+ @redis.set_delete("nanitetags", t)
112
+ end
84
113
  end
114
+ @redis.delete("s-#{name}")
115
+ services.each do |srv|
116
+ @redis.set_add(srv, name)
117
+ @redis.set_add("s-#{name}", srv)
118
+ @redis.set_add("naniteservices", srv)
119
+ end
120
+ @redis.delete("tg-#{name}")
121
+ tags.each do |tag|
122
+ next if tag.nil?
123
+ @redis.set_add(tag, name)
124
+ @redis.set_add("tg-#{name}", tag)
125
+ @redis.set_add("nanitetags", tag)
126
+ end
127
+ update_status(name, status)
85
128
  end
86
129
 
87
- # Update tags for given agent
88
- def update_tags(name, new_tags, obsolete_tags)
89
- @tag_store.update(name, new_tags, obsolete_tags)
130
+ def update_status(name, status)
131
+ @redis[name] = status
132
+ @redis["t-#{name}"] = Time.now.utc.to_i
90
133
  end
91
-
92
- # Return all registered agents
134
+
93
135
  def list_nanites
94
- log_redis_error do
136
+ log_redis_error("list_nanites") do
95
137
  @redis.keys("nanite-*")
96
138
  end
97
139
  end
98
-
99
- # Number of registered agents
140
+
100
141
  def size
101
142
  list_nanites.size
102
143
  end
103
-
104
- # Iterate through all agents, yielding services, tags
105
- # status and timestamp keyed by agent name
144
+
145
+ def clear_state
146
+ log_redis_error("clear_state") do
147
+ @redis.keys("*").each {|k| @redis.delete k}
148
+ end
149
+ end
150
+
106
151
  def each
107
152
  list_nanites.each do |nan|
108
153
  yield nan, self[nan]
109
154
  end
110
155
  end
111
-
112
- # Return agents that implement given service and expose
113
- # all given tags
114
- def nanites_for(from, service, tags)
115
- res = []
116
- @tag_store.nanites_for(from, service, tags).each do |nanite_id|
117
- if nanite = self[nanite_id]
118
- res << [nanite_id, nanite]
156
+
157
+ def nanites_for(service, *tags)
158
+ keys = tags.dup << service
159
+ log_redis_error("nanites_for") do
160
+ res = []
161
+ (@redis.set_intersect(keys)||[]).each do |nan|
162
+ res << [nan, self[nan]]
119
163
  end
164
+ res
120
165
  end
121
- res
122
- end
123
-
124
- private
125
-
126
- # Helper method, catch and log errors
127
- def log_redis_error(&blk)
128
- blk.call
129
- rescue Exception => e
130
- Nanite::Log.warn("redis error in method: #{caller[0]}")
131
- raise e
132
166
  end
133
-
134
167
  end
135
168
  end
@@ -27,26 +27,6 @@ class String
27
27
  def to_const_path
28
28
  snake_case.gsub(/::/, "/")
29
29
  end
30
-
31
- # Convert constant name to constant
32
- #
33
- # "FooBar::Baz".to_const => FooBar::Baz
34
- #
35
- # @return [Constant] Constant corresponding to given name or nil if no
36
- # constant with that name exists
37
- #
38
- # @api public
39
- def to_const
40
- names = split('::')
41
- names.shift if names.empty? || names.first.empty?
42
-
43
- constant = Object
44
- names.each do |name|
45
- # modified to return nil instead of raising an const_missing error
46
- constant = constant && constant.const_defined?(name) ? constant.const_get(name) : nil
47
- end
48
- constant
49
- end
50
30
  end
51
31
 
52
32
  class Object
@@ -75,4 +55,4 @@ class Object
75
55
  end
76
56
  end
77
57
  end
78
- end
58
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nanite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1.10
4
+ version: 0.4.1.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ezra Zygmuntowicz
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-06 00:00:00 -08:00
12
+ date: 2009-11-18 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -22,6 +22,16 @@ dependencies:
22
22
  - !ruby/object:Gem::Version
23
23
  version: 0.6.0
24
24
  version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: json
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.1.7
34
+ version:
25
35
  description: self assembling fabric of ruby daemons
26
36
  email: ezra@engineyard.com
27
37
  executables:
@@ -39,7 +49,6 @@ files:
39
49
  - README.rdoc
40
50
  - Rakefile
41
51
  - TODO
42
- - lib/nanite
43
52
  - lib/nanite/actor.rb
44
53
  - lib/nanite/actor_registry.rb
45
54
  - lib/nanite/admin.rb
@@ -52,7 +61,6 @@ files:
52
61
  - lib/nanite/identity.rb
53
62
  - lib/nanite/job.rb
54
63
  - lib/nanite/local_state.rb
55
- - lib/nanite/log
56
64
  - lib/nanite/log/formatter.rb
57
65
  - lib/nanite/log.rb
58
66
  - lib/nanite/mapper.rb
@@ -61,8 +69,6 @@ files:
61
69
  - lib/nanite/packets.rb
62
70
  - lib/nanite/pid_file.rb
63
71
  - lib/nanite/reaper.rb
64
- - lib/nanite/redis_tag_store.rb
65
- - lib/nanite/security
66
72
  - lib/nanite/security/cached_certificate_store_proxy.rb
67
73
  - lib/nanite/security/certificate.rb
68
74
  - lib/nanite/security/certificate_cache.rb
@@ -83,6 +89,8 @@ files:
83
89
  - bin/nanite-mapper
84
90
  has_rdoc: true
85
91
  homepage: http://github.com/ezmobius/nanite
92
+ licenses: []
93
+
86
94
  post_install_message:
87
95
  rdoc_options: []
88
96
 
@@ -103,9 +111,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
111
  requirements: []
104
112
 
105
113
  rubyforge_project:
106
- rubygems_version: 1.3.1
114
+ rubygems_version: 1.3.5
107
115
  signing_key:
108
- specification_version: 2
116
+ specification_version: 3
109
117
  summary: self assembling fabric of ruby daemons
110
118
  test_files: []
111
119
 
@@ -1,141 +0,0 @@
1
- require 'redis'
2
-
3
- module Nanite
4
-
5
- # Implementation of a tag store on top of Redis
6
- # For a nanite with the identity 'nanite-foobar', we store the following:
7
- #
8
- # s-nanite-foobar: { /foo/bar, /foo/nik } # a SET of the provided services
9
- # tg-nanite-foobar: { foo-42, customer-12 } # a SET of the tags for this agent
10
- #
11
- # Also we do an inverted index for quick lookup of agents providing a certain
12
- # service, so for each service the agent provides, we add the nanite to a SET
13
- # of all the nanites that provide said service:
14
- #
15
- # foo/bar: { nanite-foobar, nanite-nickelbag, nanite-another } # redis SET
16
- #
17
- # We do that same thing for tags:
18
- #
19
- # some-tag: { nanite-foobar, nanite-nickelbag, nanite-another } # redis SET
20
- #
21
- # This way we can do a lookup of what nanites provide a set of services and tags based
22
- # on redis SET intersection:
23
- #
24
- # nanites_for('/gems/list', 'some-tag')
25
- # => returns an array of nanites that provide the intersection of these two service tags
26
-
27
- class RedisTagStore
28
-
29
- # Initialize tag store with given redis handle
30
- def initialize(redis)
31
- @redis = redis
32
- end
33
-
34
- # Store services and tags for given agent
35
- def store(nanite, services, tags)
36
- services = nil if services.compact.empty?
37
- tags = nil if tags.compact.empty?
38
- log_redis_error do
39
- if services
40
- obsolete_services = @redis.set_members("s-#{nanite}") - services
41
- update_elems(nanite, services, obsolete_services, "s-#{nanite}", 'naniteservices')
42
- end
43
- if tags
44
- obsolete_tags = @redis.set_members("tg-#{nanite}") - tags
45
- update_elems(nanite, tags, obsolete_tags, "tg-#{nanite}", 'nanitestags')
46
- end
47
- end
48
- end
49
-
50
- # Update tags for given agent
51
- def update(nanite, new_tags, obsolete_tags)
52
- update_elems(nanite, new_tags, obsolete_tags, "tg-#{nanite}", 'nanitestags')
53
- end
54
-
55
- # Delete services and tags for given agent
56
- def delete(nanite)
57
- delete_elems(nanite, "s-#{nanite}", 'naniteservices')
58
- delete_elems(nanite, "tg-#{nanite}", 'nanitestags')
59
- end
60
-
61
- # Services implemented by given agent
62
- def services(nanite)
63
- @redis.set_members("s-#{nanite}")
64
- end
65
-
66
- # Tags exposed by given agent
67
- def tags(nanite)
68
- @redis.set_members("tg-#{nanite}")
69
- end
70
-
71
- # Retrieve all agents services
72
- def all_services
73
- log_redis_error do
74
- @redis.set_members('naniteservices')
75
- end
76
- end
77
-
78
- # Retrieve all agents tags
79
- def all_tags
80
- log_redis_error do
81
- @redis.set_members('nanitetags')
82
- end
83
- end
84
-
85
- # Retrieve nanites implementing given service and exposing given tags
86
- def nanites_for(from, service, tags)
87
- keys = tags && tags.dup || []
88
- keys << service
89
- log_redis_error do
90
- @redis.set_intersect(keys.compact)
91
- end
92
- end
93
-
94
- private
95
-
96
- # Update values stored for given agent
97
- # Also store reverse lookup information using both a unique and
98
- # a global key (so it's possible to retrieve that agent value or
99
- # all related values)
100
- def update_elems(nanite, new_tags, obsolete_tags, elem_key, global_key)
101
- new_tags = nil if new_tags.compact.empty?
102
- obsolete_tags = nil if obsolete_tags.compact.empty?
103
- log_redis_error do
104
- obsolete_tags.each do |val|
105
- @redis.set_delete(val, nanite)
106
- @redis.set_delete(elem_key, val)
107
- @redis.set_delete(global_key, val)
108
- end if obsolete_tags
109
- new_tags.each do |val|
110
- @redis.set_add(val, nanite)
111
- @redis.set_add(elem_key, val)
112
- @redis.set_add(global_key, val)
113
- end if new_tags
114
- end
115
- end
116
-
117
- # Delete all values for given nanite agent
118
- # Also delete reverse lookup information
119
- def delete_elems(nanite, elem_key, global_key)
120
- log_redis_error do
121
- (@redis.set_members(elem_key)||[]).each do |val|
122
- @redis.set_delete(val, nanite)
123
- if @redis.set_count(val) == 0
124
- @redis.delete(val)
125
- @redis.set_delete(global_key, val)
126
- end
127
- end
128
- @redis.delete(elem_key)
129
- end
130
- end
131
-
132
- # Helper method, catch and log errors
133
- def log_redis_error(&blk)
134
- blk.call
135
- rescue Exception => e
136
- Nanite::Log.warn("redis error in method: #{caller[0]}")
137
- raise e
138
- end
139
-
140
- end
141
- end