shift-nanite 0.4.1.2
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/LICENSE +201 -0
- data/README.rdoc +430 -0
- data/Rakefile +76 -0
- data/TODO +24 -0
- data/bin/nanite-admin +65 -0
- data/bin/nanite-agent +79 -0
- data/bin/nanite-mapper +50 -0
- data/lib/nanite.rb +74 -0
- data/lib/nanite/actor.rb +71 -0
- data/lib/nanite/actor_registry.rb +26 -0
- data/lib/nanite/admin.rb +138 -0
- data/lib/nanite/agent.rb +264 -0
- data/lib/nanite/amqp.rb +58 -0
- data/lib/nanite/cluster.rb +250 -0
- data/lib/nanite/config.rb +112 -0
- data/lib/nanite/console.rb +39 -0
- data/lib/nanite/daemonize.rb +13 -0
- data/lib/nanite/identity.rb +16 -0
- data/lib/nanite/job.rb +104 -0
- data/lib/nanite/local_state.rb +38 -0
- data/lib/nanite/log.rb +66 -0
- data/lib/nanite/log/formatter.rb +39 -0
- data/lib/nanite/mapper.rb +309 -0
- data/lib/nanite/mapper_proxy.rb +67 -0
- data/lib/nanite/nanite_dispatcher.rb +92 -0
- data/lib/nanite/packets.rb +365 -0
- data/lib/nanite/pid_file.rb +52 -0
- data/lib/nanite/reaper.rb +39 -0
- data/lib/nanite/security/cached_certificate_store_proxy.rb +24 -0
- data/lib/nanite/security/certificate.rb +55 -0
- data/lib/nanite/security/certificate_cache.rb +66 -0
- data/lib/nanite/security/distinguished_name.rb +34 -0
- data/lib/nanite/security/encrypted_document.rb +46 -0
- data/lib/nanite/security/rsa_key_pair.rb +53 -0
- data/lib/nanite/security/secure_serializer.rb +68 -0
- data/lib/nanite/security/signature.rb +46 -0
- data/lib/nanite/security/static_certificate_store.rb +35 -0
- data/lib/nanite/security_provider.rb +47 -0
- data/lib/nanite/serializer.rb +52 -0
- data/lib/nanite/state.rb +168 -0
- data/lib/nanite/streaming.rb +125 -0
- data/lib/nanite/util.rb +58 -0
- data/spec/actor_registry_spec.rb +60 -0
- data/spec/actor_spec.rb +77 -0
- data/spec/agent_spec.rb +240 -0
- data/spec/cached_certificate_store_proxy_spec.rb +34 -0
- data/spec/certificate_cache_spec.rb +49 -0
- data/spec/certificate_spec.rb +27 -0
- data/spec/cluster_spec.rb +622 -0
- data/spec/distinguished_name_spec.rb +24 -0
- data/spec/encrypted_document_spec.rb +21 -0
- data/spec/job_spec.rb +251 -0
- data/spec/local_state_spec.rb +130 -0
- data/spec/nanite_dispatcher_spec.rb +136 -0
- data/spec/packet_spec.rb +220 -0
- data/spec/rsa_key_pair_spec.rb +33 -0
- data/spec/secure_serializer_spec.rb +41 -0
- data/spec/serializer_spec.rb +107 -0
- data/spec/signature_spec.rb +30 -0
- data/spec/spec_helper.rb +33 -0
- data/spec/static_certificate_store_spec.rb +30 -0
- data/spec/util_spec.rb +63 -0
- metadata +129 -0
| @@ -0,0 +1,250 @@ | |
| 1 | 
            +
            module Nanite
         | 
| 2 | 
            +
              class Cluster
         | 
| 3 | 
            +
                attr_reader :agent_timeout, :nanites, :reaper, :serializer, :identity, :amq, :redis, :mapper, :callbacks
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def initialize(amq, agent_timeout, identity, serializer, mapper, state_configuration=nil, callbacks = {})
         | 
| 6 | 
            +
                  @amq = amq
         | 
| 7 | 
            +
                  @agent_timeout = agent_timeout
         | 
| 8 | 
            +
                  @identity = identity
         | 
