krakow 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/CHANGELOG.md +16 -0
  2. data/CONTRIBUTING.md +25 -0
  3. data/LICENSE +13 -0
  4. data/README.md +62 -9
  5. data/krakow.gemspec +3 -1
  6. data/lib/krakow/command/cls.rb +3 -4
  7. data/lib/krakow/command/fin.rb +13 -4
  8. data/lib/krakow/command/identify.rb +22 -9
  9. data/lib/krakow/command/mpub.rb +14 -4
  10. data/lib/krakow/command/nop.rb +3 -4
  11. data/lib/krakow/command/pub.rb +15 -5
  12. data/lib/krakow/command/rdy.rb +13 -4
  13. data/lib/krakow/command/req.rb +14 -4
  14. data/lib/krakow/command/sub.rb +14 -4
  15. data/lib/krakow/command/touch.rb +13 -4
  16. data/lib/krakow/command.rb +25 -3
  17. data/lib/krakow/connection.rb +286 -60
  18. data/lib/krakow/connection_features/deflate.rb +26 -1
  19. data/lib/krakow/connection_features/snappy_frames.rb +34 -3
  20. data/lib/krakow/connection_features/ssl.rb +43 -1
  21. data/lib/krakow/connection_features.rb +1 -0
  22. data/lib/krakow/consumer.rb +162 -49
  23. data/lib/krakow/discovery.rb +17 -6
  24. data/lib/krakow/distribution/default.rb +61 -33
  25. data/lib/krakow/distribution.rb +107 -57
  26. data/lib/krakow/exceptions.rb +14 -0
  27. data/lib/krakow/frame_type/error.rb +13 -7
  28. data/lib/krakow/frame_type/message.rb +47 -4
  29. data/lib/krakow/frame_type/response.rb +14 -4
  30. data/lib/krakow/frame_type.rb +20 -8
  31. data/lib/krakow/producer/http.rb +95 -6
  32. data/lib/krakow/producer.rb +60 -17
  33. data/lib/krakow/utils/lazy.rb +99 -40
  34. data/lib/krakow/utils/logging.rb +11 -0
  35. data/lib/krakow/utils.rb +3 -0
  36. data/lib/krakow/version.rb +3 -1
  37. data/lib/krakow.rb +1 -0
  38. metadata +11 -11
  39. data/Gemfile +0 -5
  40. data/Gemfile.lock +0 -34
  41. data/test/spec.rb +0 -81
  42. data/test/specs/consumer.rb +0 -49
  43. data/test/specs/http_producer.rb +0 -123
  44. data/test/specs/producer.rb +0 -20
@@ -1,9 +1,16 @@
1
+ require 'krakow'
2
+
1
3
  module Krakow
4
+
5
+ # TCP based producer
2
6
  class Producer
3
7
 
4
8
  autoload :Http, 'krakow/producer/http'
5
9
 
6
10
  include Utils::Lazy
11
+ # @!parse include Utils::Lazy::InstanceMethods
12
+ # @!parse extend Utils::Lazy::ClassMethods
13
+
7
14
  include Celluloid
8
15
 
9
16
  trap_exit :connection_failure
@@ -11,50 +18,79 @@ module Krakow
11
18
 
12
19
  attr_reader :connection
13
20
 
21
+ # @!group Attributes
22
+
23
+ # @!macro [attach] attribute
24
+ # @!method $1
25
+ # @return [$2] the $1 $0
26
+ # @!method $1?
27
+ # @return [TrueClass, FalseClass] truthiness of the $1 $0
28
+ attribute :host, String, :required => true
29
+ attribute :port, [String, Integer], :required => true
30
+ attribute :topic, String, :required => true
31
+ attribute :reconnect_retries, Integer, :default => 10
32
+ attribute :reconnect_interval, Integer, :default => 5
33
+ attribute :connection_options, Hash, :default => ->{ Hash.new }
34
+
35
+ # @!endgroup
36
+
14
37
  def initialize(args={})
15
38
  super
16
- required! :host, :port, :topic
17
- optional :reconnect_retries, :reconnect_interval, :connection_features
18
- arguments[:reconnect_retries] ||= 10
19
- arguments[:reconnect_interval] = 5
20
- arguments[:connection_features] ||= {}
39
+ arguments[:connection_options] = {:features => {}, :config => {}}.merge(
40
+ arguments.fetch(:connection_options, {})
41
+ )
21
42
  connect
