gearman_admin_client 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
@@ -0,0 +1,27 @@
1
+ # GearmanAdminClient
2
+
3
+ Connect and issue administrative commands to a [Gearman](http://gearman.org) server. `GearmanAdminClient`'s API follows the Administrative Protocol closely. You can read more about the Adminstrative Protocol under the "Administrative Protocol" section of the [Gearman protocol specification](http://gearman.org/protocol).
4
+
5
+ ## Usage
6
+
7
+ ```ruby
8
+ client = GearmanAdminClient.new('localhost:4730')
9
+
10
+ # list registered workers
11
+ client.workers
12
+
13
+ # list registered functions
14
+ client.status
15
+
16
+ # set the maximum queue size for a function
17
+ client.max_queue_size('function_name', 1_000)
18
+
19
+ # get the version of the server
20
+ client.server_version
21
+
22
+ # shutdown the server gracefully
23
+ client.shutdown graceful: true
24
+
25
+ # shutdown the server forcefully
26
+ client.shutdown
27
+ ```
@@ -0,0 +1,130 @@
1
+ require 'socket'
2
+
3
+ require_relative 'gearman_admin_client/worker'
4
+ require_relative 'gearman_admin_client/registered_function'
5
+ require_relative 'gearman_admin_client/connection'
6
+
7
+ class GearmanAdminClient
8
+
9
+ DISCONNECTED = :DISCONNECTED unless defined? DISCONNECTED
10
+
11
+ class BadAddress < RuntimeError ; end
12
+
13
+ attr_reader :address
14
+
15
+ def initialize(address)
16
+ @address = address
17
+ @connection = DISCONNECTED
18
+ end
19
+
20
+ def workers
21
+ connect! do |connection|
22
+ connection.write('workers')
23
+ output = connection.drain.split("\n")
24
+
25
+ output.map! do |line|
26
+ segments = line.split(':')
27
+
28
+ function_names = segments.pop.strip.split(' ')
29
+
30
+ remainder = segments.join(':')
31
+
32
+ fd, ip_address, client_id = remainder.split(' ').map(&:strip)
33
+
34
+ unless function_names.include?('-')
35
+ Worker.new(
36
+ :file_descriptor => fd,
37
+ :ip_address => ip_address,
38
+ :client_id => client_id,
39
+ :function_names => function_names
40
+ )
41
+ end
42
+ end
43
+
44
+ output.compact!
45
+ end
46
+ end
47
+
48
+ def status
49
+ connect! do |connection|
50
+ connection.write('status')
51
+ output = connection.drain.split("\n")
52
+
53
+ output.map do |line|
54
+ function_name, total, running, workers = line.split("\t")
55
+
56
+ RegisteredFunction.new(
57
+ :name => function_name,
58
+ :jobs_in_queue => total,
59
+ :running_jobs => running,
60
+ :available_workers => workers
61
+ )
62
+ end
63
+ end
64
+ end
65
+
66
+ def server_version
67
+ connect! do |connection|
68
+ connection.write('version')
69
+ connection.read.strip
70
+ end
71
+ end
72
+
73
+ def shutdown(options = {})
74
+ connect! do |connection|
75
+ command = ['shutdown']
76
+
77
+ if options.fetch(:graceful, false)
78
+ command << 'graceful'
79
+ end
80
+
81
+ connection.write(command.join(' '))
82
+ connection.read.strip
83
+
84
+ connection.eof? && disconnect!
85
+ end
86
+
87
+ true
88
+ end
89
+
90
+ def max_queue_size(function_name, queue_size = nil)
91
+ connect! do |connection|
92
+ command = ['maxqueue', function_name, queue_size].compact
93
+
94
+ connection.write(command.join(' '))
95
+ connection.read.strip
96
+ end
97
+ end
98
+
99
+ def disconnected?
100
+ DISCONNECTED == @connection
101
+ end
102
+
103
+ private
104
+
105
+ def disconnect!
106
+ @connection.close
107
+ @connection = DISCONNECTED
108
+ end
109
+
110
+ def connect!(&and_then)
111
+ if disconnected?
112
+ just_open_a_socket do |socket|
113
+ @connection = Connection.new(socket)
114
+ end
115
+ end
116
+
117
+ and_then.call(@connection)
118
+ end
119
+
120
+ def just_open_a_socket
121
+ host, port = address.split(':')
122
+
123
+ if host && port
124
+ yield TCPSocket.new(host, port)
125
+ else
126
+ raise BadAddress, "expected address to look like HOST:PORT"
127
+ end
128
+ end
129
+
130
+ end
@@ -0,0 +1,37 @@
1
+ require 'forwardable'
2
+
3
+ class GearmanAdminClient
4
+ class Connection
5
+ extend Forwardable
6
+
7
+ def_delegators :io, :close, :closed?, :eof?
8
+
9
+ attr_reader :io
10
+
11
+ def initialize(io)
12
+ @io = io
13
+ end
14
+
15
+ def write(command)
16
+ IO::select([], [io])
17
+ io.puts(command)
18
+ end
19
+
20
+ def read
21
+ IO::select([io])
22
+ io.gets
23
+ end
24
+
25
+ def drain
26
+ output = ''
27
+
28
+ while line = read
29
+ break if line.chop == '.'
30
+ output << line
31
+ end
32
+
33
+ output
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,13 @@
1
+ require 'virtus'
2
+
3
+ class GearmanAdminClient
4
+ class RegisteredFunction
5
+ include Virtus::ValueObject
6
+
7
+ attribute :name, String
8
+ attribute :jobs_in_queue, Integer
9
+ attribute :running_jobs, Integer
10
+ attribute :available_workers, Integer
11
+
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ class GearmanAdminClient
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,13 @@
1
+ require 'virtus'
2
+
3
+ class GearmanAdminClient
4
+ class Worker
5
+ include Virtus::ValueObject
6
+
7
+ attribute :file_descriptor, String
8
+ attribute :ip_address, String
9
+ attribute :client_id, String
10
+ attribute :function_names, Array[String]
11
+
12
+ end
13
+ end
@@ -0,0 +1,161 @@
1
+ require 'gearmand_control'
2
+ require 'gearman_admin_client'
3
+
4
+ require 'timeout'
5
+ require 'securerandom'
6
+
7
+ describe GearmanAdminClient do
8
+
9
+ before do
10
+ @sockets = []
11
+ @gearmand = GearmandControl.new(4730)
12
+ @gearmand.start
13
+ @gearmand.test!
14
+ end
15
+
16
+ after do
17
+ @sockets.each(&:close)
18
+ @gearmand.stop
19
+ end
20
+
21
+ def can_do(server_address, *function_names)
22
+ host, port = server_address.split(':')
23
+ socket = TCPSocket.new(host, port)
24
+
25
+ @sockets << socket
26
+
27
+ function_names.each do |function_name|
28
+ request(socket, 1, [function_name])
29
+ end
30
+ end
31
+
32
+ def submit_job_bg(server_address, *function_names)
33
+ host, port = server_address.split(':')
34
+ socket = TCPSocket.new(host, port)
35
+
36
+ @sockets << socket
37
+
38
+ function_names.each do |function_name|
39
+ id = SecureRandom.hex
40
+ request(socket, 18, [function_name, id, 'arg'])
41
+ end
42
+ end
43
+
44
+ def request(socket, type, arguments)
45
+ body = arguments.join("\0")
46
+ header = ["\0REQ", type, body.size].pack('a4NN')
47
+
48
+ IO::select([], [socket])
49
+ socket.write(header + body)
50
+
51
+ read_a_little_from(socket)
52
+ end
53
+
54
+ def read_a_little_from(socket)
55
+ begin
56
+ Timeout.timeout(1.0e-6, RuntimeError) do
57
+ IO::select([socket])
58
+ socket.read
59
+ end
60
+ rescue
61
+ end
62
+ end
63
+
64
+ it 'knows the server version' do
65
+ client = GearmanAdminClient.new(@gearmand.address)
66
+
67
+ expect(client.server_version).to match(/OK \d+\.?+/)
68
+ end
69
+
70
+ it 'can shutdown the server gracefully' do
71
+ client = GearmanAdminClient.new(@gearmand.address)
72
+
73
+ client.shutdown :graceful => true
74
+
75
+ expect do
76
+ @gearmand.test!
77
+ end.to raise_error(GearmandControl::TestFailed)
78
+ end
79
+
80
+ it 'can shutdown the server forcefully' do
81
+ client = GearmanAdminClient.new(@gearmand.address)
82
+
83
+ client.shutdown
84
+
85
+ expect do
86
+ @gearmand.test!
87
+ end.to raise_error(GearmandControl::TestFailed)
88
+ end
89
+
90
+ it 'can list registered workers' do
91
+ client = GearmanAdminClient.new(@gearmand.address)
92
+
93
+ expect(client.workers).to be_empty
94
+
95
+ can_do(@gearmand.address, 'function_1', 'function_2')
96
+ can_do(@gearmand.address, 'function_3')
97
+
98
+ functions = client.workers.
99
+ map { |worker| worker.function_names.sort }.
100
+ sort_by(&:size)
101
+
102
+ expect(functions).to eql([
103
+ ['function_3'],
104
+ ['function_1', 'function_2']
105
+ ])
106
+ end
107
+
108
+ it 'knows all registered functions' do
109
+ client = GearmanAdminClient.new(@gearmand.address)
110
+
111
+ expect(client.status).to be_empty
112
+
113
+ can_do(@gearmand.address, 'function_1', 'function_2')
114
+ can_do(@gearmand.address, 'function_3')
115
+ submit_job_bg(@gearmand.address, 'function_2', 'function_4')
116
+
117
+ expect(client.status.sort_by(&:name)).to eql([
118
+ { :name => 'function_1', :jobs_in_queue => 0, :running_jobs => 0,
119
+ :available_workers => 1 },
120
+ { :name => 'function_2', :jobs_in_queue => 1, :running_jobs => 0,
121
+ :available_workers => 1 },
122
+ { :name => 'function_3', :jobs_in_queue => 0, :running_jobs => 0,
123
+ :available_workers => 1 },
124
+ { :name => 'function_4', :jobs_in_queue => 1, :running_jobs => 0,
125
+ :available_workers => 0 }
126
+ ].map(&GearmanAdminClient::RegisteredFunction.method(:new)))
127
+ end
128
+
129
+ it 'can set the max queue size' do
130
+ client = GearmanAdminClient.new(@gearmand.address)
131
+
132
+ can_do(@gearmand.address, 'function_1')
133
+
134
+ client.max_queue_size('function_1', 1)
135
+
136
+ 2.times do
137
+ submit_job_bg(@gearmand.address, 'function_1')
138
+ end
139
+
140
+ expect(client.status).to eql([GearmanAdminClient::RegisteredFunction.new(
141
+ :name => 'function_1',
142
+ :jobs_in_queue => 1,
143
+ :running_jobs => 0,
144
+ :available_workers => 1
145
+ )])
146
+
147
+ client.max_queue_size('function_1', 3)
148
+
149
+ 2.times do
150
+ submit_job_bg(@gearmand.address, 'function_1')
151
+ end
152
+
153
+ expect(client.status).to eql([GearmanAdminClient::RegisteredFunction.new(
154
+ :name => 'function_1',
155
+ :jobs_in_queue => 3,
156
+ :running_jobs => 0,
157
+ :available_workers => 1
158
+ )])
159
+ end
160
+
161
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gearman_admin_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Brian Cobb
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-07-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: gearmand_control
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: virtus
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - '='
68
+ - !ruby/object:Gem::Version
69
+ version: 1.0.0.beta0
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - '='
76
+ - !ruby/object:Gem::Version
77
+ version: 1.0.0.beta0
78
+ description: A Ruby wrapper around the Gearman admin protocol
79
+ email:
80
+ - bcobb@uwalumni.com
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - lib/gearman_admin_client/connection.rb
86
+ - lib/gearman_admin_client/registered_function.rb
87
+ - lib/gearman_admin_client/version.rb
88
+ - lib/gearman_admin_client/worker.rb
89
+ - lib/gearman_admin_client.rb
90
+ - README.markdown
91
+ - spec/gearman_admin_client_spec.rb
92
+ homepage: http://github.com/bcobb/gearman_admin_client
93
+ licenses: []
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubyforge_project:
112
+ rubygems_version: 1.8.25
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: Chat with a gearman server using its admin protocol
116
+ test_files:
117
+ - spec/gearman_admin_client_spec.rb