| 9 | 
            +
                  @serializer = serializer
         | 
| 10 | 
            +
                  @mapper = mapper
         | 
| 11 | 
            +
                  @state = state_configuration
         | 
| 12 | 
            +
                  @security = SecurityProvider.get
         | 
| 13 | 
            +
                  @callbacks = callbacks
         | 
| 14 | 
            +
                  setup_state
         | 
| 15 | 
            +
                  @reaper = Reaper.new(agent_timeout)
         | 
| 16 | 
            +
                  setup_queues
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                # determine which nanites should receive the given request
         | 
| 20 | 
            +
                def targets_for(request)
         | 
| 21 | 
            +
                  return [request.target] if request.target
         | 
| 22 | 
            +
                  __send__(request.selector, request.type, request.tags).collect {|name, state| name }
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                # adds nanite to nanites map: key is nanite's identity
         | 
| 26 | 
            +
                # and value is a services/status pair implemented
         | 
| 27 | 
            +
                # as a hash
         | 
| 28 | 
            +
                def register(reg)
         | 
| 29 | 
            +
                  case reg
         | 
| 30 | 
            +
                  when Register
         | 
| 31 | 
            +
                    if @security.authorize_registration(reg)
         | 
| 32 | 
            +
                      Nanite::Log.info("RECV #{reg.to_s}")
         | 
| 33 | 
            +
                      nanites[reg.identity] = { :services => reg.services, :status => reg.status, :tags => reg.tags, :timestamp => Time.now.utc.to_i }
         | 
| 34 | 
            +
                      reaper.register(reg.identity, agent_timeout + 1) { nanite_timed_out(reg.identity) }
         | 
| 35 | 
            +
                      callbacks[:register].call(reg.identity, mapper) if callbacks[:register]
         | 
| 36 | 
            +
                    else
         | 
| 37 | 
            +
                      Nanite::Log.warn("RECV NOT AUTHORIZED #{reg.to_s}")
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
                  when UnRegister
         | 
| 40 | 
            +
                    Nanite::Log.info("RECV #{reg.to_s}")
         | 
| 41 | 
            +
                    reaper.unregister(reg.identity)
         | 
| 42 | 
            +
                    nanites.delete(reg.identity)
         | 
| 43 | 
            +
                    callbacks[:unregister].call(reg.identity, mapper) if callbacks[:unregister]
         | 
| 44 | 
            +
                  else
         | 
| 45 | 
            +
                    Nanite::Log.warn("RECV [register] Invalid packet type: #{reg.class}")
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def nanite_timed_out(token)
         | 
| 50 | 
            +
                  nanite = nanites[token]
         | 
| 51 | 
            +
                  if nanite && timed_out?(nanite)
         | 
| 52 | 
            +
                    Nanite::Log.info("Nanite #{token} timed out")
         | 
| 53 | 
            +
                    nanite = nanites.delete(token)
         | 
| 54 | 
            +
                    callbacks[:timeout].call(token, mapper) if callbacks[:timeout]
         | 
| 55 | 
            +
                    true
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
                
         | 
| 59 | 
            +
                def route(request, targets)
         | 
| 60 | 
            +
                  EM.next_tick { targets.map { |target| publish(request, target) } }
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                def publish(request, target)
         | 
| 64 | 
            +
                  # We need to initialize the 'target' field of the request object so that the serializer has
         | 
| 65 | 
            +
                  # access to it.
         | 
| 66 | 
            +
                  begin
         | 
| 67 | 
            +
                    old_target = request.target
         | 
| 68 | 
            +
                    request.target = target unless target == 'mapper-offline'
         | 
| 69 | 
            +
                    Nanite::Log.info("SEND #{request.to_s([:from, :tags, :target])}")
         | 
| 70 | 
            +
                    amq.queue(target).publish(serializer.dump(request), :persistent => request.persistent)
         | 
| 71 | 
            +
                  ensure
         | 
| 72 | 
            +
                    request.target = old_target
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                protected
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                # updates nanite information (last ping timestamps, status)
         | 
| 79 | 
            +
                # when heartbeat message is received
         | 