22
43
  end
23
44
 
24
45
  # Establish connection to configured `host` and `port`
46
+ #
47
+ # @return nil
25
48
  def connect
26
49
  info "Establishing connection to: #{host}:#{port}"
27
50
  begin
28
51
  @connection = Connection.new(
29
52
  :host => host,
30
53
  :port => port,
31
- :features => connection_features
54
+ :features => connection_options[:features],
55
+ :features_args => connection_options[:config]
32
56
  )
33
- self.link connection
34
57
  connection.init!
58
+ self.link connection
35
59
  info "Connection established: #{connection}"
60
+ nil
36
61
  rescue => e
37
62
  abort e
38
63
  end
39
64
  end
40
65
 
66
+ # @return [String] stringify object
41
67
  def to_s
42
68
  "<#{self.class.name}:#{object_id} {#{host}:#{port}} T:#{topic}>"
43
69
  end
44
70
 
45
- # Return if connected
71
+ # @return [TrueClass, FalseClass] currently connected to server
46
72
  def connected?
47
- connection && connection.alive?
73
+ !!(connection && connection.alive? && connection.connected?)
48
74
  end
49
75
 
50
76
  # Process connection failure and attempt reconnection
77
+ #
78
+ # @return [TrueClass]
51
79
  def connection_failure(*args)
52
- warn "Connection has failed to #{host}:#{port}"
53
- debug "Sleeping for reconnect interval of #{reconnect_interval} seconds"
54
- sleep reconnect_interval
55
- connect
80
+ @connection = nil
81
+ begin
82
+ warn "Connection failure detected for #{host}:#{port}"
83
+ connect
84
+ rescue => e
85
+ warn "Failed to establish connection to #{host}:#{port}. Pausing #{reconnect_interval} before retry"
86
+ sleep reconnect_interval
87
+ connect
88
+ end
89
+ true
56
90
  end
57
91
 
92
+ # Instance destructor
93
+ # @return nil
58
94
  def goodbye_my_love!
59
95
  debug 'Tearing down producer'
60
96
  if(connection && connection.alive?)
@@ -62,12 +98,19 @@ module Krakow
62
98
  end
63
99
  @connection = nil
64
100
  info 'Producer torn down'
101
+ nil
65
102
  end
66
103
 
67
- # message:: Message to send
68
- # Write message
104
+ # Write message to server
105
+ #
106
+ # @param message [String] message to write
107
+ # @return [Krakow::FrameType::Error,nil]
108
+ # @raise [Krakow::Error::ConnectionUnavailable]
69
109
  def write(*message)
70
- if(connection.alive?)
110
+ if(message.empty?)
111
+ abort ArgumentError.new 'Expecting one or more messages to send. None provided.'
112
+ end
113
+ if(connection && connection.alive?)
71
114
  if(message.size > 1)
72
115
  debug 'Multiple message publish'
73
116
  connection.transmit(
@@ -86,7 +129,7 @@ module Krakow
86
129
  )
87
130
  end
88
131
  else
89
- abort Error.new 'Remote connection is unavailable!'
132
+ abort Error::ConnectionUnavailable.new 'Remote connection is unavailable!'
90
133
  end
91
134
  end
92
135
 
@@ -1,64 +1,123 @@
1
+ require 'krakow'
2
+
1
3
  module Krakow
2
4
  module Utils
5
+ # Adds functionality to facilitate laziness
3
6
  module Lazy
4
7
 
5
8
  include Utils::Logging
6
9
 
7
- attr_reader :arguments
10
+ # Instance methods for laziness
11
+ module InstanceMethods
12
+
13
+ # @return [Hash] argument hash
14
+ attr_reader :arguments
8
15
 
