hermann 0.24.0.0 → 0.24.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ext/hermann/extconf.rb +1 -0
- data/ext/hermann/hermann_lib.c +195 -25
- data/ext/hermann/hermann_lib.h +7 -0
- data/lib/hermann/discovery/metadata.rb +77 -0
- data/lib/hermann/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ab2b9a3c3699c6d765e930686fd0fb08df9e244
|
4
|
+
data.tar.gz: 36e43ca4b80e8428760e80b3ba65d6ff35a43232
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6ba78bfd1051a7e9f13b29d2816bbb7ac973968c081743dcf15fd85a4da7bfdc2c12c5bd7bbef00a13cc089c0489e78c1447238ce4aec59263b71bbf7db67881
|
7
|
+
data.tar.gz: 7d4d31a0ee898b78fe921778b788d5a661b7d5d6630ecd95053692595f3cf37cb754399fbf30f5221f9ba481b6be0ab5eb23ca226c09a7f38d8d7695f3c413c9
|
data/ext/hermann/extconf.rb
CHANGED
@@ -147,6 +147,7 @@ $LOCAL_LIBS << File.join(librdkafka.path, 'lib', 'librdkafka.a')
|
|
147
147
|
|
148
148
|
have_header('ruby/thread.h')
|
149
149
|
have_header('ruby/intern.h')
|
150
|
+
have_header('ruby/version.h')
|
150
151
|
have_func('rb_thread_blocking_region')
|
151
152
|
have_func('rb_thread_call_without_gvl')
|
152
153
|
|
data/ext/hermann/hermann_lib.c
CHANGED
@@ -33,6 +33,9 @@
|
|
33
33
|
|
34
34
|
#include "hermann_lib.h"
|
35
35
|
|
36
|
+
#ifdef HAVE_RUBY_VERSION_H
|
37
|
+
#include <ruby/version.h>
|
38
|
+
#endif
|
36
39
|
|
37
40
|
/* how long to let librdkafka block on the socket before returning back to the interpreter.
|
38
41
|
* essentially defines how long we wait before consumer_consume_stop_callback() can fire */
|
@@ -120,7 +123,7 @@ static void msg_delivered(rd_kafka_t *rk,
|
|
120
123
|
/* call back into our Hermann::Result if it exists, discarding the
|
121
124
|
* return value
|
122
125
|
*/
|
123
|
-
if (NULL != push_ctx->result) {
|
126
|
+
if (NULL != (void *)push_ctx->result) {
|
124
127
|
rb_funcall(push_ctx->result,
|
125
128
|
hermann_result_fulfill_method,
|
126
129
|
2,
|
@@ -153,12 +156,15 @@ static int32_t producer_partitioner_callback(const rd_kafka_topic_t *rkt,
|
|
153
156
|
void *msg_opaque) {
|
154
157
|
/* Pick a random partition */
|
155
158
|
int retry = 0;
|
159
|
+
int32_t partition = RD_KAFKA_PARTITION_UA;
|
160
|
+
|
156
161
|
for (; retry < partition_cnt; retry++) {
|
157
|
-
|
162
|
+
partition = rand() % partition_cnt;
|
158
163
|
if (rd_kafka_topic_partition_available(rkt, partition)) {
|
159
164
|
break; /* this one will do */
|
160
165
|
}
|
161
166
|
}
|
167
|
+
return partition;
|
162
168
|
}
|
163
169
|
|
164
170
|
/**
|
@@ -259,6 +265,7 @@ static void msg_consume(rd_kafka_message_t *rkmessage, HermannInstanceConfig *cf
|
|
259
265
|
// Yield the data to the Consumer's block
|
260
266
|
if (rb_block_given_p()) {
|
261
267
|
VALUE value = rb_str_new((char *)rkmessage->payload, rkmessage->len);
|
268
|
+
rd_kafka_message_destroy(rkmessage);
|
262
269
|
rb_yield(value);
|
263
270
|
}
|
264
271
|
else {
|
@@ -388,15 +395,19 @@ static void *consumer_recv_msg(void *ptr)
|
|
388
395
|
* after every message, to see if the ruby interpreter wants us to exit the
|
389
396
|
* loop.
|
390
397
|
*
|
391
|
-
* @param
|
398
|
+
* @param self The consumer instance
|
392
399
|
*/
|
393
400
|
|
394
|
-
static
|
401
|
+
static VALUE consumer_consume_loop(VALUE self) {
|
402
|
+
HermannInstanceConfig* consumerConfig;
|
395
403
|
rd_kafka_message_t *msg;
|
404
|
+
|
405
|
+
Data_Get_Struct(self, HermannInstanceConfig, consumerConfig);
|
406
|
+
|
396
407
|
TRACER("\n");
|
397
408
|
|
398
409
|
while (consumerConfig->run) {
|
399
|
-
#
|
410
|
+
#if HAVE_RB_THREAD_BLOCKING_REGION && RUBY_API_VERSION_MAJOR < 2
|
400
411
|
msg = (rd_kafka_message_t *) rb_thread_blocking_region((rb_blocking_function_t *) consumer_recv_msg,
|
401
412
|
consumerConfig,
|
402
413
|
consumer_consume_stop_callback,
|
@@ -412,9 +423,24 @@ static void consumer_consume_loop(HermannInstanceConfig* consumerConfig) {
|
|
412
423
|
|
413
424
|
if ( msg ) {
|
414
425
|
msg_consume(msg, consumerConfig);
|
415
|
-
rd_kafka_message_destroy(msg);
|
416
426
|
}
|
417
427
|
}
|
428
|
+
|
429
|
+
return Qnil;
|
430
|
+
}
|
431
|
+
|
432
|
+
|
433
|
+
/**
|
434
|
+
* consumer_consume_loop_stop
|
435
|
+
*
|
436
|
+
* called when we're done with the .consume() loop. lets rdkafa cleanup some internal structures
|
437
|
+
*/
|
438
|
+
static VALUE consumer_consume_loop_stop(VALUE self) {
|
439
|
+
HermannInstanceConfig* consumerConfig;
|
440
|
+
Data_Get_Struct(self, HermannInstanceConfig, consumerConfig);
|
441
|
+
|
442
|
+
rd_kafka_consume_stop(consumerConfig->rkt, consumerConfig->partition);
|
443
|
+
return Qnil;
|
418
444
|
}
|
419
445
|
|
420
446
|
/**
|
@@ -446,17 +472,12 @@ static VALUE consumer_consume(VALUE self, VALUE topic) {
|
|
446
472
|
if (rd_kafka_consume_start(consumerConfig->rkt, consumerConfig->partition, consumerConfig->start_offset) == -1) {
|
447
473
|
fprintf(stderr, "%% Failed to start consuming: %s\n",
|
448
474
|
rd_kafka_err2str(rd_kafka_errno2err(errno)));
|
449
|
-
rb_raise(rb_eRuntimeError,
|
475
|
+
rb_raise(rb_eRuntimeError, "%s",
|
450
476
|
rd_kafka_err2str(rd_kafka_errno2err(errno)));
|
451
477
|
return Qnil;
|
452
478
|
}
|
453
479
|
|
454
|
-
|
455
|
-
|
456
|
-
/* Stop consuming */
|
457
|
-
rd_kafka_consume_stop(consumerConfig->rkt, consumerConfig->partition);
|
458
|
-
|
459
|
-
return Qnil;
|
480
|
+
return rb_ensure(consumer_consume_loop, self, consumer_consume_loop_stop, self);
|
460
481
|
}
|
461
482
|
|
462
483
|
|
@@ -575,7 +596,7 @@ static VALUE producer_push_single(VALUE self, VALUE message, VALUE topic, VALUE
|
|
575
596
|
Data_Get_Struct(self, HermannInstanceConfig, producerConfig);
|
576
597
|
|
577
598
|
delivery_ctx->producer = producerConfig;
|
578
|
-
delivery_ctx->result = NULL;
|
599
|
+
delivery_ctx->result = (VALUE) NULL;
|
579
600
|
|
580
601
|
TRACER("producerConfig: %p\n", producerConfig);
|
581
602
|
|
@@ -666,19 +687,56 @@ static VALUE producer_tick(VALUE self, VALUE timeout) {
|
|
666
687
|
events = rd_kafka_poll(conf->rk, timeout_ms);
|
667
688
|
|
668
689
|
if (conf->isErrored) {
|
669
|
-
rb_raise(rb_eStandardError, conf->error);
|
690
|
+
rb_raise(rb_eStandardError, "%s", conf->error);
|
670
691
|
}
|
671
692
|
|
672
693
|
return rb_int_new(events);
|
673
694
|
}
|
674
695
|
|
696
|
+
/*
|
697
|
+
* producer_metadata_request_nogvl
|
698
|
+
*
|
699
|
+
* call rd_kafka_metadata without the GVL held. Note that rd_kafka_metadata is not interruptible,
|
700
|
+
* so in case of interrupt the thread will not respond until timeout_ms is reached.
|
701
|
+
*
|
702
|
+
* rd_kafka_metadata will fill in the ctx->data pointer on success
|
703
|
+
*
|
704
|
+
* @param ptr void* the hermann_metadata_ctx_t
|
705
|
+
*/
|
706
|
+
|
707
|
+
static void *producer_metadata_request_nogvl(void *ptr)
|
708
|
+
{
|
709
|
+
hermann_metadata_ctx_t *ctx = (hermann_metadata_ctx_t*)ptr;
|
710
|
+
|
711
|
+
return (void *) rd_kafka_metadata(ctx->rk,
|
712
|
+
ctx->topic ? 0 : 1,
|
713
|
+
ctx->topic,
|
714
|
+
(const struct rd_kafka_metadata **) &(ctx->data),
|
715
|
+
ctx->timeout_ms);
|
716
|
+
}
|
717
|
+
|
718
|
+
|
719
|
+
static int producer_metadata_request(hermann_metadata_ctx_t *ctx)
|
720
|
+
{
|
721
|
+
int err;
|
722
|
+
|
723
|
+
#if HAVE_RB_THREAD_BLOCKING_REGION && RUBY_API_VERSION_MAJOR < 2
|
724
|
+
err = (int) rb_thread_blocking_region((rb_blocking_function_t *) producer_metadata_request_nogvl, ctx,
|
725
|
+
NULL, NULL);
|
726
|
+
#elif HAVE_RB_THREAD_CALL_WITHOUT_GVL
|
727
|
+
err = (int) rb_thread_call_without_gvl(producer_metadata_request_nogvl, ctx, NULL, NULL);
|
728
|
+
#else
|
729
|
+
err = (int) producer_metadata_request_nogvl(ctx);
|
730
|
+
#endif
|
731
|
+
|
732
|
+
return err;
|
733
|
+
}
|
675
734
|
|
676
735
|
static VALUE producer_connect(VALUE self, VALUE timeout) {
|
677
736
|
HermannInstanceConfig *producerConfig;
|
678
737
|
rd_kafka_resp_err_t err;
|
679
738
|
VALUE result = Qfalse;
|
680
|
-
|
681
|
-
struct rd_kafka_metadata *data = NULL;
|
739
|
+
hermann_metadata_ctx_t md_context;
|
682
740
|
|
683
741
|
Data_Get_Struct(self, HermannInstanceConfig, producerConfig);
|
684
742
|
|
@@ -686,17 +744,19 @@ static VALUE producer_connect(VALUE self, VALUE timeout) {
|
|
686
744
|
producer_init_kafka(self, producerConfig);
|
687
745
|
}
|
688
746
|
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
747
|
+
md_context.rk = producerConfig->rk;
|
748
|
+
md_context.topic = NULL;
|
749
|
+
md_context.data = NULL;
|
750
|
+
md_context.timeout_ms = rb_num2int(timeout);
|
751
|
+
|
752
|
+
err = producer_metadata_request(&md_context);
|
753
|
+
|
694
754
|
TRACER("err: %s (%i)\n", rd_kafka_err2str(err), err);
|
695
755
|
|
696
756
|
if (RD_KAFKA_RESP_ERR_NO_ERROR == err) {
|
697
757
|
TRACER("brokers: %i, topics: %i\n",
|
698
|
-
data->broker_cnt,
|
699
|
-
data->topic_cnt);
|
758
|
+
md_context.data->broker_cnt,
|
759
|
+
md_context.data->topic_cnt);
|
700
760
|
producerConfig->isConnected = 1;
|
701
761
|
result = Qtrue;
|
702
762
|
}
|
@@ -704,11 +764,118 @@ static VALUE producer_connect(VALUE self, VALUE timeout) {
|
|
704
764
|
producerConfig->isErrored = err;
|
705
765
|
}
|
706
766
|
|
707
|
-
|
767
|
+
if ( md_context.data )
|
768
|
+
rd_kafka_metadata_destroy(md_context.data);
|
708
769
|
|
709
770
|
return result;
|
710
771
|
}
|
711
772
|
|
773
|
+
/*
|
774
|
+
* producer_metadata_make_hash
|
775
|
+
*
|
776
|
+
* transform the rd_kafka_metadata structure into a ruby hash. eg:
|
777
|
+
* { :brokers => [ {:id=>0, :host=>"172.20.10.3", :port=>9092} ],
|
778
|
+
* :topics => { "maxwell" => [ {:id=>0, :leader_id=>0, :replica_ids=>[0], :isr_ids=>[0]}]} }
|
779
|
+
*
|
780
|
+
* @param data struct rd_kafka_metadata* data returned from rd_kafka_metadata
|
781
|
+
*/
|
782
|
+
|
783
|
+
static VALUE producer_metadata_make_hash(struct rd_kafka_metadata *data)
|
784
|
+
{
|
785
|
+
int i, j, k;
|
786
|
+
VALUE broker_hash, topic_hash, partition_ary, partition_hash, partition_replica_ary, partition_isr_ary;
|
787
|
+
VALUE hash = rb_hash_new();
|
788
|
+
VALUE brokers = rb_ary_new2(data->broker_cnt);
|
789
|
+
VALUE topics = rb_hash_new();
|
790
|
+
|
791
|
+
for ( i = 0; i < data->broker_cnt; i++ ) {
|
792
|
+
broker_hash = rb_hash_new();
|
793
|
+
rb_hash_aset(broker_hash, ID2SYM(rb_intern("id")), INT2FIX(data->brokers[i].id));
|
794
|
+
rb_hash_aset(broker_hash, ID2SYM(rb_intern("host")), rb_str_new2(data->brokers[i].host));
|
795
|
+
rb_hash_aset(broker_hash, ID2SYM(rb_intern("port")), INT2FIX(data->brokers[i].port));
|
796
|
+
rb_ary_push(brokers, broker_hash);
|
797
|
+
}
|
798
|
+
|
799
|
+
for ( i = 0; i < data->topic_cnt; i++ ) {
|
800
|
+
partition_ary = rb_ary_new2(data->topics[i].partition_cnt);
|
801
|
+
|
802
|
+
for ( j = 0 ; j < data->topics[i].partition_cnt ; j++ ) {
|
803
|
+
VALUE partition_hash = rb_hash_new();
|
804
|
+
rd_kafka_metadata_partition_t *partition = &(data->topics[i].partitions[j]);
|
805
|
+
|
806
|
+
/* id => 1, leader_id => 0 */
|
807
|
+
rb_hash_aset(partition_hash, ID2SYM(rb_intern("id")), INT2FIX(partition->id));
|
808
|
+
rb_hash_aset(partition_hash, ID2SYM(rb_intern("leader_id")), INT2FIX(partition->leader));
|
809
|
+
|
810
|
+
/* replica_ids => [1, 0] */
|
811
|
+
partition_replica_ary = rb_ary_new2(partition->replica_cnt);
|
812
|
+
for ( k = 0 ; k < partition->replica_cnt ; k++ ) {
|
813
|
+
rb_ary_push(partition_replica_ary, INT2FIX(partition->replicas[k]));
|
814
|
+
}
|
815
|
+
rb_hash_aset(partition_hash, ID2SYM(rb_intern("replica_ids")), partition_replica_ary);
|
816
|
+
|
817
|
+
/* isr_ids => [1, 0] */
|
818
|
+
partition_isr_ary = rb_ary_new2(partition->isr_cnt);
|
819
|
+
for ( k = 0 ; k < partition->isr_cnt ; k++ ) {
|
820
|
+
rb_ary_push(partition_isr_ary, INT2FIX(partition->isrs[k]));
|
821
|
+
}
|
822
|
+
rb_hash_aset(partition_hash, ID2SYM(rb_intern("isr_ids")), partition_isr_ary);
|
823
|
+
|
824
|
+
rb_ary_push(partition_ary, partition_hash);
|
825
|
+
}
|
826
|
+
|
827
|
+
rb_hash_aset(topics, rb_str_new2(data->topics[i].topic), partition_ary);
|
828
|
+
}
|
829
|
+
|
830
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("brokers")), brokers);
|
831
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("topics")), topics);
|
832
|
+
return hash;
|
833
|
+
}
|
834
|
+
|
835
|
+
/*
|
836
|
+
* producer_metadata
|
837
|
+
*
|
838
|
+
* make a metadata request to the kafka server, returning a hash
|
839
|
+
* containing a list of brokers and topics.
|
840
|
+
*
|
841
|
+
* @param data struct rd_kafka_metadata* data returned from rd_kafka_metadata
|
842
|
+
*/
|
843
|
+
|
844
|
+
static VALUE producer_metadata(VALUE self, VALUE topicStr, VALUE timeout) {
|
845
|
+
HermannInstanceConfig *producerConfig;
|
846
|
+
rd_kafka_resp_err_t err;
|
847
|
+
hermann_metadata_ctx_t md_context;
|
848
|
+
VALUE result;
|
849
|
+
|
850
|
+
Data_Get_Struct(self, HermannInstanceConfig, producerConfig);
|
851
|
+
|
852
|
+
if (!producerConfig->isInitialized) {
|
853
|
+
producer_init_kafka(self, producerConfig);
|
854
|
+
}
|
855
|
+
|
856
|
+
md_context.rk = producerConfig->rk;
|
857
|
+
md_context.timeout_ms = rb_num2int(timeout);
|
858
|
+
|
859
|
+
if ( !NIL_P(topicStr) ) {
|
860
|
+
Check_Type(topicStr, T_STRING);
|
861
|
+
md_context.topic = rd_kafka_topic_new(producerConfig->rk, StringValuePtr(topicStr), NULL);
|
862
|
+
} else {
|
863
|
+
md_context.topic = NULL;
|
864
|
+
}
|
865
|
+
|
866
|
+
err = producer_metadata_request(&md_context);
|
867
|
+
|
868
|
+
if ( err != RD_KAFKA_RESP_ERR_NO_ERROR ) {
|
869
|
+
// annoyingly, this is always a timeout error -- the rest rdkafka just jams onto STDERR
|
870
|
+
rb_raise( rb_eRuntimeError, "%s", rd_kafka_err2str(err) );
|
871
|
+
} else {
|
872
|
+
result = producer_metadata_make_hash(md_context.data);
|
873
|
+
rd_kafka_metadata_destroy(md_context.data);
|
874
|
+
return result;
|
875
|
+
}
|
876
|
+
|
877
|
+
}
|
878
|
+
|
712
879
|
static VALUE producer_is_connected(VALUE self) {
|
713
880
|
HermannInstanceConfig *producerConfig;
|
714
881
|
|
@@ -1076,4 +1243,7 @@ void Init_hermann_lib() {
|
|
1076
1243
|
|
1077
1244
|
/* Producer.connect */
|
1078
1245
|
rb_define_method(c_producer, "connect", producer_connect, 1);
|
1246
|
+
|
1247
|
+
/* Producer.metadata */
|
1248
|
+
rb_define_method(c_producer, "metadata", producer_metadata, 2);
|
1079
1249
|
}
|
data/ext/hermann/hermann_lib.h
CHANGED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'hermann_lib'
|
2
|
+
require 'hermann/consumer'
|
3
|
+
|
4
|
+
module Hermann
|
5
|
+
module Discovery
|
6
|
+
class Metadata
|
7
|
+
Broker = Struct.new(:id, :host, :port) do
|
8
|
+
def to_s
|
9
|
+
"#{host}:#{port}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
Topic = Struct.new(:name, :partitions)
|
13
|
+
|
14
|
+
Partition = Struct.new(:id, :leader, :replicas, :insync_replicas, :topic_name) do
|
15
|
+
def consumer(offset=:end)
|
16
|
+
Hermann::Consumer.new(topic_name, brokers: ([leader] + replicas).join(','), partition: id, offset: offset)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
DEFAULT_TIMEOUT_MS = 2_000
|
21
|
+
def initialize(brokers, options = {})
|
22
|
+
raise "this is an MRI api only!" if Hermann.jruby?
|
23
|
+
@internal = Hermann::Lib::Producer.new(brokers)
|
24
|
+
@timeout = options[:timeout] || DEFAULT_TIMEOUT_MS
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# @internal.metadata returns:
|
29
|
+
# {:brokers => [{:id=>3, :host=>"kafka3.alpha4.sac1.zdsys.com", :port=>9092}],
|
30
|
+
# :topics => {"testtopic"=>[{:id=>0, :leader_id=>3, :replica_ids=>[3, 1], :isr_ids=>[3, 1]}}}
|
31
|
+
#
|
32
|
+
def brokers
|
33
|
+
brokers_from_metadata(@internal.metadata(nil, @timeout))
|
34
|
+
end
|
35
|
+
|
36
|
+
def topic(t)
|
37
|
+
get_topics(t)[t]
|
38
|
+
end
|
39
|
+
|
40
|
+
def topics
|
41
|
+
get_topics
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def get_topics(filter_topics = nil)
|
47
|
+
md = @internal.metadata(filter_topics, @timeout)
|
48
|
+
|
49
|
+
broker_hash = brokers_from_metadata(md).inject({}) do |h, broker|
|
50
|
+
h[broker.id] = broker
|
51
|
+
h
|
52
|
+
end
|
53
|
+
|
54
|
+
md[:topics].inject({}) do |topic_hash, arr|
|
55
|
+
topic_name, raw_partitions = *arr
|
56
|
+
partitions = raw_partitions.map do |p|
|
57
|
+
leader = broker_hash[p[:leader_id]]
|
58
|
+
all_replicas = p[:replica_ids].map { |i| broker_hash[i] }
|
59
|
+
isr_replicas = p[:isr_ids].map { |i| broker_hash[i] }
|
60
|
+
Partition.new(p[:id], leader, all_replicas, isr_replicas, topic_name)
|
61
|
+
end
|
62
|
+
|
63
|
+
topic_hash[topic_name] = Topic.new(topic_name, partitions)
|
64
|
+
topic_hash
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
def brokers_from_metadata(md)
|
70
|
+
md[:brokers].map do |h|
|
71
|
+
Broker.new(h[:id], h[:host], h[:port])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/hermann/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hermann
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.24.
|
4
|
+
version: 0.24.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- R. Tyler Croy
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2015-06-
|
13
|
+
date: 2015-06-19 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: json
|
@@ -70,6 +70,7 @@ files:
|
|
70
70
|
- ext/hermann/hermann_lib.h
|
71
71
|
- lib/hermann.rb
|
72
72
|
- lib/hermann/consumer.rb
|
73
|
+
- lib/hermann/discovery/metadata.rb
|
73
74
|
- lib/hermann/discovery/zookeeper.rb
|
74
75
|
- lib/hermann/errors.rb
|
75
76
|
- lib/hermann/java.rb
|