| 80 | 
            +
                def handle_ping(ping)
         | 
| 81 | 
            +
                  begin
         | 
| 82 | 
            +
                    if nanite = nanites[ping.identity]
         | 
| 83 | 
            +
                      nanites.update_status(ping.identity, ping.status)
         | 
| 84 | 
            +
                      reaper.update(ping.identity, agent_timeout + 1) { nanite_timed_out(ping.identity) }
         | 
| 85 | 
            +
                    else
         | 
| 86 | 
            +
                      packet = Advertise.new
         | 
| 87 | 
            +
                      Nanite::Log.info("SEND #{packet.to_s} to #{ping.identity}")
         | 
| 88 | 
            +
                      amq.queue(ping.identity).publish(serializer.dump(packet))
         | 
| 89 | 
            +
                    end
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
                
         | 
| 93 | 
            +
                # forward request coming from agent
         | 
| 94 | 
            +
                def handle_request(request)
         | 
| 95 | 
            +
                  if @security.authorize_request(request)
         | 
| 96 | 
            +
                    Nanite::Log.info("RECV #{request.to_s([:from, :target, :tags])}") unless Nanite::Log.level == :debug
         | 
| 97 | 
            +
                    Nanite::Log.debug("RECV #{request.to_s}")
         | 
| 98 | 
            +
                    case request
         | 
| 99 | 
            +
                    when Push
         | 
| 100 | 
            +
                      mapper.send_push(request)
         | 
| 101 | 
            +
                    else
         | 
| 102 | 
            +
                      intm_handler = lambda do |result, job|
         | 
| 103 | 
            +
                        result = IntermediateMessage.new(request.token, job.request.from, mapper.identity, nil, result)
         | 
| 104 | 
            +
                        forward_response(result, request.persistent)
         | 
| 105 | 
            +
                      end
         | 
| 106 | 
            +
                    
         | 
| 107 | 
            +
                      result = Result.new(request.token, request.from, nil, mapper.identity)
         | 
| 108 | 
            +
                      ok = mapper.send_request(request, :intermediate_handler => intm_handler) do |res|
         | 
| 109 | 
            +
                        result.results = res
         | 
| 110 | 
            +
                        forward_response(result, request.persistent)
         | 
| 111 | 
            +
                      end
         | 
| 112 | 
            +
                      
         | 
| 113 | 
            +
                      if ok == false
         | 
| 114 | 
            +
                        forward_response(result, request.persistent)
         | 
| 115 | 
            +
                      end
         | 
| 116 | 
            +
                    end
         | 
| 117 | 
            +
                  else
         | 
| 118 | 
            +
                    Nanite::Log.warn("RECV NOT AUTHORIZED #{request.to_s}")
         | 
| 119 | 
            +
                  end
         | 
| 120 | 
            +
                end
         | 
| 121 | 
            +
                
         | 
| 122 | 
            +
                # forward response back to agent that originally made the request
         | 
| 123 | 
            +
                def forward_response(res, persistent)
         | 
| 124 | 
            +
                  Nanite::Log.info("SEND #{res.to_s([:to])}")
         | 
| 125 | 
            +
                  amq.queue(res.to).publish(serializer.dump(res), :persistent => persistent)
         | 
| 126 | 
            +
                end
         | 
| 127 | 
            +
                
         | 
| 128 | 
            +
                # returns least loaded nanite that provides given service
         | 
| 129 | 
            +
                def least_loaded(service, tags=[])
         | 
| 130 | 
            +
                  candidates = nanites_providing(service,tags)
         | 
| 131 | 
            +
                  return [] if candidates.empty?
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                  [candidates.min { |a,b| a[1][:status] <=> b[1][:status] }]
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                # returns all nanites that provide given service
         | 
| 137 | 
            +
                def all(service, tags=[])
         | 
| 138 | 
            +
                  nanites_providing(service,tags)
         | 
| 139 | 
            +
                end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                # returns a random nanite
         | 
| 142 | 
            +
                def random(service, tags=[])
         | 
| 143 | 
            +
                  candidates = nanites_providing(service,tags)
         | 
