nanite 0.4.1.10 → 0.4.1.13

Sign up to get free protection for your applications and to get access to all the features.
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