krakow 0.2.2 → 0.3.0

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.
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