| 144 | 
            +
                  return [] if candidates.empty?
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                  [candidates[rand(candidates.size)]]
         | 
| 147 | 
            +
                end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                # selects next nanite that provides given service
         | 
| 150 | 
            +
                # using round robin rotation
         | 
| 151 | 
            +
                def rr(service, tags=[])
         | 
| 152 | 
            +
                  @last ||= {}
         | 
| 153 | 
            +
                  @last[service] ||= 0
         | 
| 154 | 
            +
                  candidates = nanites_providing(service,tags)
         | 
| 155 | 
            +
                  return [] if candidates.empty?
         | 
| 156 | 
            +
                  @last[service] = 0 if @last[service] >= candidates.size
         | 
| 157 | 
            +
                  candidate = candidates[@last[service]]
         | 
| 158 | 
            +
                  @last[service] += 1
         | 
| 159 | 
            +
                  [candidate]
         | 
| 160 | 
            +
                end
         | 
| 161 | 
            +
                
         | 
| 162 | 
            +
                def timed_out?(nanite)
         | 
| 163 | 
            +
                  nanite[:timestamp].to_i < (Time.now.utc - agent_timeout).to_i
         | 
| 164 | 
            +
                end
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                # returns all nanites that provide the given service
         | 
| 167 | 
            +
                def nanites_providing(service, *tags)
         | 
| 168 | 
            +
                  nanites.nanites_for(service, *tags).delete_if do |nanite| 
         | 
| 169 | 
            +
                    if timed_out?(nanite[1])
         | 
| 170 | 
            +
                      Nanite::Log.debug("Ignoring timed out nanite #{nanite[0]} in target selection - last seen at #{nanite[1][:timestamp]}")
         | 
| 171 | 
            +
                    end
         | 
| 172 | 
            +
                  end
         | 
| 173 | 
            +
                end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                def setup_queues
         | 
| 176 | 
            +
                  setup_heartbeat_queue
         | 
| 177 | 
            +
                  setup_registration_queue
         | 
| 178 | 
            +
                  setup_request_queue
         | 
| 179 | 
            +
                end
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                def setup_heartbeat_queue
         | 
| 182 | 
            +
                  handler = lambda do |ping|
         | 
| 183 | 
            +
                    begin
         | 
| 184 | 
            +
                      ping = serializer.load(ping)
         | 
| 185 | 
            +
                      Nanite::Log.debug("RECV #{ping.to_s}") if ping.respond_to?(:to_s)
         | 
| 186 | 
            +
                      handle_ping(ping)
         | 
| 187 | 
            +
                    rescue Exception => e
         | 
| 188 | 
            +
                      Nanite::Log.error("RECV [ping] #{e.message}")
         | 
| 189 | 
            +
                    end
         | 
| 190 | 
            +
                  end
         | 
| 191 | 
            +
                  hb_fanout = amq.fanout('heartbeat', :durable => true)
         | 
| 192 | 
            +
                  if shared_state?
         | 
| 193 | 
            +
                    amq.queue("heartbeat").bind(hb_fanout).subscribe &handler
         | 
| 194 | 
            +
                  else
         | 
| 195 | 
            +
                    amq.queue("heartbeat-#{identity}", :exclusive => true).bind(hb_fanout).subscribe &handler
         | 
| 196 | 
            +
                  end
         | 
| 197 | 
            +
                end
         | 
| 198 | 
            +
             | 
| 199 | 
            +
                def setup_registration_queue
         | 
| 200 | 
            +
                  handler = lambda do |msg|
         | 
| 201 | 
            +
                    begin
         | 
| 202 | 
            +
                      register(serializer.load(msg))
         | 
| 203 | 
            +
                    rescue Exception => e
         | 
| 204 | 
            +
                      Nanite::Log.error("RECV [register] #{e.message}")
         | 
| 205 | 
            +
                    end
         | 
| 206 | 
            +
                  end
         | 
| 207 | 
            +
                  reg_fanout = amq.fanout('registration', :durable => true)
         | 
| 208 | 
            +
                  if shared_state?
         | 
| 209 | 
            +
                    amq.queue("registration").bind(reg_fanout).subscribe &handler
         | 
