jp 0.0.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.
@@ -0,0 +1,10 @@
1
+ #
2
+ # Autogenerated by Thrift
3
+ #
4
+ # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
5
+ #
6
+
7
+ require 'jp_types'
8
+
9
+ module Jp
10
+ end
@@ -0,0 +1,61 @@
1
+ #
2
+ # Autogenerated by Thrift
3
+ #
4
+ # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
5
+ #
6
+
7
+ require 'fb303_types'
8
+
9
+
10
+ module Jp
11
+ class Job
12
+ include ::Thrift::Struct, ::Thrift::Struct_Union
13
+ MESSAGE = 1
14
+ ID = 2
15
+
16
+ FIELDS = {
17
+ MESSAGE => {:type => ::Thrift::Types::STRING, :name => 'message', :binary => true},
18
+ ID => {:type => ::Thrift::Types::STRING, :name => 'id', :binary => true}
19
+ }
20
+
21
+ def struct_fields; FIELDS; end
22
+
23
+ def validate
24
+ raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field message is unset!') unless @message
25
+ raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field id is unset!') unless @id
26
+ end
27
+
28
+ ::Thrift::Struct.generate_accessors self
29
+ end
30
+
31
+ class EmptyPool < ::Thrift::Exception
32
+ include ::Thrift::Struct, ::Thrift::Struct_Union
33
+
34
+ FIELDS = {
35
+
36
+ }
37
+
38
+ def struct_fields; FIELDS; end
39
+
40
+ def validate
41
+ end
42
+
43
+ ::Thrift::Struct.generate_accessors self
44
+ end
45
+
46
+ class NoSuchPool < ::Thrift::Exception
47
+ include ::Thrift::Struct, ::Thrift::Struct_Union
48
+
49
+ FIELDS = {
50
+
51
+ }
52
+
53
+ def struct_fields; FIELDS; end
54
+
55
+ def validate
56
+ end
57
+
58
+ ::Thrift::Struct.generate_accessors self
59
+ end
60
+
61
+ end
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'jp/client'
4
+
5
+ autoload :JSON, 'json'
6
+
7
+ module Jp
8
+ class AbstractProducer < AbstractClient
9
+ def initialize queue, options = {}
10
+ super queue, options
11
+ end
12
+ def add message
13
+ @client.add @queue, translate(message)
14
+ end
15
+ private
16
+ def translate message
17
+ raise NotImplementedError.new
18
+ end
19
+ end
20
+
21
+ class TextProducer < AbstractProducer
22
+ private
23
+ def translate message
24
+ message
25
+ end
26
+ end
27
+
28
+ class JsonProducer < AbstractProducer
29
+ private
30
+ def translate message
31
+ JSON::dump message
32
+ end
33
+ end
34
+
35
+ class ThriftProducer < AbstractProducer
36
+ def initialize queue, options = {}
37
+ super queue, options
38
+ @serializer = Thrift::Serializer.new
39
+ end
40
+ private
41
+ def translate message
42
+ @serializer.serialize message
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ autoload :YAML, 'yaml'
4
+
5
+ # Find a config file and load it
6
+ module Jp
7
+ module Server
8
+ module ConfigurationLoader
9
+ def self.server_config
10
+ name = config_file('jp-config')
11
+ if name =~ /.ya?ml$/
12
+ load_yaml_config_file(name)
13
+ else
14
+ load_ruby_config_file(name)
15
+ end
16
+ end
17
+
18
+ private
19
+ def self.load_yaml_config_file file
20
+ config = YAML.load(File.read(file))
21
+ symbolize_hash(config)
22
+ end
23
+
24
+ def self.symbolize_hash hash
25
+ hash.dup.each do |k,v|
26
+ if v.is_a? Hash
27
+ symbolize_hash v
28
+ end
29
+ if k.is_a? String
30
+ hash[k.to_sym] = v
31
+ end
32
+ end
33
+ hash
34
+ end
35
+
36
+ def self.load_ruby_config_file file
37
+ load file
38
+ c = JpConfig.new
39
+ def c.options
40
+ m = self.public_methods - Object.public_methods - [:options]
41
+ h = Hash.new
42
+ m.each do |method|
43
+ h[method] = self.send method
44
+ end
45
+ h
46
+ end
47
+ c.options
48
+ end
49
+
50
+ def self.config_file name
51
+ if ARGV[0] && File.exists?(ARGV[0])
52
+ return File.expand_path ARGV[0]
53
+ end
54
+
55
+ # Prefixes
56
+ [
57
+ './',
58
+ '~/',
59
+ '~/.',
60
+ '/etc/',
61
+ ].each do |prefix|
62
+ full = File.expand_path "%s%s.rb" % [prefix, name]
63
+ if File.exists? full
64
+ return full
65
+ end
66
+ end
67
+ raise 'No config file found.'
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,94 @@
1
+ require 'jp/thrift'
2
+ require 'jp/server/mongo_connection'
3
+ require 'jp/server/pools'
4
+
5
+ module Jp
6
+ module Server
7
+ # Implementation class.
8
+ #
9
+ # Does not include Thrift code, etc.
10
+ class Handler
11
+ include MongoConnection
12
+ include Pools
13
+ attr_reader :retry_attempts, :retry_delay
14
+ def initialize options = {}
15
+ # Option defaults
16
+ defaults = {
17
+ :mongo_retry_attempts => 10,
18
+ :mongo_retry_delay => 1,
19
+ }
20
+ options = defaults.merge(options)
21
+ # Copy with/deal with options
22
+ @retry_attempts = options[:mongo_retry_attempts]
23
+ @retry_delay = options[:mongo_retry_delay]
24
+
25
+ load_pools(options)
26
+ connect_to_mongo(options)
27
+ end
28
+
29
+ def add pool, message
30
+ raise NoSuchPool.new unless pools.member? pool
31
+
32
+ doc = {
33
+ 'message' => message,
34
+ 'locked' => false,
35
+ }
36
+
37
+ rescue_connection_failure do
38
+ database[pool].insert doc
39
+ end
40
+ end
41
+
42
+ def acquire pool
43
+ raise NoSuchPool.new unless pools.member? pool
44
+ now = Time.new.to_i
45
+ doc = {}
46
+ begin
47
+ rescue_connection_failure do
48
+ doc = database[pool].find_and_modify(
49
+ query: {
50
+ 'locked' => false
51
+ },
52
+ update: {
53
+ '$set' => {
54
+ 'locked' => now,
55
+ 'locked_until' => now + pools[pool][:timeout],
56
+ },
57
+ }
58
+ )
59
+ end
60
+ rescue Mongo::OperationFailure => e
61
+ raise EmptyPool
62
+ end
63
+ job = Job.new
64
+ job.message = doc['message']
65
+ job.id = doc['_id'].to_s
66
+ job
67
+ end
68
+
69
+ def purge pool, id
70
+ raise NoSuchPool.new unless pools.member? pool
71
+ rescue_connection_failure do
72
+ database[pool].remove _id: BSON::ObjectId(id)
73
+ end
74
+ end
75
+
76
+ # Ensure retry upon failure
77
+ # Based on code from http://www.mongodb.org/display/DOCS/Replica+Pairs+in+Ruby
78
+ def rescue_connection_failure
79
+ success = false
80
+ retries = 0
81
+ while !success
82
+ begin
83
+ yield
84
+ success = true
85
+ rescue Mongo::ConnectionFailure => ex
86
+ retries += 1
87
+ raise ex if retries >= @retry_attempts
88
+ sleep(@retry_delay)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,87 @@
1
+ require 'jp/server/server'
2
+
3
+ module Jp
4
+ module Server
5
+ class InstrumentedServer < Jp::Server::Server
6
+ def initialize options = {}
7
+ options[:jp_server] ||= Jp::Server::Server.new options.merge(thrift_processor: JobPoolInstrumented::Processor.new(self))
8
+ @server = options[:jp_server]
9
+ @pools = options[:pools].keys
10
+ @add_count = Hash.new 0
11
+ @acquire_count = Hash.new 0
12
+ @purge_count = Hash.new 0
13
+ @empty_count = Hash.new 0
14
+ end
15
+
16
+ # Readers
17
+
18
+ def start_time
19
+ @server.aliveSince
20
+ end
21
+
22
+ def pools
23
+ @pools
24
+ end
25
+
26
+ def add_count pool
27
+ raise Jp::NoSuchPool.new unless @pools.include? pool
28
+ @add_count[pool]
29
+ end
30
+
31
+ def acquire_count pool
32
+ raise Jp::NoSuchPool.new unless @pools.include? pool
33
+ @acquire_count[pool]
34
+ end
35
+
36
+ def empty_count pool
37
+ raise Jp::NoSuchPool.new unless @pools.include? pool
38
+ @empty_count[pool]
39
+ end
40
+
41
+ def purge_count pool
42
+ raise Jp::NoSuchPool.new unless @pools.include? pool
43
+ @purge_count[pool]
44
+ end
45
+
46
+ # Data collectors
47
+
48
+ def serve
49
+ @server.serve
50
+ end
51
+
52
+ def add pool, message
53
+ result = @server.add pool, message
54
+ @add_count[pool] += 1
55
+ result
56
+ end
57
+
58
+ def acquire pool
59
+ begin
60
+ result = @server.acquire pool
61
+ @acquire_count[pool] += 1
62
+ rescue EmptyPool => e
63
+ @empty_count[pool] += 1
64
+ raise e
65
+ end
66
+ result
67
+ end
68
+
69
+ def purge pool, id
70
+ result = @server.purge pool, id
71
+ @purge_count[pool] += 1
72
+ result
73
+ end
74
+
75
+ def getCounters
76
+ counters = Hash.new
77
+ pools.each do |pool|
78
+ counters["#{pool}.added"] = add_count(pool)
79
+ counters["#{pool}.acquired"] = acquire_count(pool)
80
+ counters["#{pool}.empty"] = empty_count(pool)
81
+ counters["#{pool}.purged"] = purge_count(pool)
82
+ end
83
+ counters
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,29 @@
1
+ module Jp
2
+ module Server
3
+ module MongoConnection
4
+ attr_reader :database
5
+ def connect_to_mongo options
6
+ defaults = {
7
+ :mongo_uri => 'mongodb://localhost',
8
+ :mongo_pool_size => 10,
9
+ :mongo_pool_timeout => 60,
10
+ }
11
+ options = defaults.merge(options)
12
+ unless options[:mongo_db]
13
+ raise ArgumentError.new "mongo_db option must be specified"
14
+ end
15
+ # Connect to mongodb
16
+ if options.member? :injected_mongo_database then
17
+ @database = options[:injected_mongo_database]
18
+ else
19
+ connection = Mongo::Connection.from_uri(
20
+ options[:mongo_uri],
21
+ :pool_size => options[:mongo_pool_size],
22
+ :timeout => options[:mongo_pool_timeout],
23
+ )
24
+ @database = connection.db(options[:mongo_db])
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,28 @@
1
+ module Jp
2
+ module Server
3
+ module Pools
4
+ attr_reader :pools
5
+ protected
6
+ def load_pools options
7
+ defaults = {
8
+ :default_timeout => 3600, # 1 hour
9
+ }
10
+ options = defaults.merge(options)
11
+
12
+ unless options[:pools]
13
+ raise ArgumentError.new "pools option must be specified"
14
+ end
15
+ if options[:pools].empty?
16
+ raise ArgumentError.new "pools option must not be empty"
17
+ end
18
+
19
+ @pools = Hash.new
20
+ options[:pools].each do |name, data|
21
+ data[:timeout] ||= options[:default_timeout]
22
+ data[:cleanup_interval] ||= data[:timeout]
23
+ @pools[name] = data
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,74 @@
1
+ require 'jp/thrift'
2
+ require 'jp/server/handler'
3
+ require 'jp/server/unlocker'
4
+
5
+ require 'mongo'
6
+ include Jp
7
+
8
+ module Jp
9
+ module Server
10
+ class Server < Jp::Server::Handler
11
+ def initialize options = {}
12
+ super options
13
+ options[:port_number] ||= 9090
14
+
15
+ # Setup Thrift server (allowing dependency injection)
16
+ if options.member? :injected_thrift_server then
17
+ @server = options[:injected_thrift_server]
18
+ else
19
+ processor = options[:thrift_processor] # For testing, and allow instrumented server to override
20
+ processor ||= JobPool::Processor.new self
21
+ socket = Thrift::ServerSocket.new options[:port_number]
22
+ transportFactory = Thrift::BufferedTransportFactory.new
23
+
24
+ @server = Thrift::ThreadedServer.new processor, socket, transportFactory
25
+ end
26
+
27
+ @unlocker = nil
28
+ unless options[:skip_embedded_unlocker]
29
+ if options.member? :injected_unlocker
30
+ @unlocker = options[:injected_unlocker]
31
+ else
32
+ @unlocker = Jp::Server::Unlocker.new options
33
+ end
34
+ end
35
+
36
+ @start_time = Time.new.to_i
37
+ end
38
+
39
+ def serve
40
+ # Look for expired entries
41
+ @unlocker ||= nil
42
+ unlocker_thread = nil
43
+ if @unlocker
44
+ unlocker_thread = Thread.new do
45
+ @unlocker.run
46
+ end
47
+ end
48
+ @server.serve
49
+ unlocker_thread.join if unlocker_thread
50
+ end
51
+
52
+
53
+ # fb303:
54
+ def getName; 'jp'; end
55
+ def getVersion; '0.0.1'; end
56
+ def getStatus; Fb_status::ALIVE; end
57
+ def getStatusDetails; 'nothing to see here; move along'; end
58
+ def aliveSince; @start_time; end
59
+ def shutdown
60
+ STDERR.write "Shutdown requested via fb303\n"
61
+ exit
62
+ end
63
+ # fb303 stubs:
64
+ def setOption(key, value); end
65
+ def getOption(key); end
66
+ def getOptions; Hash.new; end
67
+ def getCpuProfile(seconds); String.new; end
68
+ def reinitialize; end
69
+ # fb303 stubs properly implemented in JpInstrumentedServer:
70
+ def getCounters; Hash.new; end
71
+ def getCounter(name); 0; end
72
+ end
73
+ end
74
+ end