9
- def initialize(args={})
10
- @arguments = {}.tap do |hash|
11
- args.each do |k,v|
12
- hash[k.to_sym] = v
16
+ # Create new instance
17
+ #
18
+ # @param args [Hash]
19
+ # @return [Object]
20
+ def initialize(args={})
21
+ @arguments = {}.tap do |hash|
22
+ self.class.attributes.each do |name, options|
23
+ val = args[name]
24
+ if(options[:required] && !args.has_key?(name))
25
+ raise ArgumentError.new("Missing required option: `#{name}`")
26
+ end
27
+ if(val && options[:type] && !(valid = [options[:type]].flatten.compact).detect{|k| val.is_a?(k)})
28
+ raise TypeError.new("Invalid type for option `#{name}` (#{val} <#{val.class}>). Valid - #{valid.map(&:to_s).join(',')}")
29
+ end
30
+ if(val.nil? && options[:default] && !args.has_key?(name))
31
+ val = options[:default].respond_to?(:call) ? options[:default].call : options[:default]
32
+ end
33
+ hash[name] = val
34
+ end
13
35
  end
14
36
  end
37
+ alias_method :super_init, :initialize
38
+
39
+ # @return [String]
40
+ def to_s
41
+ "<#{self.class.name}:#{object_id}>"
42
+ end
43
+
44
+ # @return [String]
45
+ def inspect
46
+ "<#{self.class.name}:#{object_id} [#{arguments.inspect}]>"
47
+ end
48
+
15
49
  end
16
50
 
17
- # args:: list of required keys
18
- # Check that required keys exist in `arguments` hash. Raise
19
- # error if not found
20
- def required!(*args)
21
- args.each do |key|
22
- key = key.to_sym
23
- unless(arguments.has_key?(key))
24
- raise ArgumentError.new "Missing required option `#{key}`!"
51
+ # Class methods for laziness
52
+ module ClassMethods
53
+
54
+ # Add new attributes to class
55
+ #
56
+ # @param name [String]
57
+ # @param type [Class, Array<Class>]
58
+ # @param options [Hash]
59
+ # @option options [true, false] :required must be provided on initialization
60
+ # @option options [Object, Proc] :default default value
61
+ # @return [nil]
62
+ def attribute(name, type, options={})
63
+ name = name.to_sym
64
+ attributes[name] = {:type => type}.merge(options)
65
+ define_method(name) do
66
+ arguments[name.to_sym]
25
67
  end
26
- define_singleton_method(key) do
27
- arguments[key]
68
+ define_method("#{name}?") do
69
+ !!arguments[name.to_sym]
28
70
  end
29
- if(key.match(/\w$/))
30
- define_singleton_method("#{key}?".to_sym) do
31
- !!arguments[key]
32
- end
71
+ nil
72
+ end
73
+
74
+ # Return attributes
75
+ #
76
+ # @param args [Symbol] :required or :optional
77
+ # @return [Array<Hash>]
78
+ def attributes(*args)
79
+ @attributes ||= {}
80
+ if(args.include?(:required))
81
+ Hash[@attributes.find_all{|k,v| v[:required]}]
82
+ elsif(args.include?(:optional))
83
+ Hash[@attributes.find_all{|k,v| !v[:required]}]
84
+ else
85
+ @attributes
33
86
  end
34
87
  end
88
+
89
+ # Directly set attribute hash
90
+ #
91
+ # @param attrs [Hash]
92
+ # @return [TrueClass]
93
+ # @todo need deep dup here
94
+ def set_attributes(attrs)
95
+ @attributes = attrs.dup
96
+ true
97
+ end
98
+
35
99
  end
36
100
 
37
- # args:: list of required keys
38
- # Optional keys for arguments
39
- def optional(*args)
40
- args.each do |key|
41
- key = key.to_sym
42
- unless(arguments.has_key?(key))
43
- arguments[key] = nil
44
- end
45
- define_singleton_method(key) do
46
- arguments[key]
47
- end
48
- if(key.match(/\w$/))
49
- define_singleton_method("#{key}?".to_sym) do
50
- !!arguments[key]
101
+ class << self
102
+
103
+ # Injects laziness into class
104
+ #
105
+ # @param klass [Class]
106
+ def included(klass)
107
+ klass.class_eval do
108
+ include InstanceMethods
109
+ extend ClassMethods
110
+
111
+ class << self
112
+
113
+ def inherited(klass)
114
+ klass.set_attributes(self.attributes)
115
+ end
116
+
51
117
  end
52
118
  end
53
119
  end
54
- end
55
-
56
- def to_s
57
- "<#{self.class.name}:#{object_id}>"
58
- end
59
120
 
60
- def inspect
61
- "<#{self.class.name}:#{object_id} [#{arguments.inspect}]>"
62
121
  end
63
122
 
64
123
  end