| 210 | 
            +
                  else
         | 
| 211 | 
            +
                    amq.queue("registration-#{identity}", :exclusive => true).bind(reg_fanout).subscribe &handler
         | 
| 212 | 
            +
                  end
         | 
| 213 | 
            +
                end
         | 
| 214 | 
            +
                
         | 
| 215 | 
            +
                def setup_request_queue
         | 
| 216 | 
            +
                  handler = lambda do |msg|
         | 
| 217 | 
            +
                    begin
         | 
| 218 | 
            +
                      handle_request(serializer.load(msg))
         | 
| 219 | 
            +
                    rescue Exception => e
         | 
| 220 | 
            +
                      Nanite::Log.error("RECV [request] #{e.message}")
         | 
| 221 | 
            +
                    end
         | 
| 222 | 
            +
                  end
         | 
| 223 | 
            +
                  req_fanout = amq.fanout('request', :durable => true)
         | 
| 224 | 
            +
                  if shared_state?
         | 
| 225 | 
            +
                    amq.queue("request").bind(req_fanout).subscribe &handler
         | 
| 226 | 
            +
                  else
         | 
| 227 | 
            +
                    amq.queue("request-#{identity}", :exclusive => true).bind(req_fanout).subscribe &handler
         | 
| 228 | 
            +
                  end
         | 
| 229 | 
            +
                end
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                def setup_state
         | 
| 232 | 
            +
                  case @state
         | 
| 233 | 
            +
                  when String
         | 
| 234 | 
            +
                    # backwards compatibility, we assume redis if the configuration option
         | 
| 235 | 
            +
                    # was a string
         | 
| 236 | 
            +
                    Nanite::Log.info("[setup] using redis for state storage")
         | 
| 237 | 
            +
                    require 'nanite/state'
         | 
| 238 | 
            +
                    @nanites = Nanite::State.new(@state)
         | 
| 239 | 
            +
                  when Hash
         | 
| 240 | 
            +
                  else
         | 
| 241 | 
            +
                    require 'nanite/local_state'
         | 
| 242 | 
            +
                    @nanites = Nanite::LocalState.new
         | 
| 243 | 
            +
                  end
         | 
| 244 | 
            +
                end
         | 
| 245 | 
            +
                
         | 
| 246 | 
            +
                def shared_state?
         | 
| 247 | 
            +
                  !@state.nil?
         | 
| 248 | 
            +
                end
         | 
| 249 | 
            +
              end
         | 
| 250 | 
            +
            end
         | 
| @@ -0,0 +1,112 @@ | |
| 1 | 
            +
            module Nanite
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              COMMON_DEFAULT_OPTIONS = {
         | 
| 4 | 
            +
                :pass => 'testing',
         | 
| 5 | 
            +
                :vhost => '/nanite',
         | 
| 6 | 
            +
                :secure => false,
         | 
| 7 | 
            +
                :host => '0.0.0.0',
         | 
| 8 | 
            +
                :log_level => :info,
         | 
| 9 | 
            +
                :format => :marshal,
         | 
| 10 | 
            +
                :daemonize => false,
         | 
| 11 | 
            +
                :console => false,
         | 
| 12 | 
            +
                :root => Dir.pwd,
         | 
| 13 | 
            +
                :insist => true
         | 
| 14 | 
            +
              }
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              module CommonConfig
         | 
| 17 | 
            +
                def setup_mapper_options(opts, options)
         | 
| 18 | 
            +
                  setup_common_options(opts, options, 'mapper')
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  opts.on("-a", "--agent-timeout TIMEOUT", "How long to wait before an agent is considered to be offline and thus removed from the list of available agents.") do |timeout|
         | 
| 21 | 
            +
                    options[:agent_timeout] = timeout
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  opts.on("-r", "--offline-redelivery-frequency", "The frequency in seconds that messages stored in the offline queue will be retrieved for attempted redelivery to the nanites. Default is 10 seconds.") do |frequency|
         | 
| 25 | 
            +
                    options[:offline_redelivery_frequency] = frequency
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  opts.on("--persistent", "Instructs the AMQP broker to save messages to persistent storage so that they aren't lost when the broker is restarted. Can be overriden on a per-message basis using the request and push methods.") do
         | 
| 29 | 
            +
                    options[:persistent] = true
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  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
         | 
| 33 | 
            +
                    options[:offline_failsafe] = true
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                  
         | 
| 36 | 
            +
                  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|
         | 
| 37 | 
            +
                    redishost, redisport = redis.split(':')
         | 
| 38 | 
            +
                    redishost = '127.0.0.1' if (redishost.nil? || redishost.empty?)
         | 
| 39 | 
            +
                    redisport = '6379' if (redishost.nil? || redishost.empty?)
         | 
| 40 | 
            +
                    redis = "#{redishost}:#{redisport}"
         | 
| 41 | 
            +
                    options[:redis] = redis
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
                  
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
                
         | 
| 46 | 
            +
                def setup_common_options(opts, options, type)
         | 
| 47 | 
            +
                  opts.version = Nanite::VERSION
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  opts.on("-i", "--irb-console", "Start #{type} in irb console mode.") do |console|
         | 
| 50 | 
            +
                    options[:console] = 'irb'
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  opts.on("-u", "--user USER", "Specify the rabbitmq username.") do |user|
         | 
| 54 | 
            +
                    options[:user] = user
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  opts.on("-h", "--host HOST", "Specify the rabbitmq hostname.") do |host|
         | 
| 58 | 
            +
                    options[:host] = host
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  opts.on("-P", "--port PORT", "Specify the rabbitmq PORT, default 5672.") do |port|
         | 
| 62 | 
            +
                    options[:port] = port
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  opts.on("-p", "--pass PASSWORD", "Specify the rabbitmq password") do |pass|
         | 
| 66 | 
            +
                    options[:pass] = pass
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  opts.on("-t", "--token IDENITY", "Specify the #{type} identity.") do |ident|
         | 
| 70 | 
            +
                    options[:identity] = ident
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                  opts.on("-v", "--vhost VHOST", "Specify the rabbitmq vhost") do |vhost|
         | 
| 74 | 
            +
                    options[:vhost] = vhost
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                  opts.on("-s", "--secure", "Use Security features of rabbitmq to restrict nanites to themselves") do
         | 
| 78 | 
            +
                    options[:secure] = true
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  opts.on("-f", "--format FORMAT", "The serialization type to use for transfering data. Can be marshal, json or yaml. Default is marshal") do |fmt|
         | 
| 82 | 
            +
                    options[:format] = fmt.to_sym
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  opts.on("-d", "--daemonize", "Run #{type} as a daemon") do |d|
         | 
| 86 | 
            +
                    options[:daemonize] = true
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
                  
         | 
| 89 | 
            +
                  opts.on("--pid-dir PATH", "Specify the pid path, only used with daemonize") do |dir|
         | 
| 90 | 
            +
                    options[:pid_dir] = dir
         | 
| 91 | 
            +
                  end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  opts.on("-l", "--log-level LEVEL", "Specify the log level (fatal, error, warn, info, debug). Default is info") do |level|
         | 
| 94 | 
            +
                    options[:log_level] = level
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
                  
         | 
| 97 | 
            +
                  opts.on("--log-dir PATH", "Specify the log path") do |dir|
         | 
| 98 | 
            +
                    options[:log_dir] = dir
         | 
| 99 | 
            +
                  end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                  opts.on("--tag TAG", "Specify a tag.  Can issue multiple times.") do |tag|
         | 
| 102 | 
            +
                    options[:tag] ||= []
         | 
| 103 | 
            +
                    options[:tag] << tag
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                  opts.on("--version", "Show the nanite version number") do |res|
         | 
| 107 | 
            +
                    puts "Nanite Version #{opts.version}"
         | 
| 108 | 
            +
                    exit
         | 
| 109 | 
            +
                  end
         | 
| 110 | 
            +
                end
         | 
| 111 | 
            +
              end
         | 
| 112 | 
            +
            end
         | 
| @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            module Nanite
         | 
| 2 | 
            +
              module ConsoleHelper
         | 
| 3 | 
            +
                def self.included(base)
         | 
| 4 | 
            +
                  @@base = base
         | 
| 5 | 
            +
                end
         | 
| 6 | 
            +
                
         | 
| 7 | 
            +
                def start_console
         | 
| 8 | 
            +
                  puts "Starting #{@@base.name.split(":").last.downcase} console (#{self.identity}) (Nanite #{Nanite::VERSION})"
         | 
| 9 | 
            +
                  Thread.new do
         | 
| 10 | 
            +
                    Console.start(self)
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
              
         | 
| 15 | 
            +
              module Console
         | 
| 16 | 
            +
                class << self; attr_accessor :instance; end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def self.start(binding)
         | 
| 19 | 
            +
                  require 'irb'
         | 
| 20 | 
            +
                  old_args = ARGV.dup
         | 
| 21 | 
            +
                  ARGV.replace ["--simple-prompt"]
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  IRB.setup(nil)
         | 
| 24 | 
            +
                  self.instance = IRB::Irb.new(IRB::WorkSpace.new(binding))
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  @CONF = IRB.instance_variable_get(:@CONF)
         | 
| 27 | 
            +
                  @CONF[:IRB_RC].call self.instance.context if @CONF[:IRB_RC]
         | 
| 28 | 
            +
                  @CONF[:MAIN_CONTEXT] = self.instance.context
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  catch(:IRB_EXIT) { self.instance.eval_input }
         | 
| 31 | 
            +
                ensure
         | 
| 32 | 
            +
                  ARGV.replace old_args
         | 
| 33 | 
            +
                  # Clean up tty settings in some evil, evil cases
         | 
| 34 | 
            +
                  begin; catch(:IRB_EXIT) { irb_exit }; rescue Exception; end
         | 
| 35 | 
            +
                  # Make nanite exit when irb does
         | 
| 36 | 
            +
                  EM.stop if EM.reactor_running?
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
            end
         | 
| @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            module Nanite
         | 
| 2 | 
            +
              module DaemonizeHelper
         | 
| 3 | 
            +
                def daemonize(identity, options = {})
         | 
| 4 | 
            +
                  exit if fork
         | 
| 5 | 
            +
                  Process.setsid
         | 
| 6 | 
            +
                  exit if fork
         | 
| 7 | 
            +
                  STDIN.reopen "/dev/null"
         | 
| 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 | 
            +
                end
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
            end
         | 
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            module Nanite
         | 
| 2 | 
            +
              module Identity
         | 
| 3 | 
            +
                def self.generate
         | 
| 4 | 
            +
                  values = [
         | 
| 5 | 
            +
                    rand(0x0010000),
         | 
| 6 | 
            +
                    rand(0x0010000),
         | 
| 7 | 
            +
                    rand(0x0010000),
         | 
| 8 | 
            +
                    rand(0x0010000),
         | 
| 9 | 
            +
                    rand(0x0010000),
         | 
| 10 | 
            +
                    rand(0x1000000),
         | 
| 11 | 
            +
                    rand(0x1000000),
         | 
| 12 | 
            +
                  ]
         | 
| 13 | 
            +
                  "%04x%04x%04x%04x%04x%06x%06x" % values
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
            end
         | 
    
        data/lib/nanite/job.rb
    ADDED
    
    | @@ -0,0 +1,104 @@ | |
| 1 | 
            +
            module Nanite
         | 
| 2 | 
            +
              class JobWarden
         | 
| 3 | 
            +
                attr_reader :serializer, :jobs
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def initialize(serializer)
         | 
| 6 | 
            +
                  @serializer = serializer
         | 
| 7 | 
            +
                  @jobs = {}
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def new_job(request, targets, inthandler = nil, blk = nil)
         | 
| 11 | 
            +
                  job = Job.new(request, targets, inthandler, blk)
         | 
| 12 | 
            +
                  jobs[job.token] = job
         | 
| 13 | 
            +
                  job
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def process(msg)
         | 
| 17 | 
            +
                  if job = jobs[msg.token]
         | 
| 18 | 
            +
                    job.process(msg)
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    if job.intermediate_handler && (job.pending_keys.size > 0)
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                      unless job.pending_keys.size == 1
         | 