@@ -1,5 +1,8 @@
1
+ require 'krakow'
2
+
1
3
  module Krakow
2
4
  module Utils
5
+ # Logging helpers
3
6
  module Logging
4
7
 
5
8
  # Define base logging types
@@ -10,16 +13,24 @@ module Krakow
10
13
  end
11
14
 
12
15
  # Log message
16
+ #
17
+ # @param args [Array, nil]
18
+ # @return [Logger, nil]
13
19
  def log(*args)
14
20
  if(args.empty?)
15
21
  Celluloid::Logger
16
22
  else
17
23
  severity, string = args
18
24
  Celluloid::Logger.send(severity.to_sym, "#{self}: #{string}")
25
+ nil
19
26
  end
20
27
  end
21
28
 
22
29
  class << self
30
+ # Set the logging output level
31
+ #
32
+ # @param level [Integer]
33
+ # @return [Integer, nil]
23
34
  def level=(level)
24
35
  if(Celluloid.logger.class == Logger)
25
36
  Celluloid.logger.level = Logger.const_get(level.to_s.upcase.to_sym)
data/lib/krakow/utils.rb CHANGED
@@ -1,4 +1,7 @@
1
+ require 'krakow'
2
+
1
3
  module Krakow
4
+ # Helper utilities
2
5
  module Utils
3
6
  autoload :Lazy, 'krakow/utils/lazy'
4
7
  autoload :Logging, 'krakow/utils/logging'
@@ -1,5 +1,7 @@
1
1
  module Krakow
2
+ # Custom version
2
3
  class Version < Gem::Version
3
4
  end
4
- VERSION = Version.new('0.2.2')
5
+ # Current version
6
+ VERSION = Version.new('0.3.0')
5
7
  end
data/lib/krakow.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'celluloid/autostart'
2
2
  require 'multi_json'
3
3
 
4
+ # NSQ client and producer library
4
5
  module Krakow
5
6
 
6
7
  autoload :Command, 'krakow/command'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: krakow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-03-08 00:00:00.000000000 Z
12
+ date: 2014-05-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: celluloid-io
@@ -95,7 +95,10 @@ description: NSQ ruby library
95
95
  email: code@chrisroberts.org
96
96
  executables: []
97
97
  extensions: []
98
- extra_rdoc_files: []
98
+ extra_rdoc_files:
99
+ - CHANGELOG.md
100
+ - CONTRIBUTING.md
101
+ - LICENSE
99
102
  files:
100
103
  - lib/krakow.rb
101
104
  - lib/krakow/consumer.rb
@@ -129,17 +132,14 @@ files:
129
132
  - lib/krakow/connection_features/ssl.rb
130
133
  - lib/krakow/connection_features/snappy_frames.rb
131
134
  - lib/krakow/connection_features/deflate.rb
132
- - test/spec.rb
133
- - test/specs/consumer.rb
134
- - test/specs/producer.rb
135
- - test/specs/http_producer.rb
136
- - Gemfile
137
- - README.md
138
135
  - krakow.gemspec
136
+ - README.md
139
137
  - CHANGELOG.md
140
- - Gemfile.lock
138
+ - CONTRIBUTING.md
139
+ - LICENSE
141
140
  homepage: http://github.com/chrisroberts/krakow
142
- licenses: []
141
+ licenses:
142
+ - Apache 2.0
143
143
  post_install_message:
144
144
  rdoc_options: []
145
145
  require_paths:
data/Gemfile DELETED
@@ -1,5 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gem 'minitest'
4
-
5
- gemspec
data/Gemfile.lock DELETED
@@ -1,34 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- krakow (0.1.3)
5
- celluloid-io
6
- digest-crc
7
- http
8
- multi_json
9
- snappy
10
-
11
- GEM
12
- remote: https://rubygems.org/
13
- specs:
14
- celluloid (0.15.2)
15
- timers (~> 1.1.0)
16
- celluloid-io (0.15.0)
17
- celluloid (>= 0.15.0)
18
- nio4r (>= 0.5.0)
19
- digest-crc (0.4.0)
20
- http (0.5.0)
21
- http_parser.rb
22
- http_parser.rb (0.6.0)
23
- minitest (5.0.8)
24
- multi_json (1.8.4)
25
- nio4r (1.0.0)
26
- snappy (0.0.10)
27
- timers (1.1.0)
28
-
29
- PLATFORMS
30
- ruby
31
-
32
- DEPENDENCIES
33
- krakow!
34
- minitest
data/test/spec.rb DELETED
@@ -1,81 +0,0 @@
1
- require 'krakow'
2
- require 'minitest/autorun'
3
-
4
- module Krakow
5
- class Test
6
- class << self
7
-
8
- def method_missing(*args)
9
- name = method = args.first.to_s
10
- if(name.end_with?('?'))
11
- name = name.tr('?', '')
12
- end
13
- val = ENV[name.upcase] || ENV[name]
14
- if(method.end_with?('?'))
15
- !!val
16
- else
17
- val
18
- end
19
- end
20
-
21
- def _topic
22
- 'krakow-test'
23
- end
24
-
25
- def _scrub_topic!
26
- _http_producer.delete_topic
27
- end
28
-
29
- def _http_producer
30
- @http_producer ||= Krakow::Producer::Http.new(
31
- :endpoint => 'http://127.0.0.1:4151',
32
- :topic => _topic
33
- )
34
- end
35
-
36
- def _producer(args={})
37
- Krakow::Producer.new(
38
- {
39
- :host => Krakow::Test.nsq_producer_host || '127.0.0.1',
40
- :port => Krakow::Test.nsq_producer_port || 4150,
41
- :topic => 'krakow-test'
42
- }.merge(args)
43
- )
44
- end
45
-
46
- def _consumer(args={})
47
- Krakow::Consumer.new(
48
- {
49
- :nslookupd => Krakow::Test.nsq_lookupd || 'http://127.0.0.1:4161',
50
- :topic => 'krakow-test',
51
- :channel => 'default',
52
- :discovery_interval => 0.5,
53
- :max_in_flight => 20
54
- }.merge(args)
55
- )
56
- end
57
-
58
- end
59
- end
60
- end
61
-
62
- MiniTest::Spec.before do
63
- Celluloid.boot
64
- Krakow::Test._scrub_topic!
65
- @consumer = Krakow::Test._consumer
66
- @producer = Krakow::Test._producer
67
- end
68
-
69
- MiniTest::Spec.after do
70
- @consumer.terminate if @consumer && @consumer.alive?
71
- @producer.terminate if @producer && @producer.alive?
72
- Krakow::Test._scrub_topic!
73
- end
74
-
75
- unless(Krakow::Test.debug?)
76
- Celluloid.logger.level = 3
77
- end
78
-
79
- Dir.glob(File.join(File.dirname(__FILE__), 'specs', '*.rb')).each do |path|
80
- require File.expand_path(path)
81
- end
@@ -1,49 +0,0 @@
1
- describe Krakow do
2
-
3
- describe Krakow::Consumer do
4
-
5
- it 'should not have any connections' do
6
- @consumer.connections.size.must_equal 0
7
- end
8
- it 'should have an empty queue' do
9
- @consumer.queue.size.must_equal 0
10
- end
11
-
12
- describe 'with active producer' do
13
-
14
- before do
15
- @producer.write('msg1', 'msg2', 'msg3')
16
- @inital_wait ||= sleep(0.8) # allow setup (topic creation, discovery, etc)
17
- end
18
-
19
- it 'should have one connection' do
20
- @consumer.connections.size.must_equal 1
21
- end
22
- it 'should have three messages queued' do
23
- @consumer.queue.size.must_equal 3
24
- end
25
- it 'should properly confirm messages' do
26
- 3.times do
27
- msg = @consumer.queue.pop
28
- @consumer.confirm(msg).must_equal true
29
- end
30
- sleep(0.5) # pause to let everything settle
31
- @consumer.queue.must_be :empty?
32
- end
33
- it 'should properly requeue messages' do
34
- 2.times do
35
- @consumer.confirm(@consumer.queue.pop)
36
- end
37
- @consumer.queue.size.must_equal 1
38
- original_msg = @consumer.queue.pop
39
- @consumer.queue.must_be :empty?
40
- @consumer.requeue(original_msg).must_equal true
41
- sleep(0.2)
42
- @consumer.queue.size.must_equal 1
43
- req_msg = @consumer.queue.pop
44
- req_msg.message_id.must_equal original_msg.message_id
45
- end
46
-
47
- end
48
- end
49
- end