| 23 | 
            +
                        raise "IntermediateMessages are currently dispatched as they arrive, shouldn't have more than one key in pending_keys: #{job.pending_keys.inspect}"
         | 
| 24 | 
            +
                      end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                      key = job.pending_keys.first
         | 
| 27 | 
            +
                      handler = job.intermediate_handler_for_key(key)
         | 
| 28 | 
            +
                      if handler
         | 
| 29 | 
            +
                        case handler.arity
         | 
| 30 | 
            +
                        when 2
         | 
| 31 | 
            +
                          handler.call(job.intermediate_state[msg.from][key].last, job)
         | 
| 32 | 
            +
                        when 3
         | 
| 33 | 
            +
                          handler.call(key, msg.from, job.intermediate_state[msg.from][key].last)
         | 
| 34 | 
            +
                        when 4
         | 
| 35 | 
            +
                          handler.call(key, msg.from, job.intermediate_state[msg.from][key].last, job)
         | 
| 36 | 
            +
                        end
         | 
| 37 | 
            +
                      end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                      job.reset_pending_intermediate_state_keys
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    if job.completed?
         | 
| 43 | 
            +
                      jobs.delete(job.token)
         | 
| 44 | 
            +
                      if job.completed
         | 
| 45 | 
            +
                        case job.completed.arity
         | 
| 46 | 
            +
                        when 1
         | 
| 47 | 
            +
                          job.completed.call(job.results)
         | 
| 48 | 
            +
                        when 2
         | 
| 49 | 
            +
                          job.completed.call(job.results, job)
         | 
| 50 | 
            +
                        end
         | 
| 51 | 
            +
                      end
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
              end # JobWarden
         | 
| 56 | 
            +
             | 
| 57 | 
            +
              class Job
         | 
| 58 | 
            +
                attr_reader :results, :request, :token, :completed, :intermediate_state, :pending_keys, :intermediate_handler
         | 
| 59 | 
            +
                attr_accessor :targets # This can be updated when a request gets picked up from the offline queue
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def initialize(request, targets, inthandler = nil, blk = nil)
         | 
| 62 | 
            +
                  @request = request
         | 
| 63 | 
            +
                  @targets = targets
         | 
| 64 | 
            +
                  @token = @request.token
         | 
| 65 | 
            +
                  @results = {}
         | 
| 66 | 
            +
                  @intermediate_handler = inthandler
         | 
| 67 | 
            +
                  @pending_keys = []
         | 
| 68 | 
            +
                  @completed = blk
         | 
| 69 | 
            +
                  @intermediate_state = {}
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                def process(msg)
         | 
| 73 | 
            +
                  case msg
         | 
| 74 | 
            +
                  when Result
         | 
| 75 | 
            +
                    results[msg.from] = msg.results
         | 
| 76 | 
            +
                    targets.delete(msg.from)
         | 
| 77 | 
            +
                  when IntermediateMessage
         | 
| 78 | 
            +
                    intermediate_state[msg.from] ||= {}
         | 
| 79 | 
            +
                    intermediate_state[msg.from][msg.messagekey] ||= []
         | 
| 80 | 
            +
                    intermediate_state[msg.from][msg.messagekey] << msg.message
         | 
| 81 | 
            +
                    @pending_keys << msg.messagekey
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                def intermediate_handler_for_key(key)
         | 
| 86 | 
            +
                  return nil unless @intermediate_handler
         | 
| 87 | 
            +
                  case @intermediate_handler
         | 
| 88 | 
            +
                  when Proc
         | 
| 89 | 
            +
                    @intermediate_handler
         | 
| 90 | 
            +
                  when Hash
         | 
| 91 | 
            +
                    @intermediate_handler[key] || @intermediate_handler['*']
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                def reset_pending_intermediate_state_keys
         | 
| 96 | 
            +
                  @pending_keys = []
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                def completed?
         | 
| 100 | 
            +
                  targets.empty?
         | 
| 101 | 
            +
                end
         | 
| 102 | 
            +
              end # Job
         | 
| 103 | 
            +
             | 
| 104 | 
            +
            end # Nanite
         |