You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pulsar.apache.org by mm...@apache.org on 2019/05/14 16:33:37 UTC

[pulsar-client-go] branch master updated (d74beea -> 9c5d509)

This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git.


    from d74beea  Initial commit
     new 84cb32e  Initial import
     new 0c2da14  Use string for result errors
     new ccc596a  Create producer session
     new 1bdf19f  Implemented keep-alive logic
     new d31030e  Set right level for logs
     new d8a1dcc  Removed internalClose method
     new 0fe868a  Added serialize method for string map
     new f7cac7a  Basic publishing works
     new 2e74112  Reconnection logic
     new b95a971  Added blocking queue implementation
     new 7851714  Queue sending requests and trigger callbacks
     new ecab9cf  Added blocking queue iterator
     new 427fe05  Resend pending messages after reconnection
     new 9522dd5  Handle cases with no-batching
     new 98552f3  Producer close
     new ea44ac9  Implemented producer flush
     new f9fa727  Added compression codecs and tests
     new 0eb04d5  Support compression in producer
     new b924b78  Completed lookup service with tests
     new b2c9d8c  Producer last sequence id
     new 0ac7868  Added hash functions and tests
     new e7a4aef  Completed default message router and tests
     new 585500e  Added perf producer/consumer
     new 9c6d52f  Added auto-resize when writing to buffer
     new e077250  Use logrus in perf producer/consumer
     new b329bbf  Fixed releasing of semaphore for each send request
     new 05bc67f  Added MessageID implementation
     new dfd7550  Renamed to pulsar-client-go
     new 1ead55b  Added license headers
     new b212407  Added README
     new 617f0d5  Addressed comments
     new d6dbcfa  Renamed `impl` package to `internal`
     new fe3258b  Added scripts to start test service
     new ddc789e  Added TLS connection support
     new 1a6dfaa  TLS Auth provider
     new e058b84  Added token auth provider
     new 9c5d509  Merge pull request #1 from merlimat/master

The 38 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .gitignore                                      |    1 +
 Dockerfile                                      |   34 +
 LICENSE                                         |  305 ++
 NOTICE                                          |    6 +
 README.md                                       |   60 +
 integration-tests/certs/broker-cert.pem         |   73 +
 integration-tests/certs/broker-key.pem          |   28 +
 integration-tests/certs/cacert.pem              |   62 +
 integration-tests/certs/client-cert.pem         |   73 +
 integration-tests/certs/client-key.pem          |   28 +
 integration-tests/client.conf                   |   27 +
 integration-tests/standalone.conf               |  280 ++
 integration-tests/tokens/secret.key             |    1 +
 integration-tests/tokens/token.txt              |    1 +
 perf/perf-consumer.go                           |  113 +
 perf/perf-producer.go                           |  147 +
 perf/pulsar-perf-go.go                          |   49 +
 pulsar-test-service-start.sh                    |   79 +
 pulsar-test-service-stop.sh                     |   35 +
 pulsar/client.go                                |  113 +
 pulsar/consumer.go                              |  179 +
 pulsar/error.go                                 |  103 +
 pulsar/impl_client.go                           |  152 +
 pulsar/impl_client_test.go                      |  203 ++
 pulsar/impl_message.go                          |   78 +
 pulsar/impl_message_test.go                     |   48 +
 pulsar/impl_partition_producer.go               |  427 +++
 pulsar/impl_producer.go                         |  156 +
 pulsar/internal/auth/disabled.go                |   49 +
 pulsar/internal/auth/provider.go                |   63 +
 pulsar/internal/auth/tls.go                     |   64 +
 pulsar/internal/auth/token.go                   |   98 +
 pulsar/internal/backoff.go                      |   45 +
 pulsar/internal/batch_builder.go                |  166 +
 pulsar/internal/buffer.go                       |  193 ++
 pulsar/internal/buffer_test.go                  |   38 +
 pulsar/internal/checksum.go                     |   28 +
 pulsar/internal/closable.go                     |   24 +
 pulsar/internal/commands.go                     |  141 +
 pulsar/internal/commands_test.go                |   45 +
 pulsar/internal/compression/compression.go      |   33 +
 pulsar/internal/compression/compression_test.go |   71 +
 pulsar/internal/compression/lz4.go              |   47 +
 pulsar/internal/compression/noop.go             |   35 +
 pulsar/internal/compression/zlib.go             |   54 +
 pulsar/internal/compression/zstd.go             |   39 +
 pulsar/internal/connection.go                   |  484 +++
 pulsar/internal/connection_pool.go              |   85 +
 pulsar/internal/connection_reader.go            |  136 +
 pulsar/internal/default_router.go               |   69 +
 pulsar/internal/default_router_test.go          |   85 +
 pulsar/internal/hash.go                         |   38 +
 pulsar/internal/hash_test.go                    |   59 +
 pulsar/internal/lookup_service.go               |  132 +
 pulsar/internal/lookup_service_test.go          |  268 ++
 pulsar/internal/pulsar_proto/PulsarApi.pb.go    | 4043 +++++++++++++++++++++++
 pulsar/internal/rpc_client.go                   |  124 +
 pulsar/internal/topic_name.go                   |  107 +
 pulsar/internal/topic_name_test.go              |   87 +
 pulsar/internal/util/blocking_queue.go          |  203 ++
 pulsar/internal/util/blocking_queue_test.go     |  137 +
 pulsar/internal/util/semaphore.go               |   30 +
 pulsar/internal/utils.go                        |   39 +
 pulsar/message.go                               |   88 +
 pulsar/producer.go                              |  167 +
 pulsar/producer_test.go                         |  181 +
 pulsar/reader.go                                |   84 +
 pulsar/test_helper.go                           |   43 +
 68 files changed, 10783 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 Dockerfile
 create mode 100644 LICENSE
 create mode 100644 NOTICE
 create mode 100644 integration-tests/certs/broker-cert.pem
 create mode 100644 integration-tests/certs/broker-key.pem
 create mode 100644 integration-tests/certs/cacert.pem
 create mode 100644 integration-tests/certs/client-cert.pem
 create mode 100644 integration-tests/certs/client-key.pem
 create mode 100644 integration-tests/client.conf
 create mode 100644 integration-tests/standalone.conf
 create mode 100644 integration-tests/tokens/secret.key
 create mode 100644 integration-tests/tokens/token.txt
 create mode 100644 perf/perf-consumer.go
 create mode 100644 perf/perf-producer.go
 create mode 100644 perf/pulsar-perf-go.go
 create mode 100755 pulsar-test-service-start.sh
 create mode 100755 pulsar-test-service-stop.sh
 create mode 100644 pulsar/client.go
 create mode 100644 pulsar/consumer.go
 create mode 100644 pulsar/error.go
 create mode 100644 pulsar/impl_client.go
 create mode 100644 pulsar/impl_client_test.go
 create mode 100644 pulsar/impl_message.go
 create mode 100644 pulsar/impl_message_test.go
 create mode 100644 pulsar/impl_partition_producer.go
 create mode 100644 pulsar/impl_producer.go
 create mode 100644 pulsar/internal/auth/disabled.go
 create mode 100644 pulsar/internal/auth/provider.go
 create mode 100644 pulsar/internal/auth/tls.go
 create mode 100644 pulsar/internal/auth/token.go
 create mode 100644 pulsar/internal/backoff.go
 create mode 100644 pulsar/internal/batch_builder.go
 create mode 100644 pulsar/internal/buffer.go
 create mode 100644 pulsar/internal/buffer_test.go
 create mode 100644 pulsar/internal/checksum.go
 create mode 100644 pulsar/internal/closable.go
 create mode 100644 pulsar/internal/commands.go
 create mode 100644 pulsar/internal/commands_test.go
 create mode 100644 pulsar/internal/compression/compression.go
 create mode 100644 pulsar/internal/compression/compression_test.go
 create mode 100644 pulsar/internal/compression/lz4.go
 create mode 100644 pulsar/internal/compression/noop.go
 create mode 100644 pulsar/internal/compression/zlib.go
 create mode 100644 pulsar/internal/compression/zstd.go
 create mode 100644 pulsar/internal/connection.go
 create mode 100644 pulsar/internal/connection_pool.go
 create mode 100644 pulsar/internal/connection_reader.go
 create mode 100644 pulsar/internal/default_router.go
 create mode 100644 pulsar/internal/default_router_test.go
 create mode 100644 pulsar/internal/hash.go
 create mode 100644 pulsar/internal/hash_test.go
 create mode 100644 pulsar/internal/lookup_service.go
 create mode 100644 pulsar/internal/lookup_service_test.go
 create mode 100644 pulsar/internal/pulsar_proto/PulsarApi.pb.go
 create mode 100644 pulsar/internal/rpc_client.go
 create mode 100644 pulsar/internal/topic_name.go
 create mode 100644 pulsar/internal/topic_name_test.go
 create mode 100644 pulsar/internal/util/blocking_queue.go
 create mode 100644 pulsar/internal/util/blocking_queue_test.go
 create mode 100644 pulsar/internal/util/semaphore.go
 create mode 100644 pulsar/internal/utils.go
 create mode 100644 pulsar/message.go
 create mode 100644 pulsar/producer.go
 create mode 100644 pulsar/producer_test.go
 create mode 100644 pulsar/reader.go
 create mode 100644 pulsar/test_helper.go


[pulsar-client-go] 28/38: Added MessageID implementation

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit 05bc67ffc3b6d2c486481dc38dd65ad8b829596a
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Sun May 5 10:56:14 2019 -0700

    Added MessageID implementation
---
 pulsar/impl_message.go            | 59 +++++++++++++++++++++++++++++++++++++++
 pulsar/impl_message_test.go       | 29 +++++++++++++++++++
 pulsar/impl_partition_producer.go | 15 ++++++++--
 pulsar/impl_producer.go           |  2 +-
 pulsar/message.go                 | 14 ++++------
 5 files changed, 107 insertions(+), 12 deletions(-)

diff --git a/pulsar/impl_message.go b/pulsar/impl_message.go
new file mode 100644
index 0000000..5d54678
--- /dev/null
+++ b/pulsar/impl_message.go
@@ -0,0 +1,59 @@
+package pulsar
+
+import (
+	"github.com/golang/protobuf/proto"
+	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+)
+
+type messageId struct {
+	ledgerID     int64
+	entryID      int64
+	batchIdx     int
+	partitionIdx int
+}
+
+func newMessageId(ledgerID int64, entryID int64, batchIdx int, partitionIdx int) MessageID {
+	return &messageId{
+		ledgerID:     ledgerID,
+		entryID:      entryID,
+		batchIdx:     batchIdx,
+		partitionIdx: partitionIdx,
+	}
+}
+
+func (id *messageId) Serialize() []byte {
+	msgId := &pb.MessageIdData{
+		LedgerId:   proto.Uint64(uint64(id.ledgerID)),
+		EntryId:    proto.Uint64(uint64(id.entryID)),
+		BatchIndex: proto.Int(id.batchIdx),
+		Partition:  proto.Int(id.partitionIdx),
+	}
+	data, _ := proto.Marshal(msgId)
+	return data
+}
+
+func deserializeMessageId(data []byte) (MessageID, error) {
+	msgId := &pb.MessageIdData{}
+	err := proto.Unmarshal(data, msgId)
+	if err != nil {
+		return nil, err
+	} else {
+		id := newMessageId(
+			int64(msgId.GetLedgerId()),
+			int64(msgId.GetEntryId()),
+			int(msgId.GetBatchIndex()),
+			int(msgId.GetPartition()),
+		)
+		return id, nil
+	}
+}
+
+func earliestMessageID() MessageID {
+	return newMessageId(-1, -1, -1, -1)
+}
+
+const maxLong int64 = 0x7fffffffffffffff
+
+func latestMessageID() MessageID {
+	return newMessageId(maxLong, maxLong, -1, -1)
+}
diff --git a/pulsar/impl_message_test.go b/pulsar/impl_message_test.go
new file mode 100644
index 0000000..ea64640
--- /dev/null
+++ b/pulsar/impl_message_test.go
@@ -0,0 +1,29 @@
+package pulsar
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestMessageId(t *testing.T) {
+	id := newMessageId(1,2, 3, 4)
+	bytes := id.Serialize()
+
+	id2, err := DeserializeMessageID(bytes)
+	assert.NoError(t, err)
+	assert.NotNil(t, id2)
+
+	assert.Equal(t, int64(1), id2.(*messageId).ledgerID)
+	assert.Equal(t, int64(2), id2.(*messageId).entryID)
+	assert.Equal(t, 3, id2.(*messageId).batchIdx)
+	assert.Equal(t, 4, id2.(*messageId).partitionIdx)
+
+	id, err = DeserializeMessageID(nil)
+	assert.Error(t, err)
+	assert.Nil(t, id)
+
+	id, err = DeserializeMessageID(make([]byte, 0))
+	assert.Error(t, err)
+	assert.Nil(t, id)
+}
diff --git a/pulsar/impl_partition_producer.go b/pulsar/impl_partition_producer.go
index 5f3f671..78f14af 100644
--- a/pulsar/impl_partition_producer.go
+++ b/pulsar/impl_partition_producer.go
@@ -41,11 +41,13 @@ type partitionProducer struct {
 	publishSemaphore util.Semaphore
 	pendingQueue     util.BlockingQueue
 	lastSequenceID   int64
+
+	partitionIdx int
 }
 
 const defaultBatchingMaxPublishDelay = 10 * time.Millisecond
 
-func newPartitionProducer(client *client, topic string, options *ProducerOptions) (*partitionProducer, error) {
+func newPartitionProducer(client *client, topic string, options *ProducerOptions, partitionIdx int) (*partitionProducer, error) {
 
 	var batchingMaxPublishDelay time.Duration
 	if options.BatchingMaxPublishDelay != 0 {
@@ -73,6 +75,7 @@ func newPartitionProducer(client *client, topic string, options *ProducerOptions
 		publishSemaphore: make(util.Semaphore, maxPendingMessages),
 		pendingQueue:     util.NewBlockingQueue(maxPendingMessages),
 		lastSequenceID:   -1,
+		partitionIdx:     partitionIdx,
 	}
 
 	if options.Name != "" {
@@ -313,12 +316,18 @@ func (p *partitionProducer) ReceivedSendReceipt(response *pb.CommandSendReceipt)
 
 	// The ack was indeed for the expected item in the queue, we can remove it and trigger the callback
 	p.pendingQueue.Poll()
-	for _, i := range pi.sendRequests {
+	for idx, i := range pi.sendRequests {
 		sr := i.(*sendRequest)
 		atomic.StoreInt64(&p.lastSequenceID, int64(pi.sequenceId))
 		if sr.callback != nil {
 			p.publishSemaphore.Release()
-			sr.callback(nil, sr.msg, nil)
+			msgID := newMessageId(
+				int64(response.MessageId.GetLedgerId()),
+				int64(response.MessageId.GetEntryId()),
+				idx,
+				p.partitionIdx,
+			)
+			sr.callback(msgID, sr.msg, nil)
 		}
 	}
 }
diff --git a/pulsar/impl_producer.go b/pulsar/impl_producer.go
index 12e5e7c..aec4eb5 100644
--- a/pulsar/impl_producer.go
+++ b/pulsar/impl_producer.go
@@ -59,7 +59,7 @@ func newProducer(client *client, options *ProducerOptions) (*producer, error) {
 
 	for partitionIdx, partition := range partitions {
 		go func() {
-			prod, err := newPartitionProducer(client, partition, options)
+			prod, err := newPartitionProducer(client, partition, options, partitionIdx)
 			c <- ProducerError{partitionIdx, prod, err}
 		}()
 	}
diff --git a/pulsar/message.go b/pulsar/message.go
index e987ad1..8323aa1 100644
--- a/pulsar/message.go
+++ b/pulsar/message.go
@@ -75,16 +75,14 @@ type MessageID interface {
 }
 
 // Reconstruct a MessageID object from its serialized representation
-func DeserializeMessageID(data []byte) MessageID {
-	// TODO
-	//return deserializeMessageId(data)
-	return nil
+func DeserializeMessageID(data []byte) (MessageID, error) {
+	return deserializeMessageId(data)
 }
 
 var (
-// MessageID that points to the earliest message available in a topic
-// TODO: EarliestMessage MessageID = earliestMessageID()
+	// MessageID that points to the earliest message available in a topic
+	EarliestMessage MessageID = earliestMessageID()
 
-// MessageID that points to the latest message
-// TODO: LatestMessage MessageID = latestMessageID()
+	// MessageID that points to the latest message
+	LatestMessage MessageID = latestMessageID()
 )


[pulsar-client-go] 16/38: Producer close

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit 98552f3665b7f2964aeef2484de3e3db2b150b4c
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Thu Apr 11 11:39:59 2019 -0700

    Producer close
---
 pulsar/impl/commands.go           |  2 ++
 pulsar/impl/connection.go         |  8 +++++
 pulsar/impl/rpc_client.go         | 20 ++++++++++++
 pulsar/impl_partition_producer.go | 65 ++++++++++++++++++++++++++++++++++++---
 4 files changed, 90 insertions(+), 5 deletions(-)

diff --git a/pulsar/impl/commands.go b/pulsar/impl/commands.go
index 08f8124..c2db791 100644
--- a/pulsar/impl/commands.go
+++ b/pulsar/impl/commands.go
@@ -29,6 +29,8 @@ func baseCommand(cmdType pb.BaseCommand_Type, msg proto.Message) *pb.BaseCommand
 		cmd.Pong = msg.(*pb.CommandPong)
 	case pb.BaseCommand_SEND:
 		cmd.Send = msg.(*pb.CommandSend)
+	case pb.BaseCommand_CLOSE_PRODUCER:
+		cmd.CloseProducer = msg.(*pb.CommandCloseProducer)
 	default:
 		log.Panic("Missing command type: ", cmdType)
 	}
diff --git a/pulsar/impl/connection.go b/pulsar/impl/connection.go
index 0cba51d..8cdcf8b 100644
--- a/pulsar/impl/connection.go
+++ b/pulsar/impl/connection.go
@@ -25,6 +25,7 @@ type Connection interface {
 	SendRequest(requestId uint64, req *pb.BaseCommand, callback func(command *pb.BaseCommand))
 	WriteData(data []byte)
 	RegisterListener(id uint64, listener ConnectionListener)
+	UnregisterListener(id uint64)
 	Close()
 }
 
@@ -351,6 +352,13 @@ func (c *connection) RegisterListener(id uint64, listener ConnectionListener) {
 	c.listeners[id] = listener
 }
 
+func (c *connection) UnregisterListener(id uint64) {
+	c.Lock()
+	defer c.Unlock()
+
+	delete(c.listeners, id)
+}
+
 func (c *connection) Close() {
 	c.Lock()
 	defer c.Unlock()
diff --git a/pulsar/impl/rpc_client.go b/pulsar/impl/rpc_client.go
index 2aa6106..706b9a1 100644
--- a/pulsar/impl/rpc_client.go
+++ b/pulsar/impl/rpc_client.go
@@ -26,6 +26,8 @@ type RpcClient interface {
 
 	Request(logicalAddr *url.URL, physicalAddr *url.URL, requestId uint64,
 		cmdType pb.BaseCommand_Type, message proto.Message) (*RpcResult, error)
+
+	RequestOnCnx(cnx Connection, requestId uint64, cmdType pb.BaseCommand_Type, message proto.Message) (*RpcResult, error)
 }
 
 type rpcClient struct {
@@ -72,6 +74,24 @@ func (c *rpcClient) Request(logicalAddr *url.URL, physicalAddr *url.URL, request
 	return rpcResult, nil
 }
 
+func (c *rpcClient) RequestOnCnx(cnx Connection, requestId uint64, cmdType pb.BaseCommand_Type,
+	message proto.Message) (*RpcResult, error) {
+	wg := sync.WaitGroup{}
+	wg.Add(1)
+
+	rpcResult := &RpcResult{
+		Cnx: cnx,
+	}
+
+	cnx.SendRequest(requestId, baseCommand(cmdType, message), func(response *pb.BaseCommand) {
+		rpcResult.Response = response
+		wg.Done()
+	})
+
+	wg.Wait()
+	return rpcResult, nil
+}
+
 func (c *rpcClient) NewRequestId() uint64 {
 	return atomic.AddUint64(&c.requestIdGenerator, 1)
 }
diff --git a/pulsar/impl_partition_producer.go b/pulsar/impl_partition_producer.go
index d0cd8cf..c99ea67 100644
--- a/pulsar/impl_partition_producer.go
+++ b/pulsar/impl_partition_producer.go
@@ -11,12 +11,20 @@ import (
 	"time"
 )
 
+type producerState int
+
+const (
+	producerInit = iota
+	producerReady
+	producerClosing
+	producerClosed
+)
+
 type partitionProducer struct {
+	state  producerState
 	client *client
 	topic  string
 	log    *log.Entry
-	mutex  sync.Mutex
-	cond   *sync.Cond
 	cnx    impl.Connection
 
 	options             *ProducerOptions
@@ -52,6 +60,7 @@ func newPartitionProducer(client *client, topic string, options *ProducerOptions
 	}
 
 	p := &partitionProducer{
+		state:            producerInit,
 		log:              log.WithField("topic", topic),
 		client:           client,
 		topic:            topic,
@@ -74,6 +83,7 @@ func newPartitionProducer(client *client, topic string, options *ProducerOptions
 	} else {
 		p.log = p.log.WithField("name", *p.producerName)
 		p.log.Info("Created producer")
+		p.state = producerReady
 		go p.runEventsLoop()
 		return p, nil
 	}
@@ -136,6 +146,11 @@ func (p *partitionProducer) reconnectToBroker() {
 	p.log.Info("Reconnecting to broker")
 	backoff := impl.Backoff{}
 	for {
+		if p.state != producerReady {
+			// Producer is already closing
+			return
+		}
+
 		err := p.grabCnx()
 		if err == nil {
 			// Successfully reconnected
@@ -153,6 +168,10 @@ func (p *partitionProducer) runEventsLoop() {
 	for {
 		select {
 		case i := <-p.eventsChan:
+			if i == nil {
+				return
+			}
+
 			switch v := i.(type) {
 			case *sendRequest:
 				p.internalSend(v)
@@ -286,6 +305,33 @@ func (p *partitionProducer) ReceivedSendReceipt(response *pb.CommandSendReceipt)
 	}
 }
 
+func (p *partitionProducer) internalClose(req *closeProducer) {
+	if p.state != producerReady {
+		req.waitGroup.Done()
+		return
+	}
+
+	p.state = producerClosing
+	p.log.Info("Closing producer")
+
+	id := p.client.rpcClient.NewRequestId()
+	_, err := p.client.rpcClient.RequestOnCnx(p.cnx, id, pb.BaseCommand_CLOSE_PRODUCER, &pb.CommandCloseProducer{
+		ProducerId: &p.producerId,
+		RequestId:  &id,
+	})
+
+	if err != nil {
+		req.err = err
+	} else {
+		p.log.Info("Closed producer")
+		p.state = producerClosed
+		p.cnx.UnregisterListener(p.producerId)
+		p.batchFlushTicker.Stop()
+	}
+
+	req.waitGroup.Done()
+}
+
 func (p *partitionProducer) LastSequenceID() int64 {
 	// TODO: return real last sequence id
 	return -1
@@ -296,8 +342,15 @@ func (p *partitionProducer) Flush() error {
 }
 
 func (p *partitionProducer) Close() error {
-	p.log.Info("Closing producer")
-	return nil
+
+	wg := sync.WaitGroup{}
+	wg.Add(1)
+
+	cp := &closeProducer{&wg, nil}
+	p.eventsChan <- cp
+
+	wg.Wait()
+	return cp.err
 }
 
 type sendRequest struct {
@@ -308,4 +361,6 @@ type sendRequest struct {
 }
 
 type closeProducer struct {
-}
\ No newline at end of file
+	waitGroup *sync.WaitGroup
+	err       error
+}


[pulsar-client-go] 34/38: Added scripts to start test service

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit fe3258b10c9b81e120c4600a1aec961df836b5e7
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Wed May 8 10:58:37 2019 -0700

    Added scripts to start test service
---
 Dockerfile                              |  41 +++++
 integration-tests/certs/broker-cert.pem |  73 +++++++++
 integration-tests/certs/broker-key.pem  |  28 ++++
 integration-tests/certs/cacert.pem      |  62 +++++++
 integration-tests/certs/client-cert.pem |  73 +++++++++
 integration-tests/certs/client-key.pem  |  28 ++++
 integration-tests/client.conf           |  27 +++
 integration-tests/standalone.conf       | 280 ++++++++++++++++++++++++++++++++
 pulsar-test-service-start.sh            |  79 +++++++++
 pulsar-test-service-stop.sh             |  35 ++++
 10 files changed, 726 insertions(+)

diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..3451983
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,41 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+FROM golang:1.12 as go
+
+FROM apachepulsar/pulsar:latest
+
+COPY --from=go /usr/local/go /usr/local/go
+ENV PATH /root/go/bin:/usr/local/go/bin:$PATH
+
+### Add test scripts
+
+COPY integration-tests/certs /pulsar/certs
+COPY integration-tests/standalone.conf /pulsar/conf
+COPY integration-tests/client.conf /pulsar/conf
+COPY pulsar-test-service-start.sh /pulsar/bin
+COPY pulsar-test-service-stop.sh /pulsar/bin
+
+# Initialize test configuration and credentials
+RUN mkdir /pulsar/tokens
+RUN /pulsar/bin/pulsar tokens create-secret-key --output /pulsar/tokens/secret.key
+RUN /pulsar/bin/pulsar tokens create \
+                --subject token-principal \
+                --secret-key file:///pulsar/tokens/secret.key \
+                > /pulsar/tokens/token.txt
diff --git a/integration-tests/certs/broker-cert.pem b/integration-tests/certs/broker-cert.pem
new file mode 100644
index 0000000..69ad71c
--- /dev/null
+++ b/integration-tests/certs/broker-cert.pem
@@ -0,0 +1,73 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            88:08:98:b3:13:d8:00:97
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, ST=CA, O=Apache, OU=Pulsar Incubator, CN=localhost
+        Validity
+            Not Before: Feb 17 02:06:21 2018 GMT
+            Not After : Nov 16 00:00:00 2030 GMT
+        Subject: C=US, ST=CA, O=Apache, OU=Apache Pulsar, CN=localhost
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:af:bf:b7:2d:98:ad:9d:f6:da:a3:13:d4:62:0f:
+                    98:be:1c:a2:89:22:ba:6f:d5:fd:1f:67:e3:91:03:
+                    98:80:81:0e:ed:d8:f6:70:7f:2c:36:68:3d:53:ea:
+                    58:3a:a6:d5:89:66:4b:bd:1e:57:71:13:6d:4b:11:
+                    e5:40:a5:76:84:24:92:40:58:80:96:c9:1f:2c:c4:
+                    55:eb:a3:79:73:70:5c:37:9a:89:ed:2f:ba:6b:e3:
+                    82:7c:69:4a:02:54:8b:81:5e:3c:bf:4c:8a:cb:ea:
+                    2c:5e:83:e7:b7:10:08:5f:82:58:a3:89:d1:da:92:
+                    ba:2a:28:ee:30:28:3f:5b:ae:10:71:96:c7:e1:12:
+                    c5:b0:1a:ad:44:6f:44:3a:11:4a:9a:3c:0f:8d:06:
+                    80:7b:34:ef:3f:6c:f4:5e:c5:44:54:1e:c8:dd:c7:
+                    80:85:80:d9:68:e6:c6:53:03:77:e1:fe:18:61:07:
+                    77:05:4c:ed:59:bc:5d:41:38:6a:ef:5d:a1:b2:60:
+                    98:d4:48:28:95:02:8a:0e:fd:cf:7b:1b:d2:11:cc:
+                    10:0c:50:73:d7:cc:38:6c:83:dd:79:26:aa:90:c8:
+                    9b:84:86:bc:59:e9:62:69:f4:98:1b:c4:80:78:7e:
+                    a0:1a:81:9d:d2:e1:66:dd:c4:cc:fc:63:04:ac:ec:
+                    a7:35
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                D3:F3:19:AE:74:B1:AF:E7:AF:08:7B:16:72:78:29:87:79:ED:30:8C
+            X509v3 Authority Key Identifier: 
+                keyid:D4:7A:CD:0F:44:1B:16:29:25:14:ED:A2:EF:13:0F:A7:46:09:78:F6
+
+    Signature Algorithm: sha1WithRSAEncryption
+        0f:04:f3:91:f2:87:19:fe:9d:f8:34:5a:24:4a:00:d1:58:bf:
+        1e:b2:77:67:07:bc:78:b5:4b:9a:4b:fd:a1:e5:dc:0e:09:84:
+        9e:59:c4:dd:cf:f7:2e:bf:da:f3:31:36:6b:81:6e:a2:88:76:
+        e4:2e:0b:36:44:82:36:8f:80:93:f4:9e:fc:ed:85:d0:97:da:
+        0f:fb:c9:b9:8b:da:ae:07:3d:4f:82:b7:0c:25:22:63:12:6b:
+        0a:e9:c4:12:a4:5c:ed:11:12:cc:fe:b0:2e:d4:c1:ec:79:01:
+        60:ea:cc:cc:e5:66:cc:57:f6:55:a9:09:4c:63:01:e9:b4:2e:
+        73:a5
+-----BEGIN CERTIFICATE-----
+MIIDLjCCApegAwIBAgIJAIgImLMT2ACXMA0GCSqGSIb3DQEBBQUAMFoxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJDQTEPMA0GA1UEChMGQXBhY2hlMRkwFwYDVQQLExBQ
+dWxzYXIgSW5jdWJhdG9yMRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMTgwMjE3MDIw
+NjIxWhcNMzAxMTE2MDAwMDAwWjBXMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
+DzANBgNVBAoTBkFwYWNoZTEWMBQGA1UECxMNQXBhY2hlIFB1bHNhcjESMBAGA1UE
+AxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr7+3
+LZitnfbaoxPUYg+YvhyiiSK6b9X9H2fjkQOYgIEO7dj2cH8sNmg9U+pYOqbViWZL
+vR5XcRNtSxHlQKV2hCSSQFiAlskfLMRV66N5c3BcN5qJ7S+6a+OCfGlKAlSLgV48
+v0yKy+osXoPntxAIX4JYo4nR2pK6KijuMCg/W64QcZbH4RLFsBqtRG9EOhFKmjwP
+jQaAezTvP2z0XsVEVB7I3ceAhYDZaObGUwN34f4YYQd3BUztWbxdQThq712hsmCY
+1EgolQKKDv3PexvSEcwQDFBz18w4bIPdeSaqkMibhIa8WeliafSYG8SAeH6gGoGd
+0uFm3cTM/GMErOynNQIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQf
+Fh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU0/MZrnSx
+r+evCHsWcngph3ntMIwwHwYDVR0jBBgwFoAU1HrND0QbFiklFO2i7xMPp0YJePYw
+DQYJKoZIhvcNAQEFBQADgYEADwTzkfKHGf6d+DRaJEoA0Vi/HrJ3Zwe8eLVLmkv9
+oeXcDgmEnlnE3c/3Lr/a8zE2a4Fuooh25C4LNkSCNo+Ak/Se/O2F0JfaD/vJuYva
+rgc9T4K3DCUiYxJrCunEEqRc7RESzP6wLtTB7HkBYOrMzOVmzFf2VakJTGMB6bQu
+c6U=
+-----END CERTIFICATE-----
diff --git a/integration-tests/certs/broker-key.pem b/integration-tests/certs/broker-key.pem
new file mode 100644
index 0000000..004bf8e
--- /dev/null
+++ b/integration-tests/certs/broker-key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCvv7ctmK2d9tqj
+E9RiD5i+HKKJIrpv1f0fZ+ORA5iAgQ7t2PZwfyw2aD1T6lg6ptWJZku9HldxE21L
+EeVApXaEJJJAWICWyR8sxFXro3lzcFw3montL7pr44J8aUoCVIuBXjy/TIrL6ixe
+g+e3EAhfglijidHakroqKO4wKD9brhBxlsfhEsWwGq1Eb0Q6EUqaPA+NBoB7NO8/
+bPRexURUHsjdx4CFgNlo5sZTA3fh/hhhB3cFTO1ZvF1BOGrvXaGyYJjUSCiVAooO
+/c97G9IRzBAMUHPXzDhsg915JqqQyJuEhrxZ6WJp9JgbxIB4fqAagZ3S4WbdxMz8
+YwSs7Kc1AgMBAAECggEAAaWEK9MwXTiA1+JJrRmETtOp2isPIBkbI/4vLZ6hASM0
+ZpoPxQIMAf58BJs/dF03xu/EaeMs4oxSC9ABG9fxAk/tZtjta3w65Ip6W5jOfHxj
+AMpb3HMEBhq9kDjUTq1IGVAutYQcEMkC3WfS9e4ahfqMpguWgbu6LsbvZFgcL9mv
+pGnKv9YVe6Xk6isvqtq6G1af0rd7c//xF0i0e/qEo83Buok3gLEZOELZbcRxjUYc
+jnyglnXnwkGjuL4E3wgS3l73ZKsb6+AYoqhMPVz8t4/PN3tTrsBJKOSYo8KzIm0U
+ek9T8XmPbP0cuheRxp9Dp8TXJJQZK0N9jz+EL0ogQQKBgQDnavm8GpR4pap9cDOc
++YI5s823b507pNdSU8elO9gLsP0JlFzv+sqghVko29r85D7Vn3MkgYTy0S4ANLCs
+0NFDY8N2QH6U1dTkk1QXZydVZDuKJ5SSpC4v+Vafl8yDxhB4Nlxhbm9vJEMfLcXh
+2kL6UlAuFDtYD0AdczwnHu5DjQKBgQDCauocm55FpcyDMMBO2CjurxcjBYS3S1xT
+Bz+sPtxJLjlKbAt8kSHUQcCcX9zhrQBfsT38LATCmKaOFqUW5/PPh2LcrxiMqlL1
+OJBUJ3Te2LTjlUn8r+DHv/69UIh5tchwRr3YgB0DuIs7jfmr4VfiOWTBtPVhoGFR
+1Wt60j30SQKBgHzreS26J2VNAFBALgxRf6OIVMbtgDG/FOCDCyU9vazp+F2gcd61
+QYYPFYcBzx9uUiDctroBFHRCyJMh3jEbc6ruAogl3m6XUxmkEeOkMk5dEerM3N2f
+tLL+5Gy385U6aI+LwKhzhcG4EGeXPNdjC362ykNldnddnB2Jo/H2N2XNAoGAdnft
+xpbxP+GDGKIZXTIM5zzcLWQMdiC+1n1BSHVZiGJZWMczzKknYw7aDq+/iekApE79
+xW8RS373ZvfXi3i2Mcx+6pjrrbOQL4tTL2SHq8+DknaDCi4mG7IbyUKMlxW1WO1S
+e929UGogtZ6S+DCte9WbVwosyFuRUetpvgLk67kCgYBWetihZjgBWrqVYT24TTRH
+KxzSzH1JgzzF9qgTdlhXDv9hC+Kc0uTKsgViesDqVuCOjkwzY5OQr9c6duO0fwwP
+qNk/qltdgjMC5iiv7duyukfbEuqKEdGGer9HFb7en96dZdVQJpYHaaslAGurtD80
+ejCQZgzR2XaHSuIQb0IUVQ==
+-----END PRIVATE KEY-----
diff --git a/integration-tests/certs/cacert.pem b/integration-tests/certs/cacert.pem
new file mode 100644
index 0000000..55e9067
--- /dev/null
+++ b/integration-tests/certs/cacert.pem
@@ -0,0 +1,62 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            88:08:98:b3:13:d8:00:94
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, ST=CA, O=Apache, OU=Pulsar Incubator, CN=localhost
+        Validity
+            Not Before: Feb 17 01:37:33 2018 GMT
+            Not After : Feb 16 01:37:33 2021 GMT
+        Subject: C=US, ST=CA, O=Apache, OU=Pulsar Incubator, CN=localhost
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (1024 bit)
+                Modulus (1024 bit):
+                    00:ea:16:8d:a5:b1:19:61:34:54:07:02:60:4e:6d:
+                    54:92:08:fd:fb:23:79:9c:05:bf:14:f7:bc:aa:db:
+                    2b:42:a4:35:74:86:e3:00:ad:8b:18:79:73:7d:f2:
+                    d1:74:dd:74:bc:b8:a2:4c:80:c9:f3:80:ce:bf:f8:
+                    6d:97:f5:05:4f:f4:b2:99:50:e8:d8:b0:c4:57:a0:
+                    e7:dc:82:57:75:2a:a2:02:21:76:f7:37:c2:dc:7c:
+                    4c:36:a6:73:6f:dc:75:48:72:ad:fa:98:02:70:b2:
+                    5e:a2:83:cc:c3:8d:20:a7:1e:bc:d7:1e:c1:d1:7e:
+                    39:35:4b:f5:be:6b:c1:0f:f9
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                D4:7A:CD:0F:44:1B:16:29:25:14:ED:A2:EF:13:0F:A7:46:09:78:F6
+            X509v3 Authority Key Identifier: 
+                keyid:D4:7A:CD:0F:44:1B:16:29:25:14:ED:A2:EF:13:0F:A7:46:09:78:F6
+                DirName:/C=US/ST=CA/O=Apache/OU=Pulsar Incubator/CN=localhost
+                serial:88:08:98:B3:13:D8:00:94
+
+            X509v3 Basic Constraints: 
+                CA:TRUE
+    Signature Algorithm: sha1WithRSAEncryption
+        5e:30:c5:7b:30:3e:1e:16:cd:ba:66:f1:2a:19:13:8a:1a:00:
+        08:f4:1e:8c:e4:3d:57:13:65:96:bf:07:58:55:52:37:3e:aa:
+        2c:19:de:ee:c3:92:6e:79:f3:06:0e:9a:7b:e0:02:50:c3:ef:
+        3b:84:ea:8f:e0:f0:16:a6:a6:67:8b:be:73:0e:5d:f7:88:39:
+        d3:d4:df:85:ad:7c:c1:4f:fa:55:55:6f:c2:48:4e:8e:82:fa:
+        72:3b:8e:9d:dc:f7:2e:9d:47:8e:e5:c9:a2:ee:b1:76:94:15:
+        7c:7a:62:bc:06:45:fa:61:2e:33:8c:18:3e:e9:d5:90:a5:a6:
+        80:5a
+-----BEGIN CERTIFICATE-----
+MIIC8jCCAlugAwIBAgIJAIgImLMT2ACUMA0GCSqGSIb3DQEBBQUAMFoxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJDQTEPMA0GA1UEChMGQXBhY2hlMRkwFwYDVQQLExBQ
+dWxzYXIgSW5jdWJhdG9yMRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMTgwMjE3MDEz
+NzMzWhcNMjEwMjE2MDEzNzMzWjBaMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
+DzANBgNVBAoTBkFwYWNoZTEZMBcGA1UECxMQUHVsc2FyIEluY3ViYXRvcjESMBAG
+A1UEAxMJbG9jYWxob3N0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDqFo2l
+sRlhNFQHAmBObVSSCP37I3mcBb8U97yq2ytCpDV0huMArYsYeXN98tF03XS8uKJM
+gMnzgM6/+G2X9QVP9LKZUOjYsMRXoOfcgld1KqICIXb3N8LcfEw2pnNv3HVIcq36
+mAJwsl6ig8zDjSCnHrzXHsHRfjk1S/W+a8EP+QIDAQABo4G/MIG8MB0GA1UdDgQW
+BBTUes0PRBsWKSUU7aLvEw+nRgl49jCBjAYDVR0jBIGEMIGBgBTUes0PRBsWKSUU
+7aLvEw+nRgl49qFepFwwWjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMQ8wDQYD
+VQQKEwZBcGFjaGUxGTAXBgNVBAsTEFB1bHNhciBJbmN1YmF0b3IxEjAQBgNVBAMT
+CWxvY2FsaG9zdIIJAIgImLMT2ACUMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF
+BQADgYEAXjDFezA+HhbNumbxKhkTihoACPQejOQ9VxNllr8HWFVSNz6qLBne7sOS
+bnnzBg6ae+ACUMPvO4Tqj+DwFqamZ4u+cw5d94g509Tfha18wU/6VVVvwkhOjoL6
+cjuOndz3Lp1HjuXJou6xdpQVfHpivAZF+mEuM4wYPunVkKWmgFo=
+-----END CERTIFICATE-----
diff --git a/integration-tests/certs/client-cert.pem b/integration-tests/certs/client-cert.pem
new file mode 100644
index 0000000..61847f2
--- /dev/null
+++ b/integration-tests/certs/client-cert.pem
@@ -0,0 +1,73 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            88:08:98:b3:13:d8:00:99
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, ST=CA, O=Apache, OU=Pulsar Incubator, CN=localhost
+        Validity
+            Not Before: Feb 17 02:50:05 2018 GMT
+            Not After : Nov 16 00:00:00 2030 GMT
+        Subject: C=US, ST=CA, O=Apache, OU=Apache Pulsar, CN=superUser
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:cd:43:7d:98:40:f9:b0:5b:bc:ae:db:c0:0b:ad:
+                    26:90:96:e0:62:38:ed:68:b1:70:46:3b:de:44:f9:
+                    14:51:86:10:eb:ca:90:e7:88:e8:f9:91:85:e0:dd:
+                    b5:b4:14:b9:78:e3:86:d5:54:6d:68:ec:14:92:b4:
+                    f8:22:5b:05:3d:ed:31:25:65:08:05:84:ca:e6:0c:
+                    21:12:58:32:c7:1a:60:a3:4f:d2:4a:9e:28:19:7c:
+                    45:84:00:8c:89:dc:de:8a:e5:4f:88:91:cc:a4:f1:
+                    81:45:4c:7d:c2:ff:e2:c1:89:c6:12:73:95:e2:36:
+                    bd:db:ae:8b:5a:68:6a:90:51:de:2b:88:5f:aa:67:
+                    f4:a8:e3:63:dc:be:19:82:cc:9d:7f:e6:8d:fb:82:
+                    be:22:01:3d:56:13:3b:5b:04:b4:e8:c5:18:e6:2e:
+                    0d:fa:ba:4a:8d:e8:c6:5a:a1:51:9a:4a:62:d7:af:
+                    dd:b4:fc:e2:d5:cd:ae:99:6c:5c:61:56:0b:d7:0c:
+                    1a:77:5c:f5:3a:6a:54:b5:9e:33:ac:a9:75:28:9a:
+                    76:af:d0:7a:57:00:1b:91:13:31:fd:42:88:21:47:
+                    05:10:01:2f:59:bb:c7:3a:d9:e1:58:4c:1b:6c:71:
+                    b6:98:ef:dd:03:82:58:a3:32:dc:90:a1:b6:a6:1e:
+                    e1:0b
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                53:7C:D5:D1:52:97:9A:D6:D5:EA:EC:B6:0C:9B:43:39:19:73:F6:2C
+            X509v3 Authority Key Identifier: 
+                keyid:D4:7A:CD:0F:44:1B:16:29:25:14:ED:A2:EF:13:0F:A7:46:09:78:F6
+
+    Signature Algorithm: sha1WithRSAEncryption
+        e4:03:82:ff:be:df:7c:73:2a:c5:8f:7d:87:ab:95:b1:2b:e5:
+        f7:41:22:4f:28:54:84:7a:cc:fe:70:89:0f:48:e5:8a:17:e1:
+        44:ad:12:e9:a1:3a:c7:84:55:f0:7c:29:52:0a:a1:ab:cc:5b:
+        31:e5:b2:37:73:3a:8d:f2:f1:fb:e8:f6:a2:b9:ef:11:10:f8:
+        31:43:8f:af:ce:09:f4:cb:96:0e:d4:58:42:6e:86:ab:b9:03:
+        19:8b:4a:6e:ef:50:c0:7e:c9:0b:1d:2b:42:bf:eb:d0:06:05:
+        84:ea:5a:8a:22:5c:56:fa:da:2a:9f:8a:b2:90:66:8c:5e:01:
+        87:45
+-----BEGIN CERTIFICATE-----
+MIIDLjCCApegAwIBAgIJAIgImLMT2ACZMA0GCSqGSIb3DQEBBQUAMFoxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJDQTEPMA0GA1UEChMGQXBhY2hlMRkwFwYDVQQLExBQ
+dWxzYXIgSW5jdWJhdG9yMRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMTgwMjE3MDI1
+MDA1WhcNMzAxMTE2MDAwMDAwWjBXMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
+DzANBgNVBAoTBkFwYWNoZTEWMBQGA1UECxMNQXBhY2hlIFB1bHNhcjESMBAGA1UE
+AxMJc3VwZXJVc2VyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzUN9
+mED5sFu8rtvAC60mkJbgYjjtaLFwRjveRPkUUYYQ68qQ54jo+ZGF4N21tBS5eOOG
+1VRtaOwUkrT4IlsFPe0xJWUIBYTK5gwhElgyxxpgo0/SSp4oGXxFhACMidzeiuVP
+iJHMpPGBRUx9wv/iwYnGEnOV4ja9266LWmhqkFHeK4hfqmf0qONj3L4Zgsydf+aN
++4K+IgE9VhM7WwS06MUY5i4N+rpKjejGWqFRmkpi16/dtPzi1c2umWxcYVYL1wwa
+d1z1OmpUtZ4zrKl1KJp2r9B6VwAbkRMx/UKIIUcFEAEvWbvHOtnhWEwbbHG2mO/d
+A4JYozLckKG2ph7hCwIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQf
+Fh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUU3zV0VKX
+mtbV6uy2DJtDORlz9iwwHwYDVR0jBBgwFoAU1HrND0QbFiklFO2i7xMPp0YJePYw
+DQYJKoZIhvcNAQEFBQADgYEA5AOC/77ffHMqxY99h6uVsSvl90EiTyhUhHrM/nCJ
+D0jlihfhRK0S6aE6x4RV8HwpUgqhq8xbMeWyN3M6jfLx++j2ornvERD4MUOPr84J
+9MuWDtRYQm6Gq7kDGYtKbu9QwH7JCx0rQr/r0AYFhOpaiiJcVvraKp+KspBmjF4B
+h0U=
+-----END CERTIFICATE-----
diff --git a/integration-tests/certs/client-key.pem b/integration-tests/certs/client-key.pem
new file mode 100644
index 0000000..3835b3e
--- /dev/null
+++ b/integration-tests/certs/client-key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDNQ32YQPmwW7yu
+28ALrSaQluBiOO1osXBGO95E+RRRhhDrypDniOj5kYXg3bW0FLl444bVVG1o7BSS
+tPgiWwU97TElZQgFhMrmDCESWDLHGmCjT9JKnigZfEWEAIyJ3N6K5U+Ikcyk8YFF
+TH3C/+LBicYSc5XiNr3brotaaGqQUd4riF+qZ/So42PcvhmCzJ1/5o37gr4iAT1W
+EztbBLToxRjmLg36ukqN6MZaoVGaSmLXr920/OLVza6ZbFxhVgvXDBp3XPU6alS1
+njOsqXUomnav0HpXABuREzH9QoghRwUQAS9Zu8c62eFYTBtscbaY790DglijMtyQ
+obamHuELAgMBAAECggEBALGnokJuqiz7mTj2NSdl+6TVEOuyPbiJKpV/J4cm1XEh
+ye9qaTQcCRhH3UmcWrG75jM9KevloLRY8A1x1/lUMhtA+XJWGTU9k6a8BLut3nT4
+3X87jNTMQgSczEXNe9WudmZcxhN7rVVtOOdTpt1pP0cnCWna5HTf0D8cuLvM975j
+r1YGTjKsCF1W+tp6ZAIIMfJkUI2qBRKvSxVCSs1vZBraox3yUVnq9oRLHxZZoqOd
+d51G5phRtn6ReVPBdT8fGUBEGg3jKxTu2/vLQMUyHy0hyCAM20gzOP4FIc2g+QZU
+y42byAuc89m0OrdRWsmzHCOxcq9DwY9npaz1RscR/2ECgYEA9bHJQ0Y1afpS5gn2
+KnXenRIw9oal1utQZnohCEJ4um+K/BCEHtDnI825LPNf34IKM2rSmssvHrYN51o0
+92j9lHHXsf6MVluwsTsIu8MtNaJ1BLt96dub4ScGT6vvzObKTwsajUfIHk+FNsKq
+zps8yh1q0qyyfAcvR82+Xr6JIsMCgYEA1d+RHGewi/Ub/GCG99A1KFKsgbiIJnWB
+IFmrcyPWignhzDUcw2SV9XqAzeK8EOIHNq3e5U/tkA7aCWxtLb5UsQ8xvmwQY2cy
+X2XvSdIhO4K2PgRLgjlzZ8RHSULglqyjB2i6TjwjFl8TsRzYr6JlV6+2cMujw4Bl
+g3a8gz071BkCgYBLP7BMkmw5kRliqxph1sffg3rLhmG0eU2elTkYtoMTVqZSnRxZ
+89FW/eMBCWkLo2BMbyMhlalQ1qFbgh1GyTkhBdzx/uwsZtiu7021dAmcq6z7ThE6
+VrBfPPyJ2jcPon/DxbrUGnAIGILMSsLVlGYB4RCehZYEto6chz8O9Xw60QKBgCnd
+us1BqviqwZC04JbQJie/j09RbS2CIQXRJ9PBNzUMXCwaVYgWP5ivI1mqQcBYTqsw
+fAqNi+aAUcQ4emLS+Ec0vzsUclzTDbRJAv+DZ8f7fWtEcfeLAYFVldLMiaRVJRDF
+OnsoIII3mGY6TFyNQKNanS8VXfheQQDsFFjoera5AoGBALXYEXkESXpw4LT6qJFz
+ktQuTZDfS6LtR14/+NkYL9c5wBC4Otkg4bNbT8xGlUjethRfpkm8xRTB6zfC1/p/
+Cg6YU1cwqlkRurAhE3PEv1dCc1IDbzou8xnwqHrd6sGPDQmQ3aEtU5eJhDZKIZfx
+nQqPGK92+Jtne7+W1mFZooxs
+-----END PRIVATE KEY-----
diff --git a/integration-tests/client.conf b/integration-tests/client.conf
new file mode 100644
index 0000000..011a6dd
--- /dev/null
+++ b/integration-tests/client.conf
@@ -0,0 +1,27 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Pulsar Client configuration
+webServiceUrl=https://localhost:8443/
+brokerServiceUrl=pulsar+ssl://localhost:6651/
+tlsAllowInsecureConnection=false
+tlsTrustCertsFilePath=/pulsar/certs/cacert.pem
+authPlugin=org.apache.pulsar.client.impl.auth.AuthenticationTls
+authParams=tlsCertFile:/pulsar/certs/client-cert.pem,tlsKeyFile:/pulsar/certs/client-key.pem
+
diff --git a/integration-tests/standalone.conf b/integration-tests/standalone.conf
new file mode 100644
index 0000000..d81f493
--- /dev/null
+++ b/integration-tests/standalone.conf
@@ -0,0 +1,280 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+### --- General broker settings --- ###
+
+# Zookeeper quorum connection string
+zookeeperServers=
+
+# Deprecated. Global zookeeper quorum connection string
+globalZookeeperServers=
+
+# Configuration Store connection string
+configurationStoreServers=
+
+brokerServicePort=6650
+brokerServicePortTls=6651
+
+# Port to use to server HTTP request
+webServicePort=8080
+webServicePortTls=8443
+
+# Hostname or IP address the service binds on, default is 0.0.0.0.
+bindAddress=0.0.0.0
+
+# Hostname or IP address the service advertises to the outside world. If not set, the value of InetAddress.getLocalHost().getHostName() is used.
+advertisedAddress=localhost
+
+# Name of the cluster to which this broker belongs to
+clusterName=standalone
+
+# Zookeeper session timeout in milliseconds
+zooKeeperSessionTimeoutMillis=30000
+
+# Time to wait for broker graceful shutdown. After this time elapses, the process will be killed
+brokerShutdownTimeoutMs=3000
+
+# Enable backlog quota check. Enforces action on topic when the quota is reached
+backlogQuotaCheckEnabled=true
+
+# How often to check for topics that have reached the quota
+backlogQuotaCheckIntervalInSeconds=60
+
+# Default per-topic backlog quota limit
+backlogQuotaDefaultLimitGB=10
+
+# Enable the deletion of inactive topics
+brokerDeleteInactiveTopicsEnabled=true
+
+# How often to check for inactive topics
+brokerDeleteInactiveTopicsFrequencySeconds=60
+
+# How frequently to proactively check and purge expired messages
+messageExpiryCheckIntervalInMinutes=5
+
+# Enable check for minimum allowed client library version
+clientLibraryVersionCheckEnabled=false
+
+# Allow client libraries with no version information
+clientLibraryVersionCheckAllowUnversioned=true
+
+# Path for the file used to determine the rotation status for the broker when responding
+# to service discovery health checks
+statusFilePath=/usr/local/apache/htdocs
+
+# Max number of unacknowledged messages allowed to receive messages by a consumer on a shared subscription. Broker will stop sending
+# messages to consumer once, this limit reaches until consumer starts acknowledging messages back
+# Using a value of 0, is disabling unackeMessage limit check and consumer can receive messages without any restriction
+maxUnackedMessagesPerConsumer=50000
+
+### --- Authentication --- ###
+
+# Enable TLS
+tlsEnabled=true
+tlsCertificateFilePath=/pulsar/certs/broker-cert.pem
+tlsKeyFilePath=/pulsar/certs/broker-key.pem
+tlsTrustCertsFilePath=/pulsar/certs/cacert.pem
+tlsAllowInsecureConnection=false
+
+anonymousUserRole=anonymous
+
+# Enable authentication
+authenticationEnabled=true
+
+# Autentication provider name list, which is comma separated list of class names
+authenticationProviders=org.apache.pulsar.broker.authentication.AuthenticationProviderTls,org.apache.pulsar.broker.authentication.AuthenticationProviderToken
+
+# Enforce authorization
+authorizationEnabled=true
+
+tokenSecretKey=file:///pulsar/tokens/secret.key
+
+# Role names that are treated as "super-user", meaning they will be able to do all admin
+# operations and publish/consume from all topics
+superUserRoles=localhost,superUser
+
+# Authentication settings of the broker itself. Used when the broker connects to other brokers,
+# either in same or other clusters
+brokerClientAuthenticationPlugin=
+brokerClientAuthenticationParameters=
+
+### --- BookKeeper Client --- ###
+
+# Authentication plugin to use when connecting to bookies
+bookkeeperClientAuthenticationPlugin=
+
+# BookKeeper auth plugin implementatation specifics parameters name and values
+bookkeeperClientAuthenticationParametersName=
+bookkeeperClientAuthenticationParameters=
+
+# Timeout for BK add / read operations
+bookkeeperClientTimeoutInSeconds=30
+
+# Speculative reads are initiated if a read request doesn't complete within a certain time
+# Using a value of 0, is disabling the speculative reads
+bookkeeperClientSpeculativeReadTimeoutInMillis=0
+
+# Enable bookies health check. Bookies that have more than the configured number of failure within
+# the interval will be quarantined for some time. During this period, new ledgers won't be created
+# on these bookies
+bookkeeperClientHealthCheckEnabled=true
+bookkeeperClientHealthCheckIntervalSeconds=60
+bookkeeperClientHealthCheckErrorThresholdPerInterval=5
+bookkeeperClientHealthCheckQuarantineTimeInSeconds=1800
+
+# Enable rack-aware bookie selection policy. BK will chose bookies from different racks when
+# forming a new bookie ensemble
+bookkeeperClientRackawarePolicyEnabled=true
+
+# Enable region-aware bookie selection policy. BK will chose bookies from
+# different regions and racks when forming a new bookie ensemble
+# If enabled, the value of bookkeeperClientRackawarePolicyEnabled is ignored
+bookkeeperClientRegionawarePolicyEnabled=false
+
+# Enable/disable reordering read sequence on reading entries.
+bookkeeperClientReorderReadSequenceEnabled=false
+
+# Enable bookie isolation by specifying a list of bookie groups to choose from. Any bookie
+# outside the specified groups will not be used by the broker
+bookkeeperClientIsolationGroups=
+
+### --- Managed Ledger --- ###
+
+# Number of bookies to use when creating a ledger
+managedLedgerDefaultEnsembleSize=1
+
+# Number of copies to store for each message
+managedLedgerDefaultWriteQuorum=1
+
+# Number of guaranteed copies (acks to wait before write is complete)
+managedLedgerDefaultAckQuorum=1
+
+# Amount of memory to use for caching data payload in managed ledger. This memory
+# is allocated from JVM direct memory and it's shared across all the topics
+# running  in the same broker
+managedLedgerCacheSizeMB=1024
+
+# Threshold to which bring down the cache level when eviction is triggered
+managedLedgerCacheEvictionWatermark=0.9
+
+# Rate limit the amount of writes generated by consumer acking the messages
+managedLedgerDefaultMarkDeleteRateLimit=0.1
+
+# Max number of entries to append to a ledger before triggering a rollover
+# A ledger rollover is triggered on these conditions
+#  * Either the max rollover time has been reached
+#  * or max entries have been written to the ledged and at least min-time
+#    has passed
+managedLedgerMaxEntriesPerLedger=50000
+
+# Minimum time between ledger rollover for a topic
+managedLedgerMinLedgerRolloverTimeMinutes=10
+
+# Maximum time before forcing a ledger rollover for a topic
+managedLedgerMaxLedgerRolloverTimeMinutes=240
+
+# Max number of entries to append to a cursor ledger
+managedLedgerCursorMaxEntriesPerLedger=50000
+
+# Max time before triggering a rollover on a cursor ledger
+managedLedgerCursorRolloverTimeInSeconds=14400
+
+
+
+### --- Load balancer --- ###
+
+# Enable load balancer
+loadBalancerEnabled=false
+
+# Strategy to assign a new bundle
+loadBalancerPlacementStrategy=weightedRandomSelection
+
+# Percentage of change to trigger load report update
+loadBalancerReportUpdateThresholdPercentage=10
+
+# maximum interval to update load report
+loadBalancerReportUpdateMaxIntervalMinutes=15
+
+# Frequency of report to collect
+loadBalancerHostUsageCheckIntervalMinutes=1
+
+# Load shedding interval. Broker periodically checks whether some traffic should be offload from
+# some over-loaded broker to other under-loaded brokers
+loadBalancerSheddingIntervalMinutes=30
+
+# Prevent the same topics to be shed and moved to other broker more that once within this timeframe
+loadBalancerSheddingGracePeriodMinutes=30
+
+# Usage threshold to determine a broker as under-loaded
+loadBalancerBrokerUnderloadedThresholdPercentage=1
+
+# Usage threshold to determine a broker as over-loaded
+loadBalancerBrokerOverloadedThresholdPercentage=85
+
+# Interval to update namespace bundle resource quotat
+loadBalancerResourceQuotaUpdateIntervalMinutes=15
+
+# Usage threshold to determine a broker is having just right level of load
+loadBalancerBrokerComfortLoadLevelPercentage=65
+
+# enable/disable namespace bundle auto split
+loadBalancerAutoBundleSplitEnabled=false
+
+# interval to detect & split hot namespace bundle
+loadBalancerNamespaceBundleSplitIntervalMinutes=15
+
+# maximum topics in a bundle, otherwise bundle split will be triggered
+loadBalancerNamespaceBundleMaxTopics=1000
+
+# maximum sessions (producers + consumers) in a bundle, otherwise bundle split will be triggered
+loadBalancerNamespaceBundleMaxSessions=1000
+
+# maximum msgRate (in + out) in a bundle, otherwise bundle split will be triggered
+loadBalancerNamespaceBundleMaxMsgRate=1000
+
+# maximum bandwidth (in + out) in a bundle, otherwise bundle split will be triggered
+loadBalancerNamespaceBundleMaxBandwidthMbytes=100
+
+# maximum number of bundles in a namespace
+loadBalancerNamespaceMaximumBundles=128
+
+### --- Replication --- ###
+
+# Enable replication metrics
+replicationMetricsEnabled=true
+
+# Max number of connections to open for each broker in a remote cluster
+# More connections host-to-host lead to better throughput over high-latency
+# links.
+replicationConnectionsPerBroker=16
+
+# Replicator producer queue size
+replicationProducerQueueSize=1000
+
+# Default message retention time
+defaultRetentionTimeInMinutes=0
+
+# Default retention size
+defaultRetentionSizeInMB=0
+
+# How often to check whether the connections are still alive
+keepAliveIntervalSeconds=30
+
+# How often broker checks for inactive topics to be deleted (topics with no subscriptions and no one connected)
+brokerServicePurgeInactiveFrequencyInSeconds=60
diff --git a/pulsar-test-service-start.sh b/pulsar-test-service-start.sh
new file mode 100755
index 0000000..47735ec
--- /dev/null
+++ b/pulsar-test-service-start.sh
@@ -0,0 +1,79 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+set -e
+
+SRC_DIR=$(git rev-parse --show-toplevel)
+cd $SRC_DIR
+
+IMAGE_NAME=pulsar-client-go-test:latest
+
+if [[ -f /.dockerenv ]]; then
+    # When running tests inside docker
+    PULSAR_ADMIN=/pulsar/bin/pulsar-admin
+    /pulsar/bin/pulsar-daemon start standalone --no-functions-worker --no-stream-storage
+else
+    docker build -t ${IMAGE_NAME} .
+
+    docker kill pulsar-client-go-test || true
+    docker run -d --rm --name pulsar-client-go-test \
+                -p 8080:8080 \
+                -p 6650:6650 \
+                -p 8443:8843 \
+                -p 6651:6651 \
+                ${IMAGE_NAME} \
+                /pulsar/bin/pulsar standalone \
+                    --no-functions-worker --no-stream-storage
+
+    PULSAR_ADMIN="docker exec -it pulsar-client-go-test /pulsar/bin/pulsar-admin"
+fi
+
+echo "-- Wait for Pulsar service to be ready"
+until curl http://localhost:8080/metrics > /dev/null 2>&1 ; do sleep 1; done
+
+echo "-- Pulsar service is ready -- Configure permissions"
+
+# Create "standalone" cluster
+$PULSAR_ADMIN clusters create \
+        standalone \
+        --url http://localhost:8080/ \
+        --url-secure https://localhost:8443/ \
+        --broker-url pulsar://localhost:6650/ \
+        --broker-url-secure pulsar+ssl://localhost:6651/
+
+# Create "public" tenant
+$PULSAR_ADMIN tenants create public -r "anonymous" -c "standalone"
+
+# Create "public/default" with no auth required
+$PULSAR_ADMIN namespaces create public/default
+$PULSAR_ADMIN namespaces grant-permission public/default \
+                        --actions produce,consume \
+                        --role "anonymous"
+
+# Create "private" tenant
+$PULSAR_ADMIN tenants create private
+
+# Create "private/auth" with required authentication
+$PULSAR_ADMIN namespaces create private/auth
+$PULSAR_ADMIN namespaces grant-permission private/auth \
+                        --actions produce,consume \
+                        --role "token-principal"
+
+echo "-- Ready to start tests"
diff --git a/pulsar-test-service-stop.sh b/pulsar-test-service-stop.sh
new file mode 100755
index 0000000..eafccd9
--- /dev/null
+++ b/pulsar-test-service-stop.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+set -e
+
+SRC_DIR=$(git rev-parse --show-toplevel)
+cd $SRC_DIR
+
+IMAGE_NAME=pulsar-client-go-test:latest
+
+if [[ -f /.dockerenv ]]; then
+    # When running tests inside docker
+    /pulsar/bin/pulsar-daemon stop standalone
+else
+    docker kill pulsar-client-go-test
+fi
+
+echo "Stopped Test Pulsar Service"


[pulsar-client-go] 26/38: Use logrus in perf producer/consumer

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit e077250d12080dc8688c058df47f4e602b6088a8
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Sat May 4 13:51:38 2019 -0700

    Use logrus in perf producer/consumer
---
 perf/perf-consumer.go |  9 ++++-----
 perf/perf-producer.go | 25 ++++++++++++-------------
 2 files changed, 16 insertions(+), 18 deletions(-)

diff --git a/perf/perf-consumer.go b/perf/perf-consumer.go
index 4ffa36f..e6fdf77 100644
--- a/perf/perf-consumer.go
+++ b/perf/perf-consumer.go
@@ -22,10 +22,9 @@ package main
 import (
 	"context"
 	"encoding/json"
-	"fmt"
 	"pulsar-client-go-native/pulsar"
 	"github.com/spf13/cobra"
-	"log"
+	log "github.com/sirupsen/logrus"
 	"sync/atomic"
 	"time"
 )
@@ -55,9 +54,9 @@ func initConsumer() {
 
 func consume() {
 	b, _ := json.MarshalIndent(clientArgs, "", "  ")
-	fmt.Println("Client config: ", string(b))
+	log.Info("Client config: ", string(b))
 	b, _ = json.MarshalIndent(consumeArgs, "", "  ")
-	fmt.Println("Consumer config: ", string(b))
+	log.Info("Consumer config: ", string(b))
 
 	client, err := pulsar.NewClient(pulsar.ClientOptions{
 		URL:                    clientArgs.ServiceUrl,
@@ -107,7 +106,7 @@ func consume() {
 			msgRate := float64(currentMsgReceived) / float64(10)
 			bytesRate := float64(currentBytesReceived) / float64(10)
 
-			log.Printf(`Stats - Consume rate: %6.1f msg/s - %6.1f Mbps`,
+			log.Infof(`Stats - Consume rate: %6.1f msg/s - %6.1f Mbps`,
 				msgRate, bytesRate*8/1024/1024)
 		}
 	}
diff --git a/perf/perf-producer.go b/perf/perf-producer.go
index 8b5c0fd..c40af30 100644
--- a/perf/perf-producer.go
+++ b/perf/perf-producer.go
@@ -22,21 +22,20 @@ package main
 import (
 	"context"
 	"encoding/json"
-	"fmt"
 	"github.com/beefsack/go-rate"
 	"github.com/bmizerany/perks/quantile"
 	"github.com/spf13/cobra"
-	"log"
+	log "github.com/sirupsen/logrus"
 	"pulsar-client-go-native/pulsar"
 	"time"
 )
 
 type ProduceArgs struct {
-	Topic             string
-	Rate              int
-	Batching          bool
-	MessageSize       int
-	ProducerQueueSize int
+	Topic              string
+	Rate               int
+	BatchingTimeMillis int
+	MessageSize        int
+	ProducerQueueSize  int
 }
 
 var produceArgs ProduceArgs
@@ -53,16 +52,16 @@ var cmdProduce = &cobra.Command{
 
 func initProducer() {
 	cmdProduce.Flags().IntVarP(&produceArgs.Rate, "rate", "r", 100, "Publish rate. Set to 0 to go unthrottled")
-	cmdProduce.Flags().BoolVarP(&produceArgs.Batching, "batching", "b", true, "Enable batching")
+	cmdProduce.Flags().IntVarP(&produceArgs.BatchingTimeMillis, "batching-time", "b", 1, "Batching grouping time in millis")
 	cmdProduce.Flags().IntVarP(&produceArgs.MessageSize, "size", "s", 1024, "Message size")
 	cmdProduce.Flags().IntVarP(&produceArgs.ProducerQueueSize, "queue-size", "q", 1000, "Produce queue size")
 }
 
 func produce() {
 	b, _ := json.MarshalIndent(clientArgs, "", "  ")
-	fmt.Println("Client config: ", string(b))
+	log.Info("Client config: ", string(b))
 	b, _ = json.MarshalIndent(produceArgs, "", "  ")
-	fmt.Println("Producer config: ", string(b))
+	log.Info("Producer config: ", string(b))
 
 	client, err := pulsar.NewClient(pulsar.ClientOptions{
 		URL: clientArgs.ServiceUrl,
@@ -77,7 +76,7 @@ func produce() {
 	producer, err := client.CreateProducer(pulsar.ProducerOptions{
 		Topic:                   produceArgs.Topic,
 		MaxPendingMessages:      produceArgs.ProducerQueueSize,
-		BatchingMaxPublishDelay: 1 * time.Millisecond,
+		BatchingMaxPublishDelay: time.Millisecond * time.Duration(produceArgs.BatchingTimeMillis),
 		SendTimeout:             0,
 		BlockIfQueueFull:        true,
 	})
@@ -110,7 +109,7 @@ func produce() {
 				Payload: payload,
 			}, func(msgID pulsar.MessageID, message *pulsar.ProducerMessage, e error) {
 				if e != nil {
-					log.Fatal("Failed to publish", e)
+					log.WithError(e).Fatal("Failed to publish")
 				}
 
 				latency := time.Since(start).Seconds()
@@ -128,7 +127,7 @@ func produce() {
 		select {
 		case <-tick.C:
 			messageRate := float64(messagesPublished) / float64(10)
-			log.Printf(`Stats - Publish rate: %6.1f msg/s - %6.1f Mbps - Latency ms: 50%% %5.1f - 95%% %5.1f - 99%% %5.1f - 99.9%% %5.1f - max %6.1f`,
+			log.Infof(`Stats - Publish rate: %6.1f msg/s - %6.1f Mbps - Latency ms: 50%% %5.1f - 95%% %5.1f - 99%% %5.1f - 99.9%% %5.1f - max %6.1f`,
 				messageRate,
 				messageRate*float64(produceArgs.MessageSize)/1024/1024*8,
 				q.Query(0.5)*1000,


[pulsar-client-go] 07/38: Removed internalClose method

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit d8a1dccf72b6ebfa47d1f12b129096eb42418d6a
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Sat Mar 30 11:41:13 2019 -0700

    Removed internalClose method
---
 pulsar/impl/connection.go        | 34 ++++++++++++++++------------------
 pulsar/impl/connection_reader.go |  8 ++++----
 2 files changed, 20 insertions(+), 22 deletions(-)

diff --git a/pulsar/impl/connection.go b/pulsar/impl/connection.go
index 2f09690..365f1b3 100644
--- a/pulsar/impl/connection.go
+++ b/pulsar/impl/connection.go
@@ -95,7 +95,7 @@ func (c *connection) connect() (ok bool) {
 	c.cnx, err = net.Dial("tcp", c.physicalAddr)
 	if err != nil {
 		c.log.WithError(err).Warn("Failed to connect to broker.")
-		c.internalClose()
+		c.Close()
 		return false
 	} else {
 		c.log = c.log.WithField("laddr", c.cnx.LocalAddr())
@@ -193,7 +193,7 @@ func (c *connection) writeCommand(cmd proto.Message) {
 
 	if _, err := c.cnx.Write(c.writeBuffer.ReadableSlice()); err != nil {
 		c.log.WithError(err).Warn("Failed to write on connection")
-		c.internalClose()
+		c.Close()
 	}
 }
 
@@ -271,7 +271,7 @@ func (c *connection) sendPing() {
 		// We have not received a response to the previous Ping request, the
 		// connection to broker is stale
 		c.log.Info("Detected stale connection to broker")
-		c.internalClose()
+		c.Close()
 		return
 	}
 
@@ -288,21 +288,6 @@ func (c *connection) handlePing() {
 }
 
 func (c *connection) Close() {
-	// TODO
-}
-
-func (c *connection) changeState(state connectionState) {
-	c.Lock()
-	c.state = state
-	c.cond.Broadcast()
-	c.Unlock()
-}
-
-func (c *connection) newRequestId() uint64 {
-	return atomic.AddUint64(&c.requestIdGenerator, 1)
-}
-
-func (c *connection) internalClose() {
 	c.Lock()
 	defer c.Unlock()
 
@@ -313,6 +298,19 @@ func (c *connection) internalClose() {
 		c.log.Info("Connection closed")
 		c.cnx.Close()
 		c.pingTicker.Stop()
+		close(c.incomingRequests)
 		c.cnx = nil
 	}
 }
+
+func (c *connection) changeState(state connectionState) {
+	c.Lock()
+	c.state = state
+	c.cond.Broadcast()
+	c.Unlock()
+}
+
+func (c *connection) newRequestId() uint64 {
+	return atomic.AddUint64(&c.requestIdGenerator, 1)
+}
+
diff --git a/pulsar/impl/connection_reader.go b/pulsar/impl/connection_reader.go
index 5bb87c6..3b45dc4 100644
--- a/pulsar/impl/connection_reader.go
+++ b/pulsar/impl/connection_reader.go
@@ -27,7 +27,7 @@ func (r *connectionReader) readFromConnection() {
 		cmd, headersAndPayload, err := r.readSingleCommand()
 		if err != nil {
 			r.cnx.log.WithError(err).Info("Error reading from connection")
-			r.cnx.internalClose()
+			r.cnx.Close()
 			break
 		}
 
@@ -53,7 +53,7 @@ func (r *connectionReader) readSingleCommand() (cmd *pb.BaseCommand, headersAndP
 	frameSize := r.buffer.ReadUint32()
 	if frameSize > MaxFrameSize {
 		r.cnx.log.Warnf("Received too big frame size. size=%d", frameSize)
-		r.cnx.internalClose()
+		r.cnx.Close()
 		return nil, nil, errors.New("Frame size too big")
 	}
 
@@ -96,7 +96,7 @@ func (r *connectionReader) readAtLeast(size uint32) (ok bool) {
 
 	n, err := io.ReadAtLeast(r.cnx.cnx, r.buffer.WritableSlice(), int(size))
 	if err != nil {
-		r.cnx.internalClose()
+		r.cnx.Close()
 		return false
 	}
 
@@ -109,7 +109,7 @@ func (r *connectionReader) deserializeCmd(data []byte) (*pb.BaseCommand, error)
 	err := proto.Unmarshal(data, cmd)
 	if err != nil {
 		r.cnx.log.WithError(err).Warn("Failed to parse protobuf command")
-		r.cnx.internalClose()
+		r.cnx.Close()
 		return nil, err
 	} else {
 		return cmd, nil


[pulsar-client-go] 27/38: Fixed releasing of semaphore for each send request

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit b329bbff4d8a86c12c3b673d8ee7c612439ec6ae
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Sat May 4 15:45:02 2019 -0700

    Fixed releasing of semaphore for each send request
---
 pulsar/impl_partition_producer.go |  2 +-
 pulsar/producer_test.go           | 53 ++++++++++++++++++++++++++++++++++++++-
 2 files changed, 53 insertions(+), 2 deletions(-)

diff --git a/pulsar/impl_partition_producer.go b/pulsar/impl_partition_producer.go
index 7b32fdc..5f3f671 100644
--- a/pulsar/impl_partition_producer.go
+++ b/pulsar/impl_partition_producer.go
@@ -313,11 +313,11 @@ func (p *partitionProducer) ReceivedSendReceipt(response *pb.CommandSendReceipt)
 
 	// The ack was indeed for the expected item in the queue, we can remove it and trigger the callback
 	p.pendingQueue.Poll()
-	p.publishSemaphore.Release()
 	for _, i := range pi.sendRequests {
 		sr := i.(*sendRequest)
 		atomic.StoreInt64(&p.lastSequenceID, int64(pi.sequenceId))
 		if sr.callback != nil {
+			p.publishSemaphore.Release()
 			sr.callback(nil, sr.msg, nil)
 		}
 	}
diff --git a/pulsar/producer_test.go b/pulsar/producer_test.go
index fa197b4..f7455ce 100644
--- a/pulsar/producer_test.go
+++ b/pulsar/producer_test.go
@@ -2,8 +2,12 @@ package pulsar
 
 import (
 	"context"
+	log "github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/assert"
+	"pulsar-client-go-native/pulsar/impl/util"
+	"sync"
 	"testing"
+	"time"
 )
 
 func TestSimpleProducer(t *testing.T) {
@@ -34,6 +38,53 @@ func TestSimpleProducer(t *testing.T) {
 	assert.NoError(t, err)
 }
 
+func TestProducerAsyncSend(t *testing.T) {
+	client, err := NewClient(ClientOptions{
+		URL: serviceUrl,
+	})
+	assert.NoError(t, err)
+
+	producer, err := client.CreateProducer(ProducerOptions{
+		Topic:                   newTopicName(),
+		BatchingMaxPublishDelay: 1 * time.Second,
+	})
+
+	assert.NoError(t, err)
+	assert.NotNil(t, producer)
+
+	wg := sync.WaitGroup{}
+	wg.Add(10)
+	errors := util.NewBlockingQueue(10)
+
+	for i := 0; i < 10; i++ {
+		producer.SendAsync(context.Background(), &ProducerMessage{
+			Payload: []byte("hello"),
+		}, func(id MessageID, message *ProducerMessage, e error) {
+			if e != nil {
+				log.WithError(e).Error("Failed to publish")
+				errors.Put(e)
+			} else {
+				log.Info("Published message ", id)
+			}
+			wg.Done()
+		})
+
+		assert.NoError(t, err)
+	}
+
+	producer.Flush()
+
+	wg.Wait()
+
+	assert.Equal(t, 0, errors.Size())
+
+	err = producer.Close()
+	assert.NoError(t, err)
+
+	err = client.Close()
+	assert.NoError(t, err)
+}
+
 func TestProducerCompression(t *testing.T) {
 
 	type testProvider struct {
@@ -108,4 +159,4 @@ func TestProducerLastSequenceID(t *testing.T) {
 
 	err = client.Close()
 	assert.NoError(t, err)
-}
\ No newline at end of file
+}


[pulsar-client-go] 01/38: Initial commit

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit d74beea1bccf552ef81854b5ab559a64019d7ba8
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Mon May 6 15:13:04 2019 -0700

    Initial commit
---
 README.md | 0
 1 file changed, 0 insertions(+), 0 deletions(-)

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e69de29


[pulsar-client-go] 15/38: Handle cases with no-batching

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit 9522dd53c29f25b2f5b86c4ec3fecc8c4955133e
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Wed Apr 10 16:32:23 2019 -0700

    Handle cases with no-batching
---
 pulsar/impl/batch_builder.go      | 16 ++++++++--
 pulsar/impl/commands.go           |  2 +-
 pulsar/impl_partition_producer.go | 66 +++++++++++++++++++++++++--------------
 pulsar/producer.go                |  6 ++--
 4 files changed, 61 insertions(+), 29 deletions(-)

diff --git a/pulsar/impl/batch_builder.go b/pulsar/impl/batch_builder.go
index 7a16155..e8c0af8 100644
--- a/pulsar/impl/batch_builder.go
+++ b/pulsar/impl/batch_builder.go
@@ -61,8 +61,16 @@ func (bb *BatchBuilder) hasSpace(payload []byte) bool {
 }
 
 func (bb *BatchBuilder) Add(metadata *pb.SingleMessageMetadata, sequenceId uint64, payload []byte,
-	callback interface{}) bool {
-	if bb.hasSpace(payload) {
+	callback interface{}, replicateTo []string, ) bool {
+	if replicateTo != nil && bb.numMessages != 0 {
+		// If the current batch is not empty and we're trying to set the replication clusters,
+		// then we need to force the current batch to flush and send the message individually
+		return false
+	} else if bb.msgMetadata.ReplicateTo != nil {
+		// There's already a message with cluster replication list. need to flush before next
+		// message can be sent
+		return false
+	} else if bb.hasSpace(payload) {
 		// The current batch is full. Producer has to call Flush() to
 		return false
 	}
@@ -72,10 +80,11 @@ func (bb *BatchBuilder) Add(metadata *pb.SingleMessageMetadata, sequenceId uint6
 		bb.msgMetadata.PublishTime = proto.Uint64(TimestampMillis(time.Now()))
 		bb.msgMetadata.SequenceId = proto.Uint64(sequenceId)
 		bb.msgMetadata.ProducerName = &bb.producerName
+		bb.msgMetadata.ReplicateTo = replicateTo
 
 		bb.cmdSend.Send.SequenceId = proto.Uint64(sequenceId)
 	}
-	serializeSingleMessage(bb.buffer, metadata, payload)
+	addSingleMessageToBatch(bb.buffer, metadata, payload)
 
 	bb.numMessages += 1
 	bb.callbacks = append(bb.callbacks, callback)
@@ -86,6 +95,7 @@ func (bb *BatchBuilder) reset() {
 	bb.numMessages = 0
 	bb.buffer.Clear()
 	bb.callbacks = []interface{}{}
+	bb.msgMetadata.ReplicateTo = nil
 }
 
 func (bb *BatchBuilder) Flush() (batchData []byte, sequenceId uint64, callbacks []interface{}) {
diff --git a/pulsar/impl/commands.go b/pulsar/impl/commands.go
index 6b1c840..08f8124 100644
--- a/pulsar/impl/commands.go
+++ b/pulsar/impl/commands.go
@@ -36,7 +36,7 @@ func baseCommand(cmdType pb.BaseCommand_Type, msg proto.Message) *pb.BaseCommand
 	return cmd
 }
 
-func serializeSingleMessage(wb Buffer, smm *pb.SingleMessageMetadata, payload []byte) {
+func addSingleMessageToBatch(wb Buffer, smm *pb.SingleMessageMetadata, payload []byte) {
 	serialized, err := proto.Marshal(smm)
 	if err != nil {
 		log.WithError(err).Fatal("Protobuf serialization error")
diff --git a/pulsar/impl_partition_producer.go b/pulsar/impl_partition_producer.go
index 5f50296..d0cd8cf 100644
--- a/pulsar/impl_partition_producer.go
+++ b/pulsar/impl_partition_producer.go
@@ -158,6 +158,8 @@ func (p *partitionProducer) runEventsLoop() {
 				p.internalSend(v)
 			case *connectionClosed:
 				p.reconnectToBroker()
+			case *closeProducer:
+				p.internalClose(v)
 			}
 
 		case _ = <-p.batchFlushTicker.C:
@@ -179,30 +181,38 @@ func (p *partitionProducer) internalSend(request *sendRequest) {
 
 	msg := request.msg
 
-	if msg.ReplicationClusters == nil {
-		smm := &pb.SingleMessageMetadata{
-			PayloadSize: proto.Int(len(msg.Payload)),
-		}
+	sendAsBatch := !p.options.DisableBatching && request.msg.ReplicationClusters == nil
+	smm := &pb.SingleMessageMetadata{
+		PayloadSize: proto.Int(len(msg.Payload)),
+	}
 
-		if msg.EventTime != nil {
-			smm.EventTime = proto.Uint64(impl.TimestampMillis(*msg.EventTime))
-		}
+	if msg.EventTime != nil {
+		smm.EventTime = proto.Uint64(impl.TimestampMillis(*msg.EventTime))
+	}
 
-		if msg.Key != "" {
-			smm.PartitionKey = &msg.Key
-		}
+	if msg.Key != "" {
+		smm.PartitionKey = &msg.Key
+	}
 
-		if msg.Properties != nil {
-			smm.Properties = impl.ConvertFromStringMap(msg.Properties)
-		}
+	if msg.Properties != nil {
+		smm.Properties = impl.ConvertFromStringMap(msg.Properties)
+	}
 
-		sequenceId := impl.GetAndAdd(p.sequenceIdGenerator, 1)
-		for ; p.batchBuilder.Add(smm, sequenceId, msg.Payload, request) == false; {
+	sequenceId := impl.GetAndAdd(p.sequenceIdGenerator, 1)
+
+	if sendAsBatch {
+		for ; p.batchBuilder.Add(smm, sequenceId, msg.Payload, request, msg.ReplicationClusters) == false; {
 			// The current batch is full.. flush it and retry
 			p.internalFlush()
 		}
 	} else {
-		p.log.Panic("TODO: serialize into single message")
+		// Send individually
+		p.batchBuilder.Add(smm, sequenceId, msg.Payload, request, msg.ReplicationClusters)
+		p.internalFlush()
+	}
+
+	if request.flushImmediately {
+		p.internalFlush()
 	}
 }
 
@@ -228,10 +238,10 @@ func (p *partitionProducer) Send(ctx context.Context, msg *ProducerMessage) erro
 
 	var err error
 
-	p.SendAsync(ctx, msg, func(ID MessageID, message *ProducerMessage, e error) {
+	p.internalSendAsync(ctx, msg, func(ID MessageID, message *ProducerMessage, e error) {
 		err = e
 		wg.Done()
-	})
+	}, true)
 
 	// When sending synchronously we flush immediately to avoid
 	// the increased latency and reduced throughput of batching
@@ -246,7 +256,13 @@ func (p *partitionProducer) Send(ctx context.Context, msg *ProducerMessage) erro
 func (p *partitionProducer) SendAsync(ctx context.Context, msg *ProducerMessage,
 	callback func(MessageID, *ProducerMessage, error)) {
 	p.publishSemaphore.Acquire()
-	p.eventsChan <- &sendRequest{ctx, msg, callback}
+	p.eventsChan <- &sendRequest{ctx, msg, callback, false}
+}
+
+func (p *partitionProducer) internalSendAsync(ctx context.Context, msg *ProducerMessage,
+	callback func(MessageID, *ProducerMessage, error), flushImmediately bool) {
+	p.publishSemaphore.Acquire()
+	p.eventsChan <- &sendRequest{ctx, msg, callback, flushImmediately}
 }
 
 func (p *partitionProducer) ReceivedSendReceipt(response *pb.CommandSendReceipt) {
@@ -264,7 +280,7 @@ func (p *partitionProducer) ReceivedSendReceipt(response *pb.CommandSendReceipt)
 	// The ack was indeed for the expected item in the queue, we can remove it and trigger the callback
 	p.pendingQueue.Poll()
 	p.publishSemaphore.Release()
-	for _ ,i := range pi.sendRequest {
+	for _, i := range pi.sendRequest {
 		sr := i.(*sendRequest)
 		sr.callback(nil, sr.msg, nil)
 	}
@@ -285,7 +301,11 @@ func (p *partitionProducer) Close() error {
 }
 
 type sendRequest struct {
-	ctx      context.Context
-	msg      *ProducerMessage
-	callback func(MessageID, *ProducerMessage, error)
+	ctx              context.Context
+	msg              *ProducerMessage
+	callback         func(MessageID, *ProducerMessage, error)
+	flushImmediately bool
 }
+
+type closeProducer struct {
+}
\ No newline at end of file
diff --git a/pulsar/producer.go b/pulsar/producer.go
index 0e59e66..8eaf340 100644
--- a/pulsar/producer.go
+++ b/pulsar/producer.go
@@ -130,7 +130,8 @@ type ProducerOptions struct {
 	// partition index where the message should be routed to
 	MessageRouter func(Message, TopicMetadata) int
 
-	// Control whether automatic batching of messages is enabled for the producer. Default: false [No batching]
+	// Control whether automatic batching of messages is enabled for the producer. By default batching
+	// is enabled.
 	//
 	// When batching is enabled, multiple calls to Producer.sendAsync can result in a single batch to be sent to the
 	// broker, leading to better throughput, especially when publishing small messages. If compression is enabled,
@@ -138,7 +139,8 @@ type ProducerOptions struct {
 	// contents.
 	//
 	// When enabled default batch delay is set to 1 ms and default batch size is 1000 messages
-	Batching bool
+	// Setting `DisableBatching: true` will make the producer to send messages individually
+	DisableBatching bool
 
 	// Set the time period within which the messages sent will be batched (default: 10ms) if batch messages are
 	// enabled. If set to a non zero value, messages will be queued until this time interval or until


[pulsar-client-go] 12/38: Queue sending requests and trigger callbacks

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit 7851714fb75ab9e62f50f064f9db948d6956f890
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Wed Apr 10 14:26:11 2019 -0700

    Queue sending requests and trigger callbacks
---
 pulsar/impl/batch_builder.go      | 15 ++++++---
 pulsar/impl/connection.go         | 25 +++++++++++----
 pulsar/impl/util/semaphore.go     | 11 +++++++
 pulsar/impl_partition_producer.go | 65 +++++++++++++++++++++++++++++++--------
 pulsar/message.go                 |  2 +-
 5 files changed, 95 insertions(+), 23 deletions(-)

diff --git a/pulsar/impl/batch_builder.go b/pulsar/impl/batch_builder.go
index 53bf081..7a16155 100644
--- a/pulsar/impl/batch_builder.go
+++ b/pulsar/impl/batch_builder.go
@@ -27,6 +27,7 @@ type BatchBuilder struct {
 
 	cmdSend     *pb.BaseCommand
 	msgMetadata *pb.MessageMetadata
+	callbacks   []interface{}
 }
 
 func NewBatchBuilder(maxMessages uint, producerName string, producerId uint64) *BatchBuilder {
@@ -46,6 +47,7 @@ func NewBatchBuilder(maxMessages uint, producerName string, producerId uint64) *
 		msgMetadata: &pb.MessageMetadata{
 			ProducerName: &producerName,
 		},
+		callbacks: []interface{}{},
 	}
 }
 
@@ -58,7 +60,8 @@ func (bb *BatchBuilder) hasSpace(payload []byte) bool {
 	return bb.numMessages > 0 && (bb.buffer.ReadableBytes()+msgSize) > MaxBatchSize
 }
 
-func (bb *BatchBuilder) Add(metadata *pb.SingleMessageMetadata, sequenceId uint64, payload []byte) bool {
+func (bb *BatchBuilder) Add(metadata *pb.SingleMessageMetadata, sequenceId uint64, payload []byte,
+	callback interface{}) bool {
 	if bb.hasSpace(payload) {
 		// The current batch is full. Producer has to call Flush() to
 		return false
@@ -75,19 +78,21 @@ func (bb *BatchBuilder) Add(metadata *pb.SingleMessageMetadata, sequenceId uint6
 	serializeSingleMessage(bb.buffer, metadata, payload)
 
 	bb.numMessages += 1
+	bb.callbacks = append(bb.callbacks, callback)
 	return true
 }
 
 func (bb *BatchBuilder) reset() {
 	bb.numMessages = 0
 	bb.buffer.Clear()
+	bb.callbacks = []interface{}{}
 }
 
-func (bb *BatchBuilder) Flush() []byte {
+func (bb *BatchBuilder) Flush() (batchData []byte, sequenceId uint64, callbacks []interface{}) {
 	log.Debug("BatchBuilder flush: messages: ", bb.numMessages)
 	if bb.numMessages == 0 {
 		// No-Op for empty batch
-		return nil
+		return nil, 0, nil
 	}
 
 	bb.msgMetadata.NumMessagesInBatch = proto.Int32(int32(bb.numMessages))
@@ -96,6 +101,8 @@ func (bb *BatchBuilder) Flush() []byte {
 	buffer := NewBuffer(4096)
 	serializeBatch(buffer, bb.cmdSend, bb.msgMetadata, bb.buffer.ReadableSlice())
 
+	callbacks = bb.callbacks
+	sequenceId = bb.cmdSend.Send.GetSequenceId()
 	bb.reset()
-	return buffer.ReadableSlice()
+	return buffer.ReadableSlice(), sequenceId, callbacks
 }
diff --git a/pulsar/impl/connection.go b/pulsar/impl/connection.go
index 18a3b63..fa32889 100644
--- a/pulsar/impl/connection.go
+++ b/pulsar/impl/connection.go
@@ -16,13 +16,15 @@ import (
 // a consumer) that can register itself to get notified
 // when the connection is closed.
 type ConnectionListener interface {
+	ReceivedSendReceipt(response *pb.CommandSendReceipt)
+
 	ConnectionClosed()
 }
 
 type Connection interface {
 	SendRequest(requestId uint64, req *pb.BaseCommand, callback func(command *pb.BaseCommand))
 	WriteData(data []byte)
-	RegisterListener(listener ConnectionListener)
+	RegisterListener(id uint64, listener ConnectionListener)
 	Close()
 }
 
@@ -65,7 +67,7 @@ type connection struct {
 	incomingRequests chan *request
 	writeRequests    chan []byte
 	pendingReqs      map[uint64]*request
-	listeners        []ConnectionListener
+	listeners        map[uint64]ConnectionListener
 }
 
 func newConnection(logicalAddr *url.URL, physicalAddr *url.URL) *connection {
@@ -81,7 +83,7 @@ func newConnection(logicalAddr *url.URL, physicalAddr *url.URL) *connection {
 
 		incomingRequests: make(chan *request),
 		writeRequests:    make(chan []byte),
-		listeners:        make([]ConnectionListener, 0),
+		listeners:        make(map[uint64]ConnectionListener),
 	}
 	cnx.reader = newConnectionReader(cnx)
 	cnx.cond = sync.NewCond(cnx)
@@ -263,8 +265,9 @@ func (c *connection) receivedCommand(cmd *pb.BaseCommand, headersAndPayload []by
 	case pb.BaseCommand_ERROR:
 	case pb.BaseCommand_CLOSE_PRODUCER:
 	case pb.BaseCommand_CLOSE_CONSUMER:
+
 	case pb.BaseCommand_SEND_RECEIPT:
-		c.log.Info("Got SEND_RECEIPT: ", cmd.GetSendReceipt())
+		c.handleSendReceipt(cmd.GetSendReceipt())
 
 	case pb.BaseCommand_SEND_ERROR:
 
@@ -310,6 +313,16 @@ func (c *connection) handleResponse(requestId uint64, response *pb.BaseCommand)
 	request.callback(response)
 }
 
+func (c *connection) handleSendReceipt(response *pb.CommandSendReceipt) {
+	c.log.Debug("Got SEND_RECEIPT: ", response)
+	producerId := response.GetProducerId()
+	if producer, ok := c.listeners[producerId]; ok {
+		producer.ReceivedSendReceipt(response)
+	} else {
+		c.log.WithField("producerId", producerId).Warn("Got unexpected send receipt for message: ", response.MessageId)
+	}
+}
+
 func (c *connection) sendPing() {
 	if c.lastDataReceivedTime.Add(2 * keepAliveInterval).Before(time.Now()) {
 		// We have not received a response to the previous Ping request, the
@@ -331,11 +344,11 @@ func (c *connection) handlePing() {
 	c.writeCommand(baseCommand(pb.BaseCommand_PONG, &pb.CommandPong{}))
 }
 
-func (c *connection) RegisterListener(listener ConnectionListener) {
+func (c *connection) RegisterListener(id uint64, listener ConnectionListener) {
 	c.Lock()
 	defer c.Unlock()
 
-	c.listeners = append(c.listeners, listener)
+	c.listeners[id] = listener
 }
 
 func (c *connection) Close() {
diff --git a/pulsar/impl/util/semaphore.go b/pulsar/impl/util/semaphore.go
new file mode 100644
index 0000000..9df8cd1
--- /dev/null
+++ b/pulsar/impl/util/semaphore.go
@@ -0,0 +1,11 @@
+package util
+
+type Semaphore chan bool
+
+func (s Semaphore) Acquire() {
+	s <- true
+}
+
+func (s Semaphore) Release() {
+	<-s
+}
diff --git a/pulsar/impl_partition_producer.go b/pulsar/impl_partition_producer.go
index 60ae76c..cdd34d5 100644
--- a/pulsar/impl_partition_producer.go
+++ b/pulsar/impl_partition_producer.go
@@ -5,6 +5,7 @@ import (
 	"github.com/golang/protobuf/proto"
 	log "github.com/sirupsen/logrus"
 	"pulsar-client-go-native/pulsar/impl"
+	"pulsar-client-go-native/pulsar/impl/util"
 	pb "pulsar-client-go-native/pulsar/pulsar_proto"
 	"sync"
 	"time"
@@ -27,6 +28,9 @@ type partitionProducer struct {
 
 	// Channel where app is posting messages to be published
 	eventsChan chan interface{}
+
+	publishSemaphore util.Semaphore
+	pendingQueue     util.BlockingQueue
 }
 
 const defaultBatchingMaxPublishDelay = 10 * time.Millisecond
@@ -40,6 +44,13 @@ func newPartitionProducer(client *client, topic string, options *ProducerOptions
 		batchingMaxPublishDelay = defaultBatchingMaxPublishDelay
 	}
 
+	var maxPendingMessages int
+	if options.MaxPendingMessages == 0 {
+		maxPendingMessages = 1000
+	} else {
+		maxPendingMessages = options.MaxPendingMessages
+	}
+
 	p := &partitionProducer{
 		log:              log.WithField("topic", topic),
 		client:           client,
@@ -48,6 +59,8 @@ func newPartitionProducer(client *client, topic string, options *ProducerOptions
 		producerId:       client.rpcClient.NewProducerId(),
 		eventsChan:       make(chan interface{}),
 		batchFlushTicker: time.NewTicker(batchingMaxPublishDelay),
+		publishSemaphore: make(util.Semaphore, maxPendingMessages),
+		pendingQueue:     util.NewBlockingQueue(maxPendingMessages),
 	}
 
 	if options.Name != "" {
@@ -61,7 +74,7 @@ func newPartitionProducer(client *client, topic string, options *ProducerOptions
 	} else {
 		p.log = p.log.WithField("name", *p.producerName)
 		p.log.Info("Created producer")
-		go p.run()
+		go p.runEventsLoop()
 		return p, nil
 	}
 }
@@ -99,7 +112,7 @@ func (p *partitionProducer) grabCnx() error {
 		p.sequenceIdGenerator = &nextSequenceId
 	}
 	p.cnx = res.Cnx
-	p.cnx.RegisterListener(p)
+	p.cnx.RegisterListener(p.producerId, p)
 	p.log.WithField("cnx", res.Cnx).Debug("Connected producer")
 	return nil
 }
@@ -128,8 +141,7 @@ func (p *partitionProducer) reconnectToBroker() {
 	}
 }
 
-
-func (p *partitionProducer) run() {
+func (p *partitionProducer) runEventsLoop() {
 	for {
 		select {
 		case i := <-p.eventsChan:
@@ -177,7 +189,7 @@ func (p *partitionProducer) internalSend(request *sendRequest) {
 		}
 
 		sequenceId := impl.GetAndAdd(p.sequenceIdGenerator, 1)
-		for ; p.batchBuilder.Add(smm, sequenceId, msg.Payload) == false; {
+		for ; p.batchBuilder.Add(smm, sequenceId, msg.Payload, request) == false; {
 			// The current batch is full.. flush it and retry
 			p.internalFlush()
 		}
@@ -186,12 +198,19 @@ func (p *partitionProducer) internalSend(request *sendRequest) {
 	}
 }
 
+type pendingItem struct {
+	batchData   []byte
+	sequenceId  uint64
+	sendRequest []interface{}
+}
+
 func (p *partitionProducer) internalFlush() {
-	batchData := p.batchBuilder.Flush()
+	batchData, sequenceId, callbacks := p.batchBuilder.Flush()
 	if batchData == nil {
 		return
 	}
 
+	p.pendingQueue.Put(&pendingItem{batchData, sequenceId, callbacks})
 	p.cnx.WriteData(batchData)
 }
 
@@ -216,14 +235,30 @@ func (p *partitionProducer) Send(ctx context.Context, msg *ProducerMessage) erro
 	return err
 }
 
-type sendRequest struct {
-	ctx      context.Context
-	msg      *ProducerMessage
-	callback func(MessageID, *ProducerMessage, error)
+func (p *partitionProducer) SendAsync(ctx context.Context, msg *ProducerMessage,
+	callback func(MessageID, *ProducerMessage, error)) {
+	p.publishSemaphore.Acquire()
+	p.eventsChan <- &sendRequest{ctx, msg, callback}
 }
 
-func (p *partitionProducer) SendAsync(ctx context.Context, msg *ProducerMessage, callback func(MessageID, *ProducerMessage, error)) {
-	p.eventsChan <- &sendRequest{ctx, msg, callback}
+func (p *partitionProducer) ReceivedSendReceipt(response *pb.CommandSendReceipt) {
+	pi := p.pendingQueue.Peek().(*pendingItem)
+
+	if pi == nil {
+		p.log.Warnf("Received ack for %v although the pending queue is empty", response.GetMessageId())
+		return
+	} else if pi.sequenceId != response.GetSequenceId() {
+		p.log.Warnf("Received ack for %v on sequenceId %v - expected: %v", response.GetMessageId(),
+			response.GetSequenceId(), pi.sequenceId)
+		return
+	}
+
+	// The ack was indeed for the expected item in the queue, we can remove it and trigger the callback
+	p.pendingQueue.Poll()
+	for _ ,i := range pi.sendRequest {
+		sr := i.(*sendRequest)
+		sr.callback(nil, sr.msg, nil)
+	}
 }
 
 func (p *partitionProducer) LastSequenceID() int64 {
@@ -239,3 +274,9 @@ func (p *partitionProducer) Close() error {
 	p.log.Info("Closing producer")
 	return nil
 }
+
+type sendRequest struct {
+	ctx      context.Context
+	msg      *ProducerMessage
+	callback func(MessageID, *ProducerMessage, error)
+}
diff --git a/pulsar/message.go b/pulsar/message.go
index 12bc269..e987ad1 100644
--- a/pulsar/message.go
+++ b/pulsar/message.go
@@ -82,7 +82,7 @@ func DeserializeMessageID(data []byte) MessageID {
 }
 
 var (
-// MessageID that points to the earliest message avaialable in a topic
+// MessageID that points to the earliest message available in a topic
 // TODO: EarliestMessage MessageID = earliestMessageID()
 
 // MessageID that points to the latest message


[pulsar-client-go] 02/38: Initial import

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit 84cb32ea75fbc322d107995620681e0d6feff202
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Wed Mar 27 09:34:18 2019 -0700

    Initial import
---
 .gitignore                          |    1 +
 pulsar/client.go                    |  111 +
 pulsar/consumer.go                  |  179 ++
 pulsar/error.go                     |   80 +
 pulsar/impl/batch_builder.go        |   23 +
 pulsar/impl/buffer.go               |  119 ++
 pulsar/impl/buffer_test.go          |   19 +
 pulsar/impl/closable.go             |    5 +
 pulsar/impl/commands.go             |   33 +
 pulsar/impl/connection.go           |  293 +++
 pulsar/impl/connection_pool.go      |   54 +
 pulsar/impl/connection_reader.go    |  117 +
 pulsar/impl/default_router.go       |   26 +
 pulsar/impl/lookup_service.go       |   62 +
 pulsar/impl/rpc_client.go           |   71 +
 pulsar/impl/topic_name.go           |   88 +
 pulsar/impl/topic_name_test.go      |   68 +
 pulsar/impl_client.go               |   98 +
 pulsar/impl_partition_producer.go   |   88 +
 pulsar/impl_producer.go             |  123 ++
 pulsar/message.go                   |   90 +
 pulsar/producer.go                  |  187 ++
 pulsar/pulsar_proto/PulsarApi.pb.go | 4043 +++++++++++++++++++++++++++++++++++
 pulsar/reader.go                    |   84 +
 24 files changed, 6062 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..723ef36
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.idea
\ No newline at end of file
diff --git a/pulsar/client.go b/pulsar/client.go
new file mode 100644
index 0000000..21db637
--- /dev/null
+++ b/pulsar/client.go
@@ -0,0 +1,111 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package pulsar
+
+import (
+	"time"
+)
+
+func NewClient(options ClientOptions) (Client, error) {
+	return newClient(options)
+}
+
+// Opaque interface that represents the authentication credentials
+type Authentication interface {}
+
+// Create new Authentication provider with specified auth token
+func NewAuthenticationToken(token string) Authentication {
+	// TODO: return newAuthenticationToken(token)
+	return nil
+}
+
+// Create new Authentication provider with specified auth token supplier
+func NewAuthenticationTokenSupplier(tokenSupplier func() string) Authentication {
+	// TODO:  return newAuthenticationTokenSupplier(tokenSupplier)
+	return nil
+}
+
+// Create new Authentication provider with specified TLS certificate and private key
+func NewAuthenticationTLS(certificatePath string, privateKeyPath string) Authentication {
+	// TODO: return newAuthenticationTLS(certificatePath, privateKeyPath)
+	return nil
+}
+
+// Create new Athenz Authentication provider with configuration in JSON form
+func NewAuthenticationAthenz(authParams string) Authentication {
+	// TODO: return newAuthenticationAthenz(authParams)
+	return nil
+}
+
+// Builder interface that is used to construct a Pulsar Client instance.
+type ClientOptions struct {
+	// Configure the service URL for the Pulsar service.
+	// This parameter is required
+	URL string
+
+	ConnectionTimeout time.Duration
+
+	// Set the operation timeout (default: 30 seconds)
+	// Producer-create, subscribe and unsubscribe operations will be retried until this interval, after which the
+	// operation will be marked as failed
+	OperationTimeout time.Duration
+
+	// Configure the authentication provider. (default: no authentication)
+	// Example: `Authentication: NewAuthenticationTLS("my-cert.pem", "my-key.pem")`
+	Authentication
+
+	// Set the path to the trusted TLS certificate file
+	TLSTrustCertsFilePath string
+
+	// Configure whether the Pulsar client accept untrusted TLS certificate from broker (default: false)
+	TLSAllowInsecureConnection bool
+
+	// Configure whether the Pulsar client verify the validity of the host name from broker (default: false)
+	TLSValidateHostname bool
+}
+
+type Client interface {
+	// Create the producer instance
+	// This method will block until the producer is created successfully
+	CreateProducer(ProducerOptions) (Producer, error)
+
+	// Create a `Consumer` by subscribing to a topic.
+	//
+	// If the subscription does not exist, a new subscription will be created and all messages published after the
+	// creation will be retained until acknowledged, even if the consumer is not connected
+	Subscribe(ConsumerOptions) (Consumer, error)
+
+	// Create a Reader instance.
+	// This method will block until the reader is created successfully.
+	CreateReader(ReaderOptions) (Reader, error)
+
+	// Fetch the list of partitions for a given topic
+	//
+	// If the topic is partitioned, this will return a list of partition names.
+	// If the topic is not partitioned, the returned list will contain the topic
+	// name itself.
+	//
+	// This can be used to discover the partitions and create {@link Reader},
+	// {@link Consumer} or {@link Producer} instances directly on a particular partition.
+	TopicPartitions(topic string) ([]string, error)
+
+	// Close the Client and free associated resources
+	Close() error
+}
diff --git a/pulsar/consumer.go b/pulsar/consumer.go
new file mode 100644
index 0000000..b32e5fc
--- /dev/null
+++ b/pulsar/consumer.go
@@ -0,0 +1,179 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package pulsar
+
+import (
+	"context"
+	"time"
+)
+
+// Pair of a Consumer and Message
+type ConsumerMessage struct {
+	Consumer
+	Message
+}
+
+// Types of subscription supported by Pulsar
+type SubscriptionType int
+
+const (
+	// There can be only 1 consumer on the same topic with the same subscription name
+	Exclusive SubscriptionType = iota
+
+	// Multiple consumer will be able to use the same subscription name and the messages will be dispatched according to
+	// a round-robin rotation between the connected consumers
+	Shared
+
+	// Multiple consumer will be able to use the same subscription name but only 1 consumer will receive the messages.
+	// If that consumer disconnects, one of the other connected consumers will start receiving messages.
+	Failover
+)
+
+type InitialPosition int
+
+const (
+	// Latest position which means the start consuming position will be the last message
+	Latest InitialPosition = iota
+
+	// Earliest position which means the start consuming position will be the first message
+	Earliest
+)
+
+// ConsumerBuilder is used to configure and create instances of Consumer
+type ConsumerOptions struct {
+	// Specify the topic this consumer will subscribe on.
+	// Either a topic, a list of topics or a topics pattern are required when subscribing
+	Topic string
+
+	// Specify a list of topics this consumer will subscribe on.
+	// Either a topic, a list of topics or a topics pattern are required when subscribing
+	Topics []string
+
+	// Specify a regular expression to subscribe to multiple topics under the same namespace.
+	// Either a topic, a list of topics or a topics pattern are required when subscribing
+	TopicsPattern string
+
+	// Specify the subscription name for this consumer
+	// This argument is required when subscribing
+	SubscriptionName string
+
+	// Attach a set of application defined properties to the consumer
+	// This properties will be visible in the topic stats
+	Properties map[string]string
+
+	// Set the timeout for unacked messages
+	// Message not acknowledged within the give time, will be replayed by the broker to the same or a different consumer
+	// Default is 0, which means message are not being replayed based on ack time
+	AckTimeout time.Duration
+
+	// Select the subscription type to be used when subscribing to the topic.
+	// Default is `Exclusive`
+	Type SubscriptionType
+
+	// InitialPosition at which the cursor will be set when subscribe
+	// Default is `Latest`
+	SubscriptionInitPos InitialPosition
+
+	// Sets a `MessageChannel` for the consumer
+	// When a message is received, it will be pushed to the channel for consumption
+	MessageChannel chan ConsumerMessage
+
+	// Sets the size of the consumer receive queue.
+	// The consumer receive queue controls how many messages can be accumulated by the `Consumer` before the
+	// application calls `Consumer.receive()`. Using a higher value could potentially increase the consumer
+	// throughput at the expense of bigger memory utilization.
+	// Default value is `1000` messages and should be good for most use cases.
+	// Set to -1 to disable prefetching in consumer
+	ReceiverQueueSize int
+
+	// Set the max total receiver queue size across partitions.
+	// This setting will be used to reduce the receiver queue size for individual partitions
+	// ReceiverQueueSize(int) if the total exceeds this value (default: 50000).
+	MaxTotalReceiverQueueSizeAcrossPartitions int
+
+	// Set the consumer name.
+	Name string
+
+	// If enabled, the consumer will read messages from the compacted topic rather than reading the full message backlog
+	// of the topic. This means that, if the topic has been compacted, the consumer will only see the latest value for
+	// each key in the topic, up until the point in the topic message backlog that has been compacted. Beyond that
+	// point, the messages will be sent as normal.
+	//
+	// ReadCompacted can only be enabled subscriptions to persistent topics, which have a single active consumer (i.e.
+	//  failure or exclusive subscriptions). Attempting to enable it on subscriptions to a non-persistent topics or on a
+	//  shared subscription, will lead to the subscription call throwing a PulsarClientException.
+	ReadCompacted bool
+}
+
+// An interface that abstracts behavior of Pulsar's consumer
+type Consumer interface {
+	// Get the topic for the consumer
+	Topic() string
+
+	// Get a subscription for the consumer
+	Subscription() string
+
+	// Unsubscribe the consumer
+	Unsubscribe() error
+
+	// Receives a single message.
+	// This calls blocks until a message is available.
+	Receive(context.Context) (Message, error)
+
+	// Ack the consumption of a single message
+	Ack(Message) error
+
+	// Ack the consumption of a single message, identified by its MessageID
+	AckID(MessageID) error
+
+	// Ack the reception of all the messages in the stream up to (and including) the provided message.
+	// This method will block until the acknowledge has been sent to the broker. After that, the messages will not be
+	// re-delivered to this consumer.
+	//
+	// Cumulative acknowledge cannot be used when the consumer type is set to ConsumerShared.
+	//
+	// It's equivalent to calling asyncAcknowledgeCumulative(Message) and waiting for the callback to be triggered.
+	AckCumulative(Message) error
+
+	// Ack the reception of all the messages in the stream up to (and including) the provided message.
+	// This method will block until the acknowledge has been sent to the broker. After that, the messages will not be
+	// re-delivered to this consumer.
+	//
+	// Cumulative acknowledge cannot be used when the consumer type is set to ConsumerShared.
+	//
+	// It's equivalent to calling asyncAcknowledgeCumulative(MessageID) and waiting for the callback to be triggered.
+	AckCumulativeID(MessageID) error
+
+	// Close the consumer and stop the broker to push more messages
+	Close() error
+
+	// Reset the subscription associated with this consumer to a specific message id.
+	// The message id can either be a specific message or represent the first or last messages in the topic.
+	//
+	// Note: this operation can only be done on non-partitioned topics. For these, one can rather perform the
+	//       seek() on the individual partitions.
+	Seek(msgID MessageID) error
+
+	// Redelivers all the unacknowledged messages. In Failover mode, the request is ignored if the consumer is not
+	// active for the given topic. In Shared mode, the consumers messages to be redelivered are distributed across all
+	// the connected consumers. This is a non blocking call and doesn't throw an exception. In case the connection
+	// breaks, the messages are redelivered after reconnect.
+	RedeliverUnackedMessages()
+}
diff --git a/pulsar/error.go b/pulsar/error.go
new file mode 100644
index 0000000..929b250
--- /dev/null
+++ b/pulsar/error.go
@@ -0,0 +1,80 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package pulsar
+import "C"
+import "fmt"
+
+type Result int
+
+const (
+	UnknownError                          Result = 1  // Unknown error happened on broker
+	InvalidConfiguration                  Result = 2  // Invalid configuration
+	TimeoutError                          Result = 3  // Operation timed out
+	LookupError                           Result = 4  // Broker lookup failed
+	ConnectError                          Result = 5  // Failed to connect to broker
+	ReadError                             Result = 6  // Failed to read from socket
+	AuthenticationError                   Result = 7  // Authentication failed on broker
+	AuthorizationError                    Result = 8  // Client is not authorized to create producer/consumer
+	ErrorGettingAuthenticationData        Result = 9  // Client cannot find authorization data
+	BrokerMetadataError                   Result = 10 // Broker failed in updating metadata
+	BrokerPersistenceError                Result = 11 // Broker failed to persist entry
+	ChecksumError                         Result = 12 // Corrupt message checksum failure
+	ConsumerBusy                          Result = 13 // Exclusive consumer is already connected
+	NotConnectedError                     Result = 14 // Producer/Consumer is not currently connected to broker
+	AlreadyClosedError                    Result = 15 // Producer/Consumer is already closed and not accepting any operation
+	InvalidMessage                        Result = 16 // Error in publishing an already used message
+	ConsumerNotInitialized                Result = 17 // Consumer is not initialized
+	ProducerNotInitialized                Result = 18 // Producer is not initialized
+	TooManyLookupRequestException         Result = 19 // Too Many concurrent LookupRequest
+	InvalidTopicName                      Result = 20 // Invalid topic name
+	InvalidUrl                            Result = 21 // Client Initialized with Invalid Broker Url (VIP Url passed to Client Constructor)
+	ServiceUnitNotReady                   Result = 22 // Service Unit unloaded between client did lookup and producer/consumer got created
+	OperationNotSupported                 Result = 23
+	ProducerBlockedQuotaExceededError     Result = 24 // Producer is blocked
+	ProducerBlockedQuotaExceededException Result = 25 // Producer is getting exception
+	ProducerQueueIsFull                   Result = 26 // Producer queue is full
+	MessageTooBig                         Result = 27 // Trying to send a messages exceeding the max size
+	TopicNotFound                         Result = 28 // Topic not found
+	SubscriptionNotFound                  Result = 29 // Subscription not found
+	ConsumerNotFound                      Result = 30 // Consumer not found
+	UnsupportedVersionError               Result = 31 // Error when an older client/version doesn't support a required feature
+	TopicTerminated                       Result = 32 // Topic was already terminated
+	CryptoError                           Result = 33 // Error when crypto operation fails
+)
+
+type Error struct {
+	msg    string
+	result Result
+}
+
+func (e *Error) Result() Result {
+	return e.result
+}
+
+func (e *Error) Error() string {
+	return e.msg
+}
+
+func newError(result Result, msg string) error {
+	return &Error{
+		msg:    fmt.Sprintf("%s: %d", msg, result),
+		result: result,
+	}
+}
\ No newline at end of file
diff --git a/pulsar/impl/batch_builder.go b/pulsar/impl/batch_builder.go
new file mode 100644
index 0000000..7eb2030
--- /dev/null
+++ b/pulsar/impl/batch_builder.go
@@ -0,0 +1,23 @@
+package impl
+
+type BatchBuilder struct {
+	buffer Buffer
+}
+
+func NewBatchBuilder() *BatchBuilder {
+	return &BatchBuilder{
+		buffer: NewBuffer(4096),
+	}
+}
+
+func (bb *BatchBuilder) isFull() bool {
+	return false
+}
+
+func (bb *BatchBuilder) hasSpace(size int) bool {
+	return false
+}
+
+func (bb *BatchBuilder) Flush() []byte {
+	return nil
+}
diff --git a/pulsar/impl/buffer.go b/pulsar/impl/buffer.go
new file mode 100644
index 0000000..8e81f0c
--- /dev/null
+++ b/pulsar/impl/buffer.go
@@ -0,0 +1,119 @@
+package impl
+
+import "encoding/binary"
+
+type Buffer interface {
+	ReadableBytes() uint32
+
+	WritableBytes() uint32
+
+	Capacity() uint32
+
+	IsWritable() bool
+
+	Read(size uint32) []byte
+
+	ReadableSlice() []byte
+
+	WritableSlice() []byte
+
+	// Advance the writer index when data was written in a slice
+	WrittenBytes(size uint32)
+
+	// Copy the available portion of data at the beginning of the buffer
+	MoveToFront()
+
+	ReadUint32() uint32
+
+	WriteUint32(n uint32)
+
+	Write(s []byte)
+
+	Resize(newSize uint32)
+
+	Clear()
+}
+
+type buffer struct {
+	data []byte
+
+	readerIdx uint32
+	writerIdx uint32
+}
+
+func NewBuffer(size int) Buffer {
+	return &buffer{
+		data:      make([]byte, size),
+		readerIdx: 0,
+		writerIdx: 0,
+	}
+}
+
+func (b *buffer) ReadableBytes() uint32 {
+	return b.writerIdx - b.readerIdx
+}
+
+func (b *buffer) WritableBytes() uint32 {
+	return uint32(cap(b.data)) - b.writerIdx
+}
+
+func (b *buffer) Capacity() uint32 {
+	return uint32(cap(b.data))
+}
+
+func (b *buffer) IsWritable() bool {
+	return b.WritableBytes() > 0
+}
+
+func (b *buffer) Read(size uint32) []byte {
+	res := b.data[b.readerIdx : b.readerIdx+size]
+	b.readerIdx += size
+	return res
+}
+
+func (b *buffer) ReadableSlice() []byte {
+	return b.data[b.readerIdx:b.writerIdx]
+}
+
+func (b *buffer) WritableSlice() []byte {
+	return b.data[b.writerIdx:]
+}
+
+func (b *buffer) WrittenBytes(size uint32) {
+	b.writerIdx += size
+}
+
+func (b *buffer) MoveToFront() {
+	size := b.ReadableBytes()
+	copy(b.data, b.Read(size))
+	b.readerIdx = 0
+	b.writerIdx = size
+}
+
+func (b *buffer) Resize(newSize uint32) {
+	newData := make([]byte, newSize)
+	size := b.ReadableBytes()
+	copy(newData, b.Read(size))
+	b.data = newData
+	b.readerIdx = 0
+	b.writerIdx = size
+}
+
+func (b *buffer) ReadUint32() uint32 {
+	return binary.BigEndian.Uint32(b.Read(4))
+}
+
+func (b *buffer) WriteUint32(n uint32) {
+	binary.BigEndian.PutUint32(b.WritableSlice(), n)
+	b.writerIdx += 4
+}
+
+func (b *buffer) Write(s []byte) {
+	copy(b.WritableSlice(), s)
+	b.writerIdx += uint32(len(s))
+}
+
+func (b *buffer) Clear() {
+	b.readerIdx = 0
+	b.writerIdx = 0
+}
diff --git a/pulsar/impl/buffer_test.go b/pulsar/impl/buffer_test.go
new file mode 100644
index 0000000..1d72196
--- /dev/null
+++ b/pulsar/impl/buffer_test.go
@@ -0,0 +1,19 @@
+package impl
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestBuffer(t *testing.T) {
+	b := NewBuffer(1024)
+	assert.Equal(t, uint32(0), b.ReadableBytes())
+	assert.Equal(t, uint32(1024), b.WritableBytes())
+	assert.Equal(t, uint32(1024), b.Capacity())
+
+	b.Write([]byte("hello"))
+	assert.Equal(t, uint32(5), b.ReadableBytes())
+	assert.Equal(t, uint32(1019), b.WritableBytes())
+	assert.Equal(t, uint32(1024), b.Capacity())
+}
diff --git a/pulsar/impl/closable.go b/pulsar/impl/closable.go
new file mode 100644
index 0000000..c483901
--- /dev/null
+++ b/pulsar/impl/closable.go
@@ -0,0 +1,5 @@
+package impl
+
+type Closable interface {
+	Close() error
+}
diff --git a/pulsar/impl/commands.go b/pulsar/impl/commands.go
new file mode 100644
index 0000000..90760e1
--- /dev/null
+++ b/pulsar/impl/commands.go
@@ -0,0 +1,33 @@
+package impl
+
+import (
+	"github.com/golang/protobuf/proto"
+	log "github.com/sirupsen/logrus"
+	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+)
+
+const MaxFrameSize = 5 * 1024 * 1024
+
+func baseCommand(cmdType pb.BaseCommand_Type, msg proto.Message) *pb.BaseCommand {
+	cmd := &pb.BaseCommand{
+		Type: &cmdType,
+	}
+	switch cmdType {
+	case pb.BaseCommand_CONNECT:
+		cmd.Connect = msg.(*pb.CommandConnect)
+		break
+	case pb.BaseCommand_LOOKUP:
+		cmd.LookupTopic = msg.(*pb.CommandLookupTopic)
+		break
+
+	case pb.BaseCommand_PARTITIONED_METADATA:
+		cmd.PartitionMetadata = msg.(*pb.CommandPartitionedTopicMetadata)
+		break
+
+	default:
+		log.Panic("Missing command type: ", cmdType)
+	}
+
+	return cmd
+}
+
diff --git a/pulsar/impl/connection.go b/pulsar/impl/connection.go
new file mode 100644
index 0000000..6379f6a
--- /dev/null
+++ b/pulsar/impl/connection.go
@@ -0,0 +1,293 @@
+package impl
+
+import (
+	"errors"
+	"github.com/golang/protobuf/proto"
+	log "github.com/sirupsen/logrus"
+	"net"
+	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+	"sync"
+	"sync/atomic"
+)
+
+type Connection interface {
+	SendRequest(requestId uint64, req *pb.BaseCommand, callback func(command *pb.BaseCommand))
+	Close()
+}
+
+type connectionState int
+
+const (
+	connectionInit connectionState = iota
+	connectionConnecting
+	connectionTcpConnected
+	connectionReady
+	connectionClosed
+)
+
+type request struct {
+	id       uint64
+	cmd      *pb.BaseCommand
+	callback func(command *pb.BaseCommand)
+}
+
+type connection struct {
+	sync.Mutex
+	cond  *sync.Cond
+	state connectionState
+
+	logicalAddr  string
+	physicalAddr string
+	cnx          net.Conn
+
+	writeBuffer Buffer
+	reader      *connectionReader
+
+	log *log.Entry
+
+	requestIdGenerator uint64
+
+	incomingRequests chan *request
+	pendingReqs      map[uint64]*request
+}
+
+func newConnection(logicalAddr string, physicalAddr string) *connection {
+	cnx := &connection{
+		state:        connectionInit,
+		logicalAddr:  logicalAddr,
+		physicalAddr: physicalAddr,
+		writeBuffer:  NewBuffer(4096),
+		log:          log.WithField("raddr", physicalAddr),
+		pendingReqs:  make(map[uint64]*request),
+	}
+	cnx.reader = newConnectionReader(cnx)
+	cnx.cond = sync.NewCond(cnx)
+	return cnx
+}
+
+func (c *connection) start() {
+	// Each connection gets its own goroutine that will
+	go func() {
+		if c.connect() {
+			if c.doHandshake() {
+				c.run()
+			} else {
+				c.changeState(connectionClosed)
+			}
+		} else {
+			c.changeState(connectionClosed)
+		}
+	}()
+}
+
+func (c *connection) connect() (ok bool) {
+	c.log.Info("Connecting to broker")
+
+	var err error
+	c.cnx, err = net.Dial("tcp", c.physicalAddr)
+	if err != nil {
+		c.log.WithError(err).Warn("Failed to connect to broker.")
+		c.internalClose()
+		return false
+	} else {
+		c.log = c.log.WithField("laddr", c.cnx.LocalAddr())
+		c.log.Info("TCP connection established")
+		c.state = connectionTcpConnected
+		return true
+	}
+}
+
+func (c *connection) doHandshake() (ok bool) {
+	// Send 'Connect' command to initiate handshake
+	version := int32(pb.ProtocolVersion_v13)
+	c.writeCommand(baseCommand(pb.BaseCommand_CONNECT, &pb.CommandConnect{
+		ProtocolVersion: &version,
+		ClientVersion:   proto.String("Pulsar Go 0.1"),
+		// AuthMethodName: "token",
+		// AuthData: authData,
+	}))
+
+	cmd, _, err := c.reader.readSingleCommand()
+	if err != nil {
+		c.log.WithError(err).Warn("Failed to perform initial handshake")
+		return false
+	}
+
+	if cmd.Connected == nil {
+		c.log.Warnf("Failed to perform initial handshake - Expecting 'Connected' cmd, got '%s'",
+			cmd.Type)
+		return false
+	}
+
+	c.log.Info("Connection is ready")
+	c.changeState(connectionReady)
+	return true
+}
+
+func (c *connection) waitUntilReady() error {
+	c.Lock()
+	defer c.Unlock()
+
+	for {
+		switch c.state {
+		case connectionInit:
+		case connectionConnecting:
+		case connectionTcpConnected:
+			// Wait for the state to change
+			c.cond.Wait()
+			break
+
+		case connectionReady:
+			return nil
+
+		case connectionClosed:
+			return errors.New("connection error")
+		}
+	}
+}
+
+func (c *connection) run() {
+	// All reads come from the reader goroutine
+	go c.reader.readFromConnection()
+
+	for {
+		select {
+		case req := <-c.incomingRequests:
+			c.pendingReqs[req.id] = req
+			c.writeCommand(req.cmd)
+		}
+	}
+}
+
+func (c *connection) writeCommand(cmd proto.Message) {
+	// Wire format
+	// [FRAME_SIZE] [CMD_SIZE][CMD]
+	cmdSize := uint32(proto.Size(cmd))
+	frameSize := cmdSize + 4
+	bufferSize := frameSize + 4
+
+	c.writeBuffer.Clear()
+	if c.writeBuffer.WritableBytes() < bufferSize {
+		c.writeBuffer.Resize(c.writeBuffer.Capacity() * 2)
+	}
+
+	c.writeBuffer.WriteUint32(frameSize)
+	c.writeBuffer.WriteUint32(cmdSize)
+	serialized, err := proto.Marshal(cmd)
+	if err != nil {
+		c.log.WithError(err).Fatal("Protobuf serialization error")
+	}
+
+	c.writeBuffer.Write(serialized)
+
+	if _, err := c.cnx.Write(c.writeBuffer.ReadableSlice()); err != nil {
+		c.log.WithError(err).Warn("Failed to write on connection")
+		c.internalClose()
+	}
+}
+
+func (c *connection) receivedCommand(cmd *pb.BaseCommand, headersAndPayload []byte) {
+	c.log.Infof("Received command: %s -- payload: %v", cmd, headersAndPayload)
+
+	switch *cmd.Type {
+	case pb.BaseCommand_SUCCESS:
+		c.handleResponse(*cmd.Success.RequestId, cmd)
+		break
+
+	case pb.BaseCommand_PRODUCER_SUCCESS:
+		c.handleResponse(*cmd.ProducerSuccess.RequestId, cmd)
+		break
+
+	case pb.BaseCommand_PARTITIONED_METADATA_RESPONSE:
+		c.handleResponse(*cmd.PartitionMetadataResponse.RequestId, cmd)
+		break
+
+	case pb.BaseCommand_LOOKUP_RESPONSE:
+		c.handleResponse(*cmd.LookupTopicResponse.RequestId, cmd)
+		break
+
+	case pb.BaseCommand_CONSUMER_STATS_RESPONSE:
+		c.handleResponse(*cmd.ConsumerStatsResponse.RequestId, cmd)
+		break
+
+	case pb.BaseCommand_GET_LAST_MESSAGE_ID_RESPONSE:
+		c.handleResponse(*cmd.GetLastMessageIdResponse.RequestId, cmd)
+		break
+
+	case pb.BaseCommand_GET_TOPICS_OF_NAMESPACE_RESPONSE:
+		c.handleResponse(*cmd.GetTopicsOfNamespaceResponse.RequestId, cmd)
+		break
+
+	case pb.BaseCommand_GET_SCHEMA_RESPONSE:
+		c.handleResponse(*cmd.GetSchemaResponse.RequestId, cmd)
+		break
+
+	case pb.BaseCommand_ERROR:
+		break
+
+	case pb.BaseCommand_CLOSE_PRODUCER:
+	case pb.BaseCommand_CLOSE_CONSUMER:
+
+	case pb.BaseCommand_SEND_RECEIPT:
+		break
+	case pb.BaseCommand_SEND_ERROR:
+		break
+
+	case pb.BaseCommand_MESSAGE:
+		break
+
+	case pb.BaseCommand_PONG:
+
+	case pb.BaseCommand_ACTIVE_CONSUMER_CHANGE:
+		// TODO
+		break
+
+	default:
+		c.log.Errorf("Received invalid command type: %s", cmd.Type)
+		c.Close()
+	}
+}
+
+func (c *connection) SendRequest(requestId uint64, req *pb.BaseCommand, callback func(command *pb.BaseCommand)) {
+	c.pendingReqs[requestId] = &request{
+		id:       requestId,
+		cmd:      req,
+		callback: callback,
+	}
+	c.writeCommand(req)
+}
+
+func (c *connection) handleResponse(requestId uint64, response *pb.BaseCommand) {
+	request, ok := c.pendingReqs[requestId]
+	if !ok {
+		c.log.Warnf("Received unexpected response for request %d of type %s", requestId, response.Type)
+		return
+	}
+
+	delete(c.pendingReqs, requestId)
+	request.callback(response)
+}
+
+func (c *connection) Close() {
+	// TODO
+}
+
+func (c *connection) changeState(state connectionState) {
+	c.Lock()
+	c.state = state
+	c.cond.Broadcast()
+	c.Unlock()
+}
+
+func (c *connection) newRequestId() uint64 {
+	return atomic.AddUint64(&c.requestIdGenerator, 1)
+}
+
+func (c *connection) internalClose() {
+	c.state = connectionClosed
+	c.cond.Broadcast()
+
+	if c.cnx != nil {
+		c.cnx.Close()
+	}
+}
diff --git a/pulsar/impl/connection_pool.go b/pulsar/impl/connection_pool.go
new file mode 100644
index 0000000..676f205
--- /dev/null
+++ b/pulsar/impl/connection_pool.go
@@ -0,0 +1,54 @@
+package impl
+
+import (
+	"sync"
+)
+
+type ConnectionPool interface {
+	GetConnection(logicalAddr string, physicalAddr string) (Connection, error)
+
+	// Close all the connections in the pool
+	Close()
+}
+
+type connectionPool struct {
+	pool sync.Map
+}
+
+func NewConnectionPool() ConnectionPool {
+	return &connectionPool{}
+}
+
+func (p *connectionPool) GetConnection(logicalAddr string, physicalAddr string) (Connection, error) {
+	cachedCnx, found := p.pool.Load(logicalAddr)
+	if found {
+		cnx := cachedCnx.(*connection)
+		if err := cnx.waitUntilReady(); err != nil {
+			// Connection is ready to be used
+			return cnx, nil
+		} else {
+			// The cached connection is failed
+			p.pool.Delete(logicalAddr)
+		}
+	}
+
+	// Try to create a new connection
+	newCnx, wasCached := p.pool.LoadOrStore(logicalAddr, newConnection(logicalAddr, physicalAddr))
+	cnx := newCnx.(*connection)
+	if !wasCached {
+		cnx.start()
+	}
+
+	if err := cnx.waitUntilReady(); err != nil {
+		return nil, err
+	} else {
+		return cnx, nil
+	}
+}
+
+func (p *connectionPool) Close() {
+	p.pool.Range(func(key, value interface{}) bool {
+		value.(Connection).Close()
+		return true
+	})
+}
diff --git a/pulsar/impl/connection_reader.go b/pulsar/impl/connection_reader.go
new file mode 100644
index 0000000..5bb87c6
--- /dev/null
+++ b/pulsar/impl/connection_reader.go
@@ -0,0 +1,117 @@
+package impl
+
+import (
+	"bufio"
+	"github.com/golang/protobuf/proto"
+	"github.com/pkg/errors"
+	"io"
+	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+)
+
+type connectionReader struct {
+	cnx    *connection
+	buffer Buffer
+	reader *bufio.Reader
+}
+
+func newConnectionReader(cnx *connection) *connectionReader {
+	return &connectionReader{
+		cnx:    cnx,
+		reader: bufio.NewReader(cnx.cnx),
+		buffer: NewBuffer(4096),
+	}
+}
+
+func (r *connectionReader) readFromConnection() {
+	for {
+		cmd, headersAndPayload, err := r.readSingleCommand()
+		if err != nil {
+			r.cnx.log.WithError(err).Info("Error reading from connection")
+			r.cnx.internalClose()
+			break
+		}
+
+		// Process
+		r.cnx.log.Debug("Got command! ", cmd, " with payload ", headersAndPayload)
+		r.cnx.receivedCommand(cmd, headersAndPayload)
+	}
+}
+
+func (r *connectionReader) readSingleCommand() (cmd *pb.BaseCommand, headersAndPayload []byte, err error) {
+	// First, we need to read the frame size
+	if r.buffer.ReadableBytes() < 4 {
+		if r.buffer.ReadableBytes() == 0 {
+			// If the buffer is empty, just go back to write at the beginning
+			r.buffer.Clear()
+		}
+		if !r.readAtLeast(4) {
+			return nil, nil, errors.New("Short read when reading frame size")
+		}
+	}
+
+	// We have enough to read frame size
+	frameSize := r.buffer.ReadUint32()
+	if frameSize > MaxFrameSize {
+		r.cnx.log.Warnf("Received too big frame size. size=%d", frameSize)
+		r.cnx.internalClose()
+		return nil, nil, errors.New("Frame size too big")
+	}
+
+	// Next, we read the rest of the frame
+	if r.buffer.ReadableBytes() < frameSize {
+		if !r.readAtLeast(frameSize) {
+			return nil, nil, errors.New("Short read when reading frame")
+		}
+	}
+
+	// We have now the complete frame
+	cmdSize := r.buffer.ReadUint32()
+	cmd, err = r.deserializeCmd(r.buffer.Read(cmdSize))
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Also read the eventual payload
+	headersAndPayloadSize := frameSize - (cmdSize + 4)
+	if cmdSize+4 < frameSize {
+		headersAndPayload = make([]byte, headersAndPayloadSize)
+		copy(headersAndPayload, r.buffer.Read(headersAndPayloadSize))
+	}
+	return cmd, headersAndPayload, nil
+}
+
+func (r *connectionReader) readAtLeast(size uint32) (ok bool) {
+	if r.buffer.WritableBytes() < size {
+		// There's not enough room in the current buffer to read the requested amount of data
+		totalFrameSize := r.buffer.ReadableBytes() + size
+		if r.buffer.ReadableBytes()+size > r.buffer.Capacity() {
+			// Resize to a bigger buffer to avoid continuous resizing
+			r.buffer.Resize(totalFrameSize * 2)
+		} else {
+			// Compact the buffer by moving the partial data to the beginning.
+			// This will have enough room for reading the remainder of the data
+			r.buffer.MoveToFront()
+		}
+	}
+
+	n, err := io.ReadAtLeast(r.cnx.cnx, r.buffer.WritableSlice(), int(size))
+	if err != nil {
+		r.cnx.internalClose()
+		return false
+	}
+
+	r.buffer.WrittenBytes(uint32(n))
+	return true
+}
+
+func (r *connectionReader) deserializeCmd(data []byte) (*pb.BaseCommand, error) {
+	cmd := &pb.BaseCommand{}
+	err := proto.Unmarshal(data, cmd)
+	if err != nil {
+		r.cnx.log.WithError(err).Warn("Failed to parse protobuf command")
+		r.cnx.internalClose()
+		return nil, err
+	} else {
+		return cmd, nil
+	}
+}
diff --git a/pulsar/impl/default_router.go b/pulsar/impl/default_router.go
new file mode 100644
index 0000000..bb5ca73
--- /dev/null
+++ b/pulsar/impl/default_router.go
@@ -0,0 +1,26 @@
+package impl
+
+import (
+	"time"
+)
+
+type defaultRouter struct {
+	partitionIdx     uint32
+	maxBatchingDelay time.Duration
+}
+
+func NewDefaultRouter(maxBatchingDelay time.Duration) func(uint32) int {
+	// TODO: XXX
+	//state := &defaultRouter{
+	//	partitionIdx:     rand.Uint32(),
+	//	maxBatchingDelay: maxBatchingDelay,
+	//}
+
+	return func(numPartitions uint32) int {
+		if numPartitions == 1 {
+			return 0
+		}
+
+		return -1
+	}
+}
diff --git a/pulsar/impl/lookup_service.go b/pulsar/impl/lookup_service.go
new file mode 100644
index 0000000..0ebf424
--- /dev/null
+++ b/pulsar/impl/lookup_service.go
@@ -0,0 +1,62 @@
+package impl
+
+import (
+	log "github.com/sirupsen/logrus"
+	"net/url"
+	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+)
+
+type LookupResult struct {
+	LogicalAddr  *url.URL
+	PhysicalAddr *url.URL
+}
+
+type LookupService interface {
+	Lookup(topic string) (*LookupResult, error)
+}
+
+type lookupService struct {
+	rpcClient RpcClient
+}
+
+func NewLookupService(rpcClient RpcClient) LookupService {
+	return &lookupService{
+		rpcClient: rpcClient,
+	}
+}
+
+func (ls *lookupService) Lookup(topic string) (*LookupResult, error) {
+	// Follow brokers redirect up to certain number of times
+	for i := 0; i < 20; i++ {
+		id := ls.rpcClient.NewRequestId()
+		res, err := ls.rpcClient.RequestToAnyBroker(id, pb.BaseCommand_LOOKUP, &pb.CommandLookupTopic{
+			RequestId: &id,
+			Topic:     &topic,
+		})
+
+		if err != nil {
+			return nil, err
+		}
+
+		log.Infof("Got lookup response: %s", res)
+		lr := res.Response.LookupTopicResponse
+		switch *lr.Response {
+		case pb.CommandLookupTopicResponse_Redirect:
+			log.WithField("topic", topic).Infof("Follow redirect to broker. %v / %v - Use proxy: %v",
+				lr.BrokerServiceUrl, lr.BrokerServiceUrlTls, lr.ProxyThroughServiceUrl)
+			break
+
+		case pb.CommandLookupTopicResponse_Connect:
+			log.WithField("topic", topic).Infof("Successfully looked up topic on broker. %s / %s - Use proxy: %t",
+				lr.GetBrokerServiceUrl(), lr.GetBrokerServiceUrlTls(), lr.GetProxyThroughServiceUrl())
+			return nil, nil
+
+		case pb.CommandLookupTopicResponse_Failed:
+			log.WithField("topic", topic).Warn("Failed to lookup topic",
+				lr.Error.String())
+			return nil, nil
+		}
+	}
+
+	return nil, nil
+}
diff --git a/pulsar/impl/rpc_client.go b/pulsar/impl/rpc_client.go
new file mode 100644
index 0000000..fd76569
--- /dev/null
+++ b/pulsar/impl/rpc_client.go
@@ -0,0 +1,71 @@
+package impl
+
+import (
+	"github.com/golang/protobuf/proto"
+	"net/url"
+	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+	"sync"
+	"sync/atomic"
+)
+
+type RpcResult struct {
+	Response *pb.BaseCommand
+	Cnx      Connection
+}
+
+type RpcClient interface {
+	// Create a new unique request id
+	NewRequestId() uint64
+
+	// Send a request and block until the result is available
+	RequestToAnyBroker(requestId uint64, cmdType pb.BaseCommand_Type, message proto.Message) (*RpcResult, error)
+
+	Request(logicalAddr string, physicalAddr string, requestId uint64,
+		cmdType pb.BaseCommand_Type, message proto.Message) (*RpcResult, error)
+}
+
+type rpcClient struct {
+	hostPort           string
+	pool               ConnectionPool
+	requestIdGenerator uint64
+}
+
+func NewRpcClient(serviceUrl *url.URL, pool ConnectionPool) RpcClient {
+	return &rpcClient{
+		hostPort: serviceUrl.Host,
+		pool:     pool,
+	}
+}
+
+func (c *rpcClient) RequestToAnyBroker(requestId uint64, cmdType pb.BaseCommand_Type, message proto.Message) (*RpcResult, error) {
+	return c.Request(c.hostPort, c.hostPort, requestId, cmdType, message)
+}
+
+func (c *rpcClient) Request(logicalAddr string, physicalAddr string, requestId uint64,
+	cmdType pb.BaseCommand_Type, message proto.Message) (*RpcResult, error) {
+	// TODO: Add retry logic in case of connection issues
+	cnx, err := c.pool.GetConnection(logicalAddr, physicalAddr)
+	if err != nil {
+		return nil, err
+	}
+
+	wg := sync.WaitGroup{}
+	wg.Add(1)
+
+	rpcResult := &RpcResult{
+		Cnx: cnx,
+	}
+
+	// TODO: Handle errors with disconnections
+	cnx.SendRequest(requestId, baseCommand(cmdType, message), func(response *pb.BaseCommand) {
+		rpcResult.Response = response
+		wg.Done()
+	})
+
+	wg.Wait()
+	return rpcResult, nil
+}
+
+func (c *rpcClient) NewRequestId() uint64 {
+	return atomic.AddUint64(&c.requestIdGenerator, 1)
+}
diff --git a/pulsar/impl/topic_name.go b/pulsar/impl/topic_name.go
new file mode 100644
index 0000000..2bafeb2
--- /dev/null
+++ b/pulsar/impl/topic_name.go
@@ -0,0 +1,88 @@
+package impl
+
+import (
+	"errors"
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+type TopicName struct {
+	Name      string
+	Namespace string
+	Partition int
+}
+
+const (
+	publicTenant           = "public"
+	defaultNamespace       = "default"
+	partitionedTopicSuffix = "-partition-"
+)
+
+func ParseTopicName(topic string) (*TopicName, error) {
+	// The topic name can be in two different forms, one is fully qualified topic name,
+	// the other one is short topic name
+	if !strings.Contains(topic, "://") {
+		// The short topic name can be:
+		// - <topic>
+		// - <property>/<namespace>/<topic>
+		parts := strings.Split(topic, "/")
+		if len(parts) == 3 {
+			topic = "persistent://" + topic
+		} else if len(parts) == 1 {
+			topic = "persistent://" + publicTenant + "/" + defaultNamespace + "/" + parts[0]
+		} else {
+			return nil, errors.New(
+				"Invalid short topic name '" + topic +
+					"', it should be in the format of <tenant>/<namespace>/<topic> or <topic>")
+		}
+	}
+
+	tn := &TopicName{}
+
+	// The fully qualified topic name can be in two different forms:
+	// new:    persistent://tenant/namespace/topic
+	// legacy: persistent://tenant/cluster/namespace/topic
+	parts := strings.SplitN(topic, "://", 2)
+	domain := parts[0]
+	if domain != "persistent" && domain != "non-persistent" {
+		return nil, errors.New("Invalid topic domain: " + domain)
+	}
+
+	rest := parts[1]
+	var err error
+
+	// The rest of the name can be in different forms:
+	// new:    tenant/namespace/<localName>
+	// legacy: tenant/cluster/namespace/<localName>
+	// Examples of localName:
+	// 1. some/name/xyz//
+	// 2. /xyz-123/feeder-2
+	parts = strings.SplitN(rest, "/", 4)
+	if len(parts) == 3 {
+		// New topic name without cluster name
+		tn.Namespace = parts[0] + "/" + parts[1]
+	} else if len(parts) == 4 {
+		// Legacy topic name that includes cluster name
+		tn.Namespace = fmt.Sprintf("%s/%s/%s", parts[0], parts[1], parts[2])
+	} else {
+		return nil, errors.New("Invalid topic name: " + topic)
+	}
+
+	tn.Name = topic
+	tn.Partition, err = getPartitionIndex(topic)
+	if err != nil {
+		return nil, err
+	}
+
+	return tn, nil
+}
+
+func getPartitionIndex(topic string) (int, error) {
+	if strings.Contains(topic, partitionedTopicSuffix) {
+		idx  := strings.LastIndex(topic, "-") + 1
+		return strconv.Atoi(topic[idx:])
+	} else {
+		return -1, nil
+	}
+}
diff --git a/pulsar/impl/topic_name_test.go b/pulsar/impl/topic_name_test.go
new file mode 100644
index 0000000..b4d6184
--- /dev/null
+++ b/pulsar/impl/topic_name_test.go
@@ -0,0 +1,68 @@
+package impl
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestParseTopicName(t *testing.T) {
+	topic, err := ParseTopicName("persistent://my-tenant/my-ns/my-topic")
+
+	assert.Nil(t, err)
+	assert.Equal(t, "persistent://my-tenant/my-ns/my-topic", topic.Name)
+	assert.Equal(t, "my-tenant/my-ns", topic.Namespace)
+	assert.Equal(t, -1, topic.Partition)
+
+	topic, err = ParseTopicName("my-topic")
+	assert.Nil(t, err)
+	assert.Equal(t, "persistent://public/default/my-topic", topic.Name)
+	assert.Equal(t, "public/default", topic.Namespace)
+	assert.Equal(t, -1, topic.Partition)
+
+	topic, err = ParseTopicName("my-tenant/my-namespace/my-topic")
+	assert.Nil(t, err)
+	assert.Equal(t, "persistent://my-tenant/my-namespace/my-topic", topic.Name)
+	assert.Equal(t, "my-tenant/my-namespace", topic.Namespace)
+	assert.Equal(t, -1, topic.Partition)
+
+	topic, err = ParseTopicName("non-persistent://my-tenant/my-namespace/my-topic")
+	assert.Nil(t, err)
+	assert.Equal(t, "non-persistent://my-tenant/my-namespace/my-topic", topic.Name)
+	assert.Equal(t, "my-tenant/my-namespace", topic.Namespace)
+	assert.Equal(t, -1, topic.Partition)
+
+
+	topic, err = ParseTopicName("my-topic-partition-5")
+	assert.Nil(t, err)
+	assert.Equal(t, "persistent://public/default/my-topic-partition-5", topic.Name)
+	assert.Equal(t, "public/default", topic.Namespace)
+	assert.Equal(t, 5, topic.Partition)
+
+	// V1 topic name
+	topic, err = ParseTopicName("persistent://my-tenant/my-cluster/my-ns/my-topic")
+	assert.Nil(t, err)
+	assert.Equal(t, "persistent://my-tenant/my-cluster/my-ns/my-topic", topic.Name)
+	assert.Equal(t, "my-tenant/my-cluster/my-ns", topic.Namespace)
+	assert.Equal(t, -1, topic.Partition)
+}
+
+func TestParseTopicNameErrors(t *testing.T) {
+	_, err := ParseTopicName("invalid://my-tenant/my-ns/my-topic")
+	assert.NotNil(t, err)
+
+	_, err = ParseTopicName("invalid://my-tenant/my-ns/my-topic-partition-xyz")
+	assert.NotNil(t, err)
+
+	_, err = ParseTopicName("my-tenant/my-ns/my-topic-partition-xyz/invalid")
+	assert.NotNil(t, err)
+
+	_, err = ParseTopicName("persistent://my-tenant")
+	assert.NotNil(t, err)
+
+	_, err = ParseTopicName("persistent://my-tenant/my-namespace")
+	assert.NotNil(t, err)
+
+	_, err = ParseTopicName("persistent://my-tenant/my-cluster/my-ns/my-topic-partition-xyz/invalid")
+	assert.NotNil(t, err)
+}
diff --git a/pulsar/impl_client.go b/pulsar/impl_client.go
new file mode 100644
index 0000000..e143601
--- /dev/null
+++ b/pulsar/impl_client.go
@@ -0,0 +1,98 @@
+package pulsar
+
+import (
+	"fmt"
+	log "github.com/sirupsen/logrus"
+	"net/url"
+	"pulsar-client-go-native/pulsar/impl"
+	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+)
+
+type client struct {
+	options ClientOptions
+
+	cnxPool       impl.ConnectionPool
+	rpcClient     impl.RpcClient
+	lookupService impl.LookupService
+
+	handlers map[impl.Closable]bool
+}
+
+func newClient(options ClientOptions) (Client, error) {
+	if options.URL == "" {
+		return nil, newError(InvalidConfiguration, "URL is required for client")
+	}
+
+	url, err := url.Parse(options.URL)
+	if err != nil {
+		log.WithError(err).Error("Failed to parse service URL")
+		return nil, newError(InvalidConfiguration, "Invalid service URL")
+	}
+
+	if url.Scheme != "pulsar" {
+		return nil, newError(InvalidConfiguration, fmt.Sprintf("Invalid URL scheme '%s'", url.Scheme))
+	}
+
+	c := &client{
+		cnxPool: impl.NewConnectionPool(),
+	}
+	c.rpcClient = impl.NewRpcClient(url, c.cnxPool)
+	c.lookupService = impl.NewLookupService(c.rpcClient)
+	return c, nil
+}
+
+func (client *client) CreateProducer(options ProducerOptions) (Producer, error) {
+	producer, err := newProducer(client, options)
+	if err == nil {
+		client.handlers[producer] = true
+	}
+	return producer, err
+}
+
+func (client *client) Subscribe(options ConsumerOptions) (Consumer, error) {
+	// TODO: Implement consumer
+	return nil, nil
+}
+
+func (client *client) CreateReader(options ReaderOptions) (Reader, error) {
+	// TODO: Implement reader
+	return nil, nil
+}
+
+func (client *client) TopicPartitions(topic string) ([]string, error) {
+	topicName, err := impl.ParseTopicName(topic)
+	if err != nil {
+		return nil, err
+	}
+
+	id := client.rpcClient.NewRequestId()
+	res, err := client.rpcClient.RequestToAnyBroker(id, pb.BaseCommand_PARTITIONED_METADATA,
+		&pb.CommandPartitionedTopicMetadata{
+			RequestId: &id,
+			Topic:     &topicName.Name,
+		})
+	if err != nil {
+		return nil, err
+	}
+
+	r := res.Response.PartitionMetadataResponse
+	if r.Error != nil {
+		return nil, newError(LookupError, r.GetError().String())
+	}
+
+	partitions := make([]string, r.GetPartitions())
+	for i := 0; i < int(r.GetPartitions()); i++ {
+		partitions[i] = fmt.Sprintf("%s-partition-%d", topic, i)
+	}
+	return partitions, nil
+}
+
+func (client *client) Close() error {
+	for handler := range client.handlers {
+		if err := handler.Close(); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/pulsar/impl_partition_producer.go b/pulsar/impl_partition_producer.go
new file mode 100644
index 0000000..bb96a27
--- /dev/null
+++ b/pulsar/impl_partition_producer.go
@@ -0,0 +1,88 @@
+package pulsar
+
+import (
+	"context"
+	log "github.com/sirupsen/logrus"
+	"pulsar-client-go-native/pulsar/impl"
+	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+	"sync"
+)
+
+type partitionProducer struct {
+	client *client
+	topic  string
+	log    *log.Entry
+	mutex  sync.Mutex
+	cond   *sync.Cond
+}
+
+func newPartitionProducer(client *client, topic string, options *ProducerOptions) (*partitionProducer, error) {
+
+	p := &partitionProducer{
+		log: log.WithField("topic", topic),
+	}
+
+	err := p.grabCnx()
+	if err != nil {
+		log.WithError(err).Errorf("Failed to create producer")
+		return nil, err
+	} else {
+		log.Info("Created producer")
+		return p, nil
+	}
+}
+
+func (p *partitionProducer) grabCnx() error {
+	lr, err := p.client.lookupService.Lookup(p.topic)
+	if err != nil {
+		p.log.WithError(err).Warn("Failed to lookup topic")
+		return err
+	}
+
+	id := p.client.rpcClient.NewRequestId()
+	p.client.rpcClient.Request(lr.LogicalAddr.Host, lr.PhysicalAddr.Host, id, pb.BaseCommand_PRODUCER, *pb.CommandProducer{
+
+	})
+
+	var cnx impl.Connection
+	cnx, err = p.client.cnxPool.GetConnection(lr.LogicalAddr.Host, lr.PhysicalAddr.Host)
+	if err != nil {
+		p.log.WithError(err).Warn("Failed to get connection")
+		return err
+	}
+
+	cnx.
+
+	return nil
+}
+
+func (p *partitionProducer) run() {
+
+}
+
+func (p *partitionProducer) Topic() string {
+	return ""
+}
+
+func (p *partitionProducer) Name() string {
+	return ""
+}
+
+func (p *partitionProducer) Send(context.Context, ProducerMessage) error {
+	return nil
+}
+
+func (p *partitionProducer) SendAsync(context.Context, ProducerMessage, func(ProducerMessage, error)) {
+}
+
+func (p *partitionProducer) LastSequenceID() int64 {
+	return -1
+}
+
+func (p *partitionProducer) Flush() error {
+	return nil
+}
+
+func (p *partitionProducer) Close() error {
+	return nil
+}
diff --git a/pulsar/impl_producer.go b/pulsar/impl_producer.go
new file mode 100644
index 0000000..f2332ea
--- /dev/null
+++ b/pulsar/impl_producer.go
@@ -0,0 +1,123 @@
+package pulsar
+
+import (
+	"context"
+	"pulsar-client-go-native/pulsar/impl"
+	"sync"
+)
+
+type producer struct {
+	topic         string
+	producers     []Producer
+	messageRouter func(ProducerMessage, TopicMetadata) int
+}
+
+func newProducer(client *client, options ProducerOptions) (*producer, error) {
+	if options.Topic == "" {
+		return nil, newError(InvalidTopicName, "Topic name is required for producer")
+	}
+
+	p := &producer{
+		topic: options.Topic,
+	}
+
+	if options.MessageRouter == nil {
+		internalRouter := impl.NewDefaultRouter(options.BatchingMaxPublishDelay)
+		p.messageRouter = func(message ProducerMessage, metadata TopicMetadata) int {
+			return internalRouter(metadata.NumPartitions())
+		}
+	}
+
+	partitions, err := client.TopicPartitions(options.Topic)
+	if err != nil {
+		return nil, err
+	}
+
+	numPartitions := len(partitions)
+	p.producers = make([]Producer, numPartitions)
+
+	type ProducerError struct {
+		partition int
+		Producer
+		error
+	}
+
+	c := make(chan ProducerError, numPartitions)
+
+	for i := 0; i < numPartitions; i++ {
+		partition := i
+		go func() {
+			prod, err := newPartitionProducer(client, &options)
+			c <- ProducerError{partition, prod, err}
+		}()
+	}
+
+	for i := 0; i < numPartitions; i++ {
+		pe := <-c
+		err = pe.error
+		p.producers[pe.partition] = pe.Producer
+	}
+
+	if err != nil {
+		// Since there were some failures, cleanup all the partitions that succeeded in creating the producers
+		for _, producer := range p.producers {
+			if producer != nil {
+				_ := producer.Close()
+			}
+		}
+		return nil, err
+	} else {
+		return p, nil
+	}
+}
+
+func (p *producer) Topic() string {
+	return p.topic
+}
+
+func (p *producer) Name() string {
+	return p.producers[0].Name()
+}
+
+func (p *producer) NumPartitions() uint32 {
+	return uint32(len(p.producers))
+}
+
+func (p *producer) Send(ctx context.Context, msg ProducerMessage) error {
+	wg := sync.WaitGroup{}
+	wg.Add(1)
+
+	var err error
+
+	p.SendAsync(ctx, msg, func(message ProducerMessage, e error) {
+		err = e
+		wg.Done()
+	})
+
+	wg.Wait()
+	return err
+}
+
+func (p *producer) SendAsync(ctx context.Context, msg ProducerMessage, callback func(ProducerMessage, error)) {
+	partition := p.messageRouter(msg, p)
+	p.producers[partition].SendAsync(ctx, msg, callback)
+}
+
+func (p *producer) LastSequenceID() int64 {
+	var maxSeq int64 = -1
+	for _, pp := range p.producers {
+		s := pp.LastSequenceID()
+		if s > maxSeq {
+			maxSeq = s
+		}
+	}
+	return maxSeq
+}
+
+func (p *producer) Flush() error {
+	return nil
+}
+
+func (p *producer) Close() error {
+	return nil
+}
diff --git a/pulsar/message.go b/pulsar/message.go
new file mode 100644
index 0000000..b829b96
--- /dev/null
+++ b/pulsar/message.go
@@ -0,0 +1,90 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package pulsar
+
+import "time"
+
+type ProducerMessage struct {
+	// Payload for the message
+	Payload []byte
+
+	// Sets the key of the message for routing policy
+	Key string
+
+	// Attach application defined properties on the message
+	Properties map[string]string
+
+	// Set the event time for a given message
+	EventTime time.Time
+
+	// Override the replication clusters for this message.
+	ReplicationClusters []string
+
+	// Set the sequence id to assign to the current message
+	SequenceID int64
+}
+
+type Message interface {
+	// Get the topic from which this message originated from
+	Topic() string
+
+	// Return the properties attached to the message.
+	// Properties are application defined key/value pairs that will be attached to the message
+	Properties() map[string]string
+
+	// Get the payload of the message
+	Payload() []byte
+
+	// Get the unique message ID associated with this message.
+	// The message id can be used to univocally refer to a message without having the keep the entire payload in memory.
+	ID() MessageID
+
+	// Get the publish time of this message. The publish time is the timestamp that a client publish the message.
+	PublishTime() time.Time
+
+	// Get the event time associated with this message. It is typically set by the applications via
+	// `ProducerMessage.EventTime`.
+	// If there isn't any event time associated with this event, it will be nil.
+	EventTime() *time.Time
+
+	// Get the key of the message, if any
+	Key() string
+}
+
+// Identifier for a particular message
+type MessageID interface {
+	// Serialize the message id into a sequence of bytes that can be stored somewhere else
+	Serialize() []byte
+}
+
+// Reconstruct a MessageID object from its serialized representation
+func DeserializeMessageID(data []byte) MessageID {
+	// TODO
+	//return deserializeMessageId(data)
+	return nil
+}
+
+var (
+	// MessageID that points to the earliest message avaialable in a topic
+	//EarliestMessage MessageID = earliestMessageID()
+
+	// MessageID that points to the latest message
+	//LatestMessage MessageID = latestMessageID()
+)
diff --git a/pulsar/producer.go b/pulsar/producer.go
new file mode 100644
index 0000000..90769cb
--- /dev/null
+++ b/pulsar/producer.go
@@ -0,0 +1,187 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package pulsar
+
+import (
+	"context"
+	"time"
+)
+
+type MessageRoutingMode int
+
+const (
+	// Publish messages across all partitions in round-robin.
+	RoundRobinDistribution MessageRoutingMode = iota
+
+	// The producer will chose one single partition and publish all the messages into that partition
+	UseSinglePartition
+
+	// Use custom message router implementation that will be called to determine the partition for a particular message.
+	CustomPartition
+)
+
+type HashingScheme int
+
+const (
+	JavaStringHash HashingScheme = iota // Java String.hashCode() equivalent
+	Murmur3_32Hash                      // Use Murmur3 hashing function
+	BoostHash                           // C++ based boost::hash
+)
+
+type CompressionType int
+
+const (
+	NoCompression CompressionType = iota
+	LZ4
+	ZLib
+	ZSTD
+)
+
+type TopicMetadata interface {
+	// Get the number of partitions for the specific topic
+	NumPartitions() uint32
+}
+
+type ProducerOptions struct {
+	// Specify the topic this producer will be publishing on.
+	// This argument is required when constructing the producer.
+	Topic string
+
+	// Specify a name for the producer
+	// If not assigned, the system will generate a globally unique name which can be access with
+	// Producer.ProducerName().
+	// When specifying a name, it is up to the user to ensure that, for a given topic, the producer name is unique
+	// across all Pulsar's clusters. Brokers will enforce that only a single producer a given name can be publishing on
+	// a topic.
+	Name string
+
+	// Attach a set of application defined properties to the producer
+	// This properties will be visible in the topic stats
+	Properties map[string]string
+
+	// Set the send timeout (default: 30 seconds)
+	// If a message is not acknowledged by the server before the sendTimeout expires, an error will be reported.
+	// Setting the timeout to -1, will set the timeout to infinity, which can be useful when using Pulsar's message
+	// deduplication feature.
+	SendTimeout time.Duration
+
+	// Set the max size of the queue holding the messages pending to receive an acknowledgment from the broker.
+	// When the queue is full, by default, all calls to Producer.send() and Producer.sendAsync() will fail
+	// unless `BlockIfQueueFull` is set to true. Use BlockIfQueueFull(boolean) to change the blocking behavior.
+	MaxPendingMessages int
+
+	// Set the number of max pending messages across all the partitions
+	// This setting will be used to lower the max pending messages for each partition
+	// `MaxPendingMessages(int)`, if the total exceeds the configured value.
+	MaxPendingMessagesAcrossPartitions int
+
+	// Set whether the `Producer.Send()` and `Producer.sendAsync()` operations should block when the outgoing
+	// message queue is full. Default is `false`. If set to `false`, send operations will immediately fail with
+	// `ProducerQueueIsFullError` when there is no space left in pending queue.
+	BlockIfQueueFull bool
+
+	// Set the message routing mode for the partitioned producer.
+	// Default routing mode is round-robin routing.
+	//
+	// This logic is applied when the application is not setting a key ProducerMessage#setKey(String) on a
+	// particular message.
+	MessageRoutingMode
+
+	// Change the `HashingScheme` used to chose the partition on where to publish a particular message.
+	// Standard hashing functions available are:
+	//
+	//  - `JavaStringHash` : Java String.hashCode() equivalent
+	//  - `Murmur3_32Hash` : Use Murmur3 hashing function.
+	// 		https://en.wikipedia.org/wiki/MurmurHash">https://en.wikipedia.org/wiki/MurmurHash
+	//  - `BoostHash`      : C++ based boost::hash
+	//
+	// Default is `JavaStringHash`.
+	HashingScheme
+
+	// Set the compression type for the producer.
+	// By default, message payloads are not compressed. Supported compression types are:
+	//  - LZ4
+	//  - ZLIB
+	//  - ZSTD
+	//
+	// Note: ZSTD is supported since Pulsar 2.3. Consumers will need to be at least at that
+	// release in order to be able to receive messages compressed with ZSTD.
+	CompressionType
+
+	// Set a custom message routing policy by passing an implementation of MessageRouter
+	// The router is a function that given a particular message and the topic metadata, returns the
+	// partition index where the message should be routed to
+	MessageRouter func(Message, TopicMetadata) int
+
+	// Control whether automatic batching of messages is enabled for the producer. Default: false [No batching]
+	//
+	// When batching is enabled, multiple calls to Producer.sendAsync can result in a single batch to be sent to the
+	// broker, leading to better throughput, especially when publishing small messages. If compression is enabled,
+	// messages will be compressed at the batch level, leading to a much better compression ratio for similar headers or
+	// contents.
+	//
+	// When enabled default batch delay is set to 1 ms and default batch size is 1000 messages
+	Batching bool
+
+	// Set the time period within which the messages sent will be batched (default: 10ms) if batch messages are
+	// enabled. If set to a non zero value, messages will be queued until this time interval or until
+	BatchingMaxPublishDelay time.Duration
+
+	// Set the maximum number of messages permitted in a batch. (default: 1000) If set to a value greater than 1,
+	// messages will be queued until this threshold is reached or batch interval has elapsed
+	BatchingMaxMessages uint
+}
+
+// The producer is used to publish messages on a topic
+type Producer interface {
+	// return the topic to which producer is publishing to
+	Topic() string
+
+	// return the producer name which could have been assigned by the system or specified by the client
+	Name() string
+
+	// Send a message
+	// This call will be blocking until is successfully acknowledged by the Pulsar broker.
+	// Example:
+	// producer.Send(ctx, pulsar.ProducerMessage{ Payload: myPayload })
+	Send(context.Context, ProducerMessage) error
+
+	// Send a message in asynchronous mode
+	// The callback will report back the message being published and
+	// the eventual error in publishing
+	SendAsync(context.Context, ProducerMessage, func(ProducerMessage, error))
+
+	// Get the last sequence id that was published by this producer.
+	// This represent either the automatically assigned or custom sequence id (set on the ProducerMessage) that
+	// was published and acknowledged by the broker.
+	// After recreating a producer with the same producer name, this will return the last message that was
+	// published in the previous producer session, or -1 if there no message was ever published.
+	// return the last sequence id published by this producer.
+	LastSequenceID() int64
+
+	// Flush all the messages buffered in the client and wait until all messages have been successfully
+	// persisted.
+	Flush() error
+
+	// Close the producer and releases resources allocated
+	// No more writes will be accepted from this producer. Waits until all pending write request are persisted. In case
+	// of errors, pending writes will not be retried.
+	Close() error
+}
diff --git a/pulsar/pulsar_proto/PulsarApi.pb.go b/pulsar/pulsar_proto/PulsarApi.pb.go
new file mode 100644
index 0000000..cb82acd
--- /dev/null
+++ b/pulsar/pulsar_proto/PulsarApi.pb.go
@@ -0,0 +1,4043 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: PulsarApi.proto
+
+package pulsar_proto
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+
+type CompressionType int32
+
+const (
+	CompressionType_NONE CompressionType = 0
+	CompressionType_LZ4  CompressionType = 1
+	CompressionType_ZLIB CompressionType = 2
+	CompressionType_ZSTD CompressionType = 3
+)
+
+var CompressionType_name = map[int32]string{
+	0: "NONE",
+	1: "LZ4",
+	2: "ZLIB",
+	3: "ZSTD",
+}
+var CompressionType_value = map[string]int32{
+	"NONE": 0,
+	"LZ4":  1,
+	"ZLIB": 2,
+	"ZSTD": 3,
+}
+
+func (x CompressionType) Enum() *CompressionType {
+	p := new(CompressionType)
+	*p = x
+	return p
+}
+func (x CompressionType) String() string {
+	return proto.EnumName(CompressionType_name, int32(x))
+}
+func (x *CompressionType) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(CompressionType_value, data, "CompressionType")
+	if err != nil {
+		return err
+	}
+	*x = CompressionType(value)
+	return nil
+}
+func (CompressionType) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{0}
+}
+
+type ServerError int32
+
+const (
+	ServerError_UnknownError        ServerError = 0
+	ServerError_MetadataError       ServerError = 1
+	ServerError_PersistenceError    ServerError = 2
+	ServerError_AuthenticationError ServerError = 3
+	ServerError_AuthorizationError  ServerError = 4
+	ServerError_ConsumerBusy        ServerError = 5
+	// other consumers are connected
+	ServerError_ServiceNotReady                       ServerError = 6
+	ServerError_ProducerBlockedQuotaExceededError     ServerError = 7
+	ServerError_ProducerBlockedQuotaExceededException ServerError = 8
+	ServerError_ChecksumError                         ServerError = 9
+	ServerError_UnsupportedVersionError               ServerError = 10
+	ServerError_TopicNotFound                         ServerError = 11
+	ServerError_SubscriptionNotFound                  ServerError = 12
+	ServerError_ConsumerNotFound                      ServerError = 13
+	ServerError_TooManyRequests                       ServerError = 14
+	ServerError_TopicTerminatedError                  ServerError = 15
+	ServerError_ProducerBusy                          ServerError = 16
+	ServerError_InvalidTopicName                      ServerError = 17
+	ServerError_IncompatibleSchema                    ServerError = 18
+)
+
+var ServerError_name = map[int32]string{
+	0:  "UnknownError",
+	1:  "MetadataError",
+	2:  "PersistenceError",
+	3:  "AuthenticationError",
+	4:  "AuthorizationError",
+	5:  "ConsumerBusy",
+	6:  "ServiceNotReady",
+	7:  "ProducerBlockedQuotaExceededError",
+	8:  "ProducerBlockedQuotaExceededException",
+	9:  "ChecksumError",
+	10: "UnsupportedVersionError",
+	11: "TopicNotFound",
+	12: "SubscriptionNotFound",
+	13: "ConsumerNotFound",
+	14: "TooManyRequests",
+	15: "TopicTerminatedError",
+	16: "ProducerBusy",
+	17: "InvalidTopicName",
+	18: "IncompatibleSchema",
+}
+var ServerError_value = map[string]int32{
+	"UnknownError":                          0,
+	"MetadataError":                         1,
+	"PersistenceError":                      2,
+	"AuthenticationError":                   3,
+	"AuthorizationError":                    4,
+	"ConsumerBusy":                          5,
+	"ServiceNotReady":                       6,
+	"ProducerBlockedQuotaExceededError":     7,
+	"ProducerBlockedQuotaExceededException": 8,
+	"ChecksumError":                         9,
+	"UnsupportedVersionError":               10,
+	"TopicNotFound":                         11,
+	"SubscriptionNotFound":                  12,
+	"ConsumerNotFound":                      13,
+	"TooManyRequests":                       14,
+	"TopicTerminatedError":                  15,
+	"ProducerBusy":                          16,
+	"InvalidTopicName":                      17,
+	"IncompatibleSchema":                    18,
+}
+
+func (x ServerError) Enum() *ServerError {
+	p := new(ServerError)
+	*p = x
+	return p
+}
+func (x ServerError) String() string {
+	return proto.EnumName(ServerError_name, int32(x))
+}
+func (x *ServerError) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(ServerError_value, data, "ServerError")
+	if err != nil {
+		return err
+	}
+	*x = ServerError(value)
+	return nil
+}
+func (ServerError) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{1}
+}
+
+type AuthMethod int32
+
+const (
+	AuthMethod_AuthMethodNone   AuthMethod = 0
+	AuthMethod_AuthMethodYcaV1  AuthMethod = 1
+	AuthMethod_AuthMethodAthens AuthMethod = 2
+)
+
+var AuthMethod_name = map[int32]string{
+	0: "AuthMethodNone",
+	1: "AuthMethodYcaV1",
+	2: "AuthMethodAthens",
+}
+var AuthMethod_value = map[string]int32{
+	"AuthMethodNone":   0,
+	"AuthMethodYcaV1":  1,
+	"AuthMethodAthens": 2,
+}
+
+func (x AuthMethod) Enum() *AuthMethod {
+	p := new(AuthMethod)
+	*p = x
+	return p
+}
+func (x AuthMethod) String() string {
+	return proto.EnumName(AuthMethod_name, int32(x))
+}
+func (x *AuthMethod) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(AuthMethod_value, data, "AuthMethod")
+	if err != nil {
+		return err
+	}
+	*x = AuthMethod(value)
+	return nil
+}
+func (AuthMethod) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{2}
+}
+
+// Each protocol version identify new features that are
+// incrementally added to the protocol
+type ProtocolVersion int32
+
+const (
+	ProtocolVersion_v0  ProtocolVersion = 0
+	ProtocolVersion_v1  ProtocolVersion = 1
+	ProtocolVersion_v2  ProtocolVersion = 2
+	ProtocolVersion_v3  ProtocolVersion = 3
+	ProtocolVersion_v4  ProtocolVersion = 4
+	ProtocolVersion_v5  ProtocolVersion = 5
+	ProtocolVersion_v6  ProtocolVersion = 6
+	ProtocolVersion_v7  ProtocolVersion = 7
+	ProtocolVersion_v8  ProtocolVersion = 8
+	ProtocolVersion_v9  ProtocolVersion = 9
+	ProtocolVersion_v10 ProtocolVersion = 10
+	ProtocolVersion_v11 ProtocolVersion = 11
+	ProtocolVersion_v12 ProtocolVersion = 12
+	// Added CommandActiveConsumerChange
+	// Added CommandGetTopicsOfNamespace
+	ProtocolVersion_v13 ProtocolVersion = 13
+)
+
+var ProtocolVersion_name = map[int32]string{
+	0:  "v0",
+	1:  "v1",
+	2:  "v2",
+	3:  "v3",
+	4:  "v4",
+	5:  "v5",
+	6:  "v6",
+	7:  "v7",
+	8:  "v8",
+	9:  "v9",
+	10: "v10",
+	11: "v11",
+	12: "v12",
+	13: "v13",
+}
+var ProtocolVersion_value = map[string]int32{
+	"v0":  0,
+	"v1":  1,
+	"v2":  2,
+	"v3":  3,
+	"v4":  4,
+	"v5":  5,
+	"v6":  6,
+	"v7":  7,
+	"v8":  8,
+	"v9":  9,
+	"v10": 10,
+	"v11": 11,
+	"v12": 12,
+	"v13": 13,
+}
+
+func (x ProtocolVersion) Enum() *ProtocolVersion {
+	p := new(ProtocolVersion)
+	*p = x
+	return p
+}
+func (x ProtocolVersion) String() string {
+	return proto.EnumName(ProtocolVersion_name, int32(x))
+}
+func (x *ProtocolVersion) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(ProtocolVersion_value, data, "ProtocolVersion")
+	if err != nil {
+		return err
+	}
+	*x = ProtocolVersion(value)
+	return nil
+}
+func (ProtocolVersion) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{3}
+}
+
+type Schema_Type int32
+
+const (
+	Schema_None     Schema_Type = 0
+	Schema_String   Schema_Type = 1
+	Schema_Json     Schema_Type = 2
+	Schema_Protobuf Schema_Type = 3
+	Schema_Avro     Schema_Type = 4
+)
+
+var Schema_Type_name = map[int32]string{
+	0: "None",
+	1: "String",
+	2: "Json",
+	3: "Protobuf",
+	4: "Avro",
+}
+var Schema_Type_value = map[string]int32{
+	"None":     0,
+	"String":   1,
+	"Json":     2,
+	"Protobuf": 3,
+	"Avro":     4,
+}
+
+func (x Schema_Type) Enum() *Schema_Type {
+	p := new(Schema_Type)
+	*p = x
+	return p
+}
+func (x Schema_Type) String() string {
+	return proto.EnumName(Schema_Type_name, int32(x))
+}
+func (x *Schema_Type) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(Schema_Type_value, data, "Schema_Type")
+	if err != nil {
+		return err
+	}
+	*x = Schema_Type(value)
+	return nil
+}
+func (Schema_Type) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{0, 0}
+}
+
+type CommandSubscribe_SubType int32
+
+const (
+	CommandSubscribe_Exclusive CommandSubscribe_SubType = 0
+	CommandSubscribe_Shared    CommandSubscribe_SubType = 1
+	CommandSubscribe_Failover  CommandSubscribe_SubType = 2
+)
+
+var CommandSubscribe_SubType_name = map[int32]string{
+	0: "Exclusive",
+	1: "Shared",
+	2: "Failover",
+}
+var CommandSubscribe_SubType_value = map[string]int32{
+	"Exclusive": 0,
+	"Shared":    1,
+	"Failover":  2,
+}
+
+func (x CommandSubscribe_SubType) Enum() *CommandSubscribe_SubType {
+	p := new(CommandSubscribe_SubType)
+	*p = x
+	return p
+}
+func (x CommandSubscribe_SubType) String() string {
+	return proto.EnumName(CommandSubscribe_SubType_name, int32(x))
+}
+func (x *CommandSubscribe_SubType) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(CommandSubscribe_SubType_value, data, "CommandSubscribe_SubType")
+	if err != nil {
+		return err
+	}
+	*x = CommandSubscribe_SubType(value)
+	return nil
+}
+func (CommandSubscribe_SubType) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{9, 0}
+}
+
+type CommandSubscribe_InitialPosition int32
+
+const (
+	CommandSubscribe_Latest   CommandSubscribe_InitialPosition = 0
+	CommandSubscribe_Earliest CommandSubscribe_InitialPosition = 1
+)
+
+var CommandSubscribe_InitialPosition_name = map[int32]string{
+	0: "Latest",
+	1: "Earliest",
+}
+var CommandSubscribe_InitialPosition_value = map[string]int32{
+	"Latest":   0,
+	"Earliest": 1,
+}
+
+func (x CommandSubscribe_InitialPosition) Enum() *CommandSubscribe_InitialPosition {
+	p := new(CommandSubscribe_InitialPosition)
+	*p = x
+	return p
+}
+func (x CommandSubscribe_InitialPosition) String() string {
+	return proto.EnumName(CommandSubscribe_InitialPosition_name, int32(x))
+}
+func (x *CommandSubscribe_InitialPosition) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(CommandSubscribe_InitialPosition_value, data, "CommandSubscribe_InitialPosition")
+	if err != nil {
+		return err
+	}
+	*x = CommandSubscribe_InitialPosition(value)
+	return nil
+}
+func (CommandSubscribe_InitialPosition) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{9, 1}
+}
+
+type CommandPartitionedTopicMetadataResponse_LookupType int32
+
+const (
+	CommandPartitionedTopicMetadataResponse_Success CommandPartitionedTopicMetadataResponse_LookupType = 0
+	CommandPartitionedTopicMetadataResponse_Failed  CommandPartitionedTopicMetadataResponse_LookupType = 1
+)
+
+var CommandPartitionedTopicMetadataResponse_LookupType_name = map[int32]string{
+	0: "Success",
+	1: "Failed",
+}
+var CommandPartitionedTopicMetadataResponse_LookupType_value = map[string]int32{
+	"Success": 0,
+	"Failed":  1,
+}
+
+func (x CommandPartitionedTopicMetadataResponse_LookupType) Enum() *CommandPartitionedTopicMetadataResponse_LookupType {
+	p := new(CommandPartitionedTopicMetadataResponse_LookupType)
+	*p = x
+	return p
+}
+func (x CommandPartitionedTopicMetadataResponse_LookupType) String() string {
+	return proto.EnumName(CommandPartitionedTopicMetadataResponse_LookupType_name, int32(x))
+}
+func (x *CommandPartitionedTopicMetadataResponse_LookupType) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(CommandPartitionedTopicMetadataResponse_LookupType_value, data, "CommandPartitionedTopicMetadataResponse_LookupType")
+	if err != nil {
+		return err
+	}
+	*x = CommandPartitionedTopicMetadataResponse_LookupType(value)
+	return nil
+}
+func (CommandPartitionedTopicMetadataResponse_LookupType) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{11, 0}
+}
+
+type CommandLookupTopicResponse_LookupType int32
+
+const (
+	CommandLookupTopicResponse_Redirect CommandLookupTopicResponse_LookupType = 0
+	CommandLookupTopicResponse_Connect  CommandLookupTopicResponse_LookupType = 1
+	CommandLookupTopicResponse_Failed   CommandLookupTopicResponse_LookupType = 2
+)
+
+var CommandLookupTopicResponse_LookupType_name = map[int32]string{
+	0: "Redirect",
+	1: "Connect",
+	2: "Failed",
+}
+var CommandLookupTopicResponse_LookupType_value = map[string]int32{
+	"Redirect": 0,
+	"Connect":  1,
+	"Failed":   2,
+}
+
+func (x CommandLookupTopicResponse_LookupType) Enum() *CommandLookupTopicResponse_LookupType {
+	p := new(CommandLookupTopicResponse_LookupType)
+	*p = x
+	return p
+}
+func (x CommandLookupTopicResponse_LookupType) String() string {
+	return proto.EnumName(CommandLookupTopicResponse_LookupType_name, int32(x))
+}
+func (x *CommandLookupTopicResponse_LookupType) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(CommandLookupTopicResponse_LookupType_value, data, "CommandLookupTopicResponse_LookupType")
+	if err != nil {
+		return err
+	}
+	*x = CommandLookupTopicResponse_LookupType(value)
+	return nil
+}
+func (CommandLookupTopicResponse_LookupType) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{13, 0}
+}
+
+type CommandAck_AckType int32
+
+const (
+	CommandAck_Individual CommandAck_AckType = 0
+	CommandAck_Cumulative CommandAck_AckType = 1
+)
+
+var CommandAck_AckType_name = map[int32]string{
+	0: "Individual",
+	1: "Cumulative",
+}
+var CommandAck_AckType_value = map[string]int32{
+	"Individual": 0,
+	"Cumulative": 1,
+}
+
+func (x CommandAck_AckType) Enum() *CommandAck_AckType {
+	p := new(CommandAck_AckType)
+	*p = x
+	return p
+}
+func (x CommandAck_AckType) String() string {
+	return proto.EnumName(CommandAck_AckType_name, int32(x))
+}
+func (x *CommandAck_AckType) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(CommandAck_AckType_value, data, "CommandAck_AckType")
+	if err != nil {
+		return err
+	}
+	*x = CommandAck_AckType(value)
+	return nil
+}
+func (CommandAck_AckType) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{19, 0}
+}
+
+// Acks can contain a flag to indicate the consumer
+// received an invalid message that got discarded
+// before being passed on to the application.
+type CommandAck_ValidationError int32
+
+const (
+	CommandAck_UncompressedSizeCorruption CommandAck_ValidationError = 0
+	CommandAck_DecompressionError         CommandAck_ValidationError = 1
+	CommandAck_ChecksumMismatch           CommandAck_ValidationError = 2
+	CommandAck_BatchDeSerializeError      CommandAck_ValidationError = 3
+	CommandAck_DecryptionError            CommandAck_ValidationError = 4
+)
+
+var CommandAck_ValidationError_name = map[int32]string{
+	0: "UncompressedSizeCorruption",
+	1: "DecompressionError",
+	2: "ChecksumMismatch",
+	3: "BatchDeSerializeError",
+	4: "DecryptionError",
+}
+var CommandAck_ValidationError_value = map[string]int32{
+	"UncompressedSizeCorruption": 0,
+	"DecompressionError":         1,
+	"ChecksumMismatch":           2,
+	"BatchDeSerializeError":      3,
+	"DecryptionError":            4,
+}
+
+func (x CommandAck_ValidationError) Enum() *CommandAck_ValidationError {
+	p := new(CommandAck_ValidationError)
+	*p = x
+	return p
+}
+func (x CommandAck_ValidationError) String() string {
+	return proto.EnumName(CommandAck_ValidationError_name, int32(x))
+}
+func (x *CommandAck_ValidationError) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(CommandAck_ValidationError_value, data, "CommandAck_ValidationError")
+	if err != nil {
+		return err
+	}
+	*x = CommandAck_ValidationError(value)
+	return nil
+}
+func (CommandAck_ValidationError) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{19, 1}
+}
+
+type CommandGetTopicsOfNamespace_Mode int32
+
+const (
+	CommandGetTopicsOfNamespace_PERSISTENT     CommandGetTopicsOfNamespace_Mode = 0
+	CommandGetTopicsOfNamespace_NON_PERSISTENT CommandGetTopicsOfNamespace_Mode = 1
+	CommandGetTopicsOfNamespace_ALL            CommandGetTopicsOfNamespace_Mode = 2
+)
+
+var CommandGetTopicsOfNamespace_Mode_name = map[int32]string{
+	0: "PERSISTENT",
+	1: "NON_PERSISTENT",
+	2: "ALL",
+}
+var CommandGetTopicsOfNamespace_Mode_value = map[string]int32{
+	"PERSISTENT":     0,
+	"NON_PERSISTENT": 1,
+	"ALL":            2,
+}
+
+func (x CommandGetTopicsOfNamespace_Mode) Enum() *CommandGetTopicsOfNamespace_Mode {
+	p := new(CommandGetTopicsOfNamespace_Mode)
+	*p = x
+	return p
+}
+func (x CommandGetTopicsOfNamespace_Mode) String() string {
+	return proto.EnumName(CommandGetTopicsOfNamespace_Mode_name, int32(x))
+}
+func (x *CommandGetTopicsOfNamespace_Mode) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(CommandGetTopicsOfNamespace_Mode_value, data, "CommandGetTopicsOfNamespace_Mode")
+	if err != nil {
+		return err
+	}
+	*x = CommandGetTopicsOfNamespace_Mode(value)
+	return nil
+}
+func (CommandGetTopicsOfNamespace_Mode) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{37, 0}
+}
+
+type BaseCommand_Type int32
+
+const (
+	BaseCommand_CONNECT                           BaseCommand_Type = 2
+	BaseCommand_CONNECTED                         BaseCommand_Type = 3
+	BaseCommand_SUBSCRIBE                         BaseCommand_Type = 4
+	BaseCommand_PRODUCER                          BaseCommand_Type = 5
+	BaseCommand_SEND                              BaseCommand_Type = 6
+	BaseCommand_SEND_RECEIPT                      BaseCommand_Type = 7
+	BaseCommand_SEND_ERROR                        BaseCommand_Type = 8
+	BaseCommand_MESSAGE                           BaseCommand_Type = 9
+	BaseCommand_ACK                               BaseCommand_Type = 10
+	BaseCommand_FLOW                              BaseCommand_Type = 11
+	BaseCommand_UNSUBSCRIBE                       BaseCommand_Type = 12
+	BaseCommand_SUCCESS                           BaseCommand_Type = 13
+	BaseCommand_ERROR                             BaseCommand_Type = 14
+	BaseCommand_CLOSE_PRODUCER                    BaseCommand_Type = 15
+	BaseCommand_CLOSE_CONSUMER                    BaseCommand_Type = 16
+	BaseCommand_PRODUCER_SUCCESS                  BaseCommand_Type = 17
+	BaseCommand_PING                              BaseCommand_Type = 18
+	BaseCommand_PONG                              BaseCommand_Type = 19
+	BaseCommand_REDELIVER_UNACKNOWLEDGED_MESSAGES BaseCommand_Type = 20
+	BaseCommand_PARTITIONED_METADATA              BaseCommand_Type = 21
+	BaseCommand_PARTITIONED_METADATA_RESPONSE     BaseCommand_Type = 22
+	BaseCommand_LOOKUP                            BaseCommand_Type = 23
+	BaseCommand_LOOKUP_RESPONSE                   BaseCommand_Type = 24
+	BaseCommand_CONSUMER_STATS                    BaseCommand_Type = 25
+	BaseCommand_CONSUMER_STATS_RESPONSE           BaseCommand_Type = 26
+	BaseCommand_REACHED_END_OF_TOPIC              BaseCommand_Type = 27
+	BaseCommand_SEEK                              BaseCommand_Type = 28
+	BaseCommand_GET_LAST_MESSAGE_ID               BaseCommand_Type = 29
+	BaseCommand_GET_LAST_MESSAGE_ID_RESPONSE      BaseCommand_Type = 30
+	BaseCommand_ACTIVE_CONSUMER_CHANGE            BaseCommand_Type = 31
+	BaseCommand_GET_TOPICS_OF_NAMESPACE           BaseCommand_Type = 32
+	BaseCommand_GET_TOPICS_OF_NAMESPACE_RESPONSE  BaseCommand_Type = 33
+	BaseCommand_GET_SCHEMA                        BaseCommand_Type = 34
+	BaseCommand_GET_SCHEMA_RESPONSE               BaseCommand_Type = 35
+)
+
+var BaseCommand_Type_name = map[int32]string{
+	2:  "CONNECT",
+	3:  "CONNECTED",
+	4:  "SUBSCRIBE",
+	5:  "PRODUCER",
+	6:  "SEND",
+	7:  "SEND_RECEIPT",
+	8:  "SEND_ERROR",
+	9:  "MESSAGE",
+	10: "ACK",
+	11: "FLOW",
+	12: "UNSUBSCRIBE",
+	13: "SUCCESS",
+	14: "ERROR",
+	15: "CLOSE_PRODUCER",
+	16: "CLOSE_CONSUMER",
+	17: "PRODUCER_SUCCESS",
+	18: "PING",
+	19: "PONG",
+	20: "REDELIVER_UNACKNOWLEDGED_MESSAGES",
+	21: "PARTITIONED_METADATA",
+	22: "PARTITIONED_METADATA_RESPONSE",
+	23: "LOOKUP",
+	24: "LOOKUP_RESPONSE",
+	25: "CONSUMER_STATS",
+	26: "CONSUMER_STATS_RESPONSE",
+	27: "REACHED_END_OF_TOPIC",
+	28: "SEEK",
+	29: "GET_LAST_MESSAGE_ID",
+	30: "GET_LAST_MESSAGE_ID_RESPONSE",
+	31: "ACTIVE_CONSUMER_CHANGE",
+	32: "GET_TOPICS_OF_NAMESPACE",
+	33: "GET_TOPICS_OF_NAMESPACE_RESPONSE",
+	34: "GET_SCHEMA",
+	35: "GET_SCHEMA_RESPONSE",
+}
+var BaseCommand_Type_value = map[string]int32{
+	"CONNECT":                           2,
+	"CONNECTED":                         3,
+	"SUBSCRIBE":                         4,
+	"PRODUCER":                          5,
+	"SEND":                              6,
+	"SEND_RECEIPT":                      7,
+	"SEND_ERROR":                        8,
+	"MESSAGE":                           9,
+	"ACK":                               10,
+	"FLOW":                              11,
+	"UNSUBSCRIBE":                       12,
+	"SUCCESS":                           13,
+	"ERROR":                             14,
+	"CLOSE_PRODUCER":                    15,
+	"CLOSE_CONSUMER":                    16,
+	"PRODUCER_SUCCESS":                  17,
+	"PING":                              18,
+	"PONG":                              19,
+	"REDELIVER_UNACKNOWLEDGED_MESSAGES": 20,
+	"PARTITIONED_METADATA":              21,
+	"PARTITIONED_METADATA_RESPONSE":     22,
+	"LOOKUP":                            23,
+	"LOOKUP_RESPONSE":                   24,
+	"CONSUMER_STATS":                    25,
+	"CONSUMER_STATS_RESPONSE":           26,
+	"REACHED_END_OF_TOPIC":              27,
+	"SEEK":                              28,
+	"GET_LAST_MESSAGE_ID":               29,
+	"GET_LAST_MESSAGE_ID_RESPONSE":      30,
+	"ACTIVE_CONSUMER_CHANGE":            31,
+	"GET_TOPICS_OF_NAMESPACE":           32,
+	"GET_TOPICS_OF_NAMESPACE_RESPONSE":  33,
+	"GET_SCHEMA":                        34,
+	"GET_SCHEMA_RESPONSE":               35,
+}
+
+func (x BaseCommand_Type) Enum() *BaseCommand_Type {
+	p := new(BaseCommand_Type)
+	*p = x
+	return p
+}
+func (x BaseCommand_Type) String() string {
+	return proto.EnumName(BaseCommand_Type_name, int32(x))
+}
+func (x *BaseCommand_Type) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(BaseCommand_Type_value, data, "BaseCommand_Type")
+	if err != nil {
+		return err
+	}
+	*x = BaseCommand_Type(value)
+	return nil
+}
+func (BaseCommand_Type) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{41, 0}
+}
+
+type Schema struct {
+	Name                 *string      `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
+	SchemaData           []byte       `protobuf:"bytes,3,req,name=schema_data,json=schemaData" json:"schema_data,omitempty"`
+	Type                 *Schema_Type `protobuf:"varint,4,req,name=type,enum=pulsar.proto.Schema_Type" json:"type,omitempty"`
+	Properties           []*KeyValue  `protobuf:"bytes,5,rep,name=properties" json:"properties,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}     `json:"-"`
+	XXX_unrecognized     []byte       `json:"-"`
+	XXX_sizecache        int32        `json:"-"`
+}
+
+func (m *Schema) Reset()         { *m = Schema{} }
+func (m *Schema) String() string { return proto.CompactTextString(m) }
+func (*Schema) ProtoMessage()    {}
+func (*Schema) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{0}
+}
+func (m *Schema) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Schema.Unmarshal(m, b)
+}
+func (m *Schema) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Schema.Marshal(b, m, deterministic)
+}
+func (dst *Schema) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Schema.Merge(dst, src)
+}
+func (m *Schema) XXX_Size() int {
+	return xxx_messageInfo_Schema.Size(m)
+}
+func (m *Schema) XXX_DiscardUnknown() {
+	xxx_messageInfo_Schema.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Schema proto.InternalMessageInfo
+
+func (m *Schema) GetName() string {
+	if m != nil && m.Name != nil {
+		return *m.Name
+	}
+	return ""
+}
+
+func (m *Schema) GetSchemaData() []byte {
+	if m != nil {
+		return m.SchemaData
+	}
+	return nil
+}
+
+func (m *Schema) GetType() Schema_Type {
+	if m != nil && m.Type != nil {
+		return *m.Type
+	}
+	return Schema_None
+}
+
+func (m *Schema) GetProperties() []*KeyValue {
+	if m != nil {
+		return m.Properties
+	}
+	return nil
+}
+
+type MessageIdData struct {
+	LedgerId             *uint64  `protobuf:"varint,1,req,name=ledgerId" json:"ledgerId,omitempty"`
+	EntryId              *uint64  `protobuf:"varint,2,req,name=entryId" json:"entryId,omitempty"`
+	Partition            *int32   `protobuf:"varint,3,opt,name=partition,def=-1" json:"partition,omitempty"`
+	BatchIndex           *int32   `protobuf:"varint,4,opt,name=batch_index,json=batchIndex,def=-1" json:"batch_index,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *MessageIdData) Reset()         { *m = MessageIdData{} }
+func (m *MessageIdData) String() string { return proto.CompactTextString(m) }
+func (*MessageIdData) ProtoMessage()    {}
+func (*MessageIdData) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{1}
+}
+func (m *MessageIdData) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_MessageIdData.Unmarshal(m, b)
+}
+func (m *MessageIdData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_MessageIdData.Marshal(b, m, deterministic)
+}
+func (dst *MessageIdData) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_MessageIdData.Merge(dst, src)
+}
+func (m *MessageIdData) XXX_Size() int {
+	return xxx_messageInfo_MessageIdData.Size(m)
+}
+func (m *MessageIdData) XXX_DiscardUnknown() {
+	xxx_messageInfo_MessageIdData.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_MessageIdData proto.InternalMessageInfo
+
+const Default_MessageIdData_Partition int32 = -1
+const Default_MessageIdData_BatchIndex int32 = -1
+
+func (m *MessageIdData) GetLedgerId() uint64 {
+	if m != nil && m.LedgerId != nil {
+		return *m.LedgerId
+	}
+	return 0
+}
+
+func (m *MessageIdData) GetEntryId() uint64 {
+	if m != nil && m.EntryId != nil {
+		return *m.EntryId
+	}
+	return 0
+}
+
+func (m *MessageIdData) GetPartition() int32 {
+	if m != nil && m.Partition != nil {
+		return *m.Partition
+	}
+	return Default_MessageIdData_Partition
+}
+
+func (m *MessageIdData) GetBatchIndex() int32 {
+	if m != nil && m.BatchIndex != nil {
+		return *m.BatchIndex
+	}
+	return Default_MessageIdData_BatchIndex
+}
+
+type KeyValue struct {
+	Key                  *string  `protobuf:"bytes,1,req,name=key" json:"key,omitempty"`
+	Value                *string  `protobuf:"bytes,2,req,name=value" json:"value,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *KeyValue) Reset()         { *m = KeyValue{} }
+func (m *KeyValue) String() string { return proto.CompactTextString(m) }
+func (*KeyValue) ProtoMessage()    {}
+func (*KeyValue) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{2}
+}
+func (m *KeyValue) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_KeyValue.Unmarshal(m, b)
+}
+func (m *KeyValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_KeyValue.Marshal(b, m, deterministic)
+}
+func (dst *KeyValue) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_KeyValue.Merge(dst, src)
+}
+func (m *KeyValue) XXX_Size() int {
+	return xxx_messageInfo_KeyValue.Size(m)
+}
+func (m *KeyValue) XXX_DiscardUnknown() {
+	xxx_messageInfo_KeyValue.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_KeyValue proto.InternalMessageInfo
+
+func (m *KeyValue) GetKey() string {
+	if m != nil && m.Key != nil {
+		return *m.Key
+	}
+	return ""
+}
+
+func (m *KeyValue) GetValue() string {
+	if m != nil && m.Value != nil {
+		return *m.Value
+	}
+	return ""
+}
+
+type KeyLongValue struct {
+	Key                  *string  `protobuf:"bytes,1,req,name=key" json:"key,omitempty"`
+	Value                *uint64  `protobuf:"varint,2,req,name=value" json:"value,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *KeyLongValue) Reset()         { *m = KeyLongValue{} }
+func (m *KeyLongValue) String() string { return proto.CompactTextString(m) }
+func (*KeyLongValue) ProtoMessage()    {}
+func (*KeyLongValue) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{3}
+}
+func (m *KeyLongValue) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_KeyLongValue.Unmarshal(m, b)
+}
+func (m *KeyLongValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_KeyLongValue.Marshal(b, m, deterministic)
+}
+func (dst *KeyLongValue) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_KeyLongValue.Merge(dst, src)
+}
+func (m *KeyLongValue) XXX_Size() int {
+	return xxx_messageInfo_KeyLongValue.Size(m)
+}
+func (m *KeyLongValue) XXX_DiscardUnknown() {
+	xxx_messageInfo_KeyLongValue.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_KeyLongValue proto.InternalMessageInfo
+
+func (m *KeyLongValue) GetKey() string {
+	if m != nil && m.Key != nil {
+		return *m.Key
+	}
+	return ""
+}
+
+func (m *KeyLongValue) GetValue() uint64 {
+	if m != nil && m.Value != nil {
+		return *m.Value
+	}
+	return 0
+}
+
+type EncryptionKeys struct {
+	Key                  *string     `protobuf:"bytes,1,req,name=key" json:"key,omitempty"`
+	Value                []byte      `protobuf:"bytes,2,req,name=value" json:"value,omitempty"`
+	Metadata             []*KeyValue `protobuf:"bytes,3,rep,name=metadata" json:"metadata,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
+	XXX_unrecognized     []byte      `json:"-"`
+	XXX_sizecache        int32       `json:"-"`
+}
+
+func (m *EncryptionKeys) Reset()         { *m = EncryptionKeys{} }
+func (m *EncryptionKeys) String() string { return proto.CompactTextString(m) }
+func (*EncryptionKeys) ProtoMessage()    {}
+func (*EncryptionKeys) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{4}
+}
+func (m *EncryptionKeys) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_EncryptionKeys.Unmarshal(m, b)
+}
+func (m *EncryptionKeys) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_EncryptionKeys.Marshal(b, m, deterministic)
+}
+func (dst *EncryptionKeys) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_EncryptionKeys.Merge(dst, src)
+}
+func (m *EncryptionKeys) XXX_Size() int {
+	return xxx_messageInfo_EncryptionKeys.Size(m)
+}
+func (m *EncryptionKeys) XXX_DiscardUnknown() {
+	xxx_messageInfo_EncryptionKeys.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_EncryptionKeys proto.InternalMessageInfo
+
+func (m *EncryptionKeys) GetKey() string {
+	if m != nil && m.Key != nil {
+		return *m.Key
+	}
+	return ""
+}
+
+func (m *EncryptionKeys) GetValue() []byte {
+	if m != nil {
+		return m.Value
+	}
+	return nil
+}
+
+func (m *EncryptionKeys) GetMetadata() []*KeyValue {
+	if m != nil {
+		return m.Metadata
+	}
+	return nil
+}
+
+type MessageMetadata struct {
+	ProducerName *string     `protobuf:"bytes,1,req,name=producer_name,json=producerName" json:"producer_name,omitempty"`
+	SequenceId   *uint64     `protobuf:"varint,2,req,name=sequence_id,json=sequenceId" json:"sequence_id,omitempty"`
+	PublishTime  *uint64     `protobuf:"varint,3,req,name=publish_time,json=publishTime" json:"publish_time,omitempty"`
+	Properties   []*KeyValue `protobuf:"bytes,4,rep,name=properties" json:"properties,omitempty"`
+	// Property set on replicated message,
+	// includes the source cluster name
+	ReplicatedFrom *string `protobuf:"bytes,5,opt,name=replicated_from,json=replicatedFrom" json:"replicated_from,omitempty"`
+	// key to decide partition for the msg
+	PartitionKey *string `protobuf:"bytes,6,opt,name=partition_key,json=partitionKey" json:"partition_key,omitempty"`
+	// Override namespace's replication
+	ReplicateTo      []string         `protobuf:"bytes,7,rep,name=replicate_to,json=replicateTo" json:"replicate_to,omitempty"`
+	Compression      *CompressionType `protobuf:"varint,8,opt,name=compression,enum=pulsar.proto.CompressionType,def=0" json:"compression,omitempty"`
+	UncompressedSize *uint32          `protobuf:"varint,9,opt,name=uncompressed_size,json=uncompressedSize,def=0" json:"uncompressed_size,omitempty"`
+	// Removed below checksum field from Metadata as
+	// it should be part of send-command which keeps checksum of header + payload
+	// optional sfixed64 checksum = 10;
+	// differentiate single and batch message metadata
+	NumMessagesInBatch *int32 `protobuf:"varint,11,opt,name=num_messages_in_batch,json=numMessagesInBatch,def=1" json:"num_messages_in_batch,omitempty"`
+	// the timestamp that this event occurs. it is typically set by applications.
+	// if this field is omitted, `publish_time` can be used for the purpose of `event_time`.
+	EventTime *uint64 `protobuf:"varint,12,opt,name=event_time,json=eventTime,def=0" json:"event_time,omitempty"`
+	// Contains encryption key name, encrypted key and metadata to describe the key
+	EncryptionKeys []*EncryptionKeys `protobuf:"bytes,13,rep,name=encryption_keys,json=encryptionKeys" json:"encryption_keys,omitempty"`
+	// Algorithm used to encrypt data key
+	EncryptionAlgo *string `protobuf:"bytes,14,opt,name=encryption_algo,json=encryptionAlgo" json:"encryption_algo,omitempty"`
+	// Additional parameters required by encryption
+	EncryptionParam        []byte   `protobuf:"bytes,15,opt,name=encryption_param,json=encryptionParam" json:"encryption_param,omitempty"`
+	SchemaVersion          []byte   `protobuf:"bytes,16,opt,name=schema_version,json=schemaVersion" json:"schema_version,omitempty"`
+	PartitionKeyB64Encoded *bool    `protobuf:"varint,17,opt,name=partition_key_b64_encoded,json=partitionKeyB64Encoded,def=0" json:"partition_key_b64_encoded,omitempty"`
+	XXX_NoUnkeyedLiteral   struct{} `json:"-"`
+	XXX_unrecognized       []byte   `json:"-"`
+	XXX_sizecache          int32    `json:"-"`
+}
+
+func (m *MessageMetadata) Reset()         { *m = MessageMetadata{} }
+func (m *MessageMetadata) String() string { return proto.CompactTextString(m) }
+func (*MessageMetadata) ProtoMessage()    {}
+func (*MessageMetadata) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{5}
+}
+func (m *MessageMetadata) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_MessageMetadata.Unmarshal(m, b)
+}
+func (m *MessageMetadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_MessageMetadata.Marshal(b, m, deterministic)
+}
+func (dst *MessageMetadata) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_MessageMetadata.Merge(dst, src)
+}
+func (m *MessageMetadata) XXX_Size() int {
+	return xxx_messageInfo_MessageMetadata.Size(m)
+}
+func (m *MessageMetadata) XXX_DiscardUnknown() {
+	xxx_messageInfo_MessageMetadata.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_MessageMetadata proto.InternalMessageInfo
+
+const Default_MessageMetadata_Compression CompressionType = CompressionType_NONE
+const Default_MessageMetadata_UncompressedSize uint32 = 0
+const Default_MessageMetadata_NumMessagesInBatch int32 = 1
+const Default_MessageMetadata_EventTime uint64 = 0
+const Default_MessageMetadata_PartitionKeyB64Encoded bool = false
+
+func (m *MessageMetadata) GetProducerName() string {
+	if m != nil && m.ProducerName != nil {
+		return *m.ProducerName
+	}
+	return ""
+}
+
+func (m *MessageMetadata) GetSequenceId() uint64 {
+	if m != nil && m.SequenceId != nil {
+		return *m.SequenceId
+	}
+	return 0
+}
+
+func (m *MessageMetadata) GetPublishTime() uint64 {
+	if m != nil && m.PublishTime != nil {
+		return *m.PublishTime
+	}
+	return 0
+}
+
+func (m *MessageMetadata) GetProperties() []*KeyValue {
+	if m != nil {
+		return m.Properties
+	}
+	return nil
+}
+
+func (m *MessageMetadata) GetReplicatedFrom() string {
+	if m != nil && m.ReplicatedFrom != nil {
+		return *m.ReplicatedFrom
+	}
+	return ""
+}
+
+func (m *MessageMetadata) GetPartitionKey() string {
+	if m != nil && m.PartitionKey != nil {
+		return *m.PartitionKey
+	}
+	return ""
+}
+
+func (m *MessageMetadata) GetReplicateTo() []string {
+	if m != nil {
+		return m.ReplicateTo
+	}
+	return nil
+}
+
+func (m *MessageMetadata) GetCompression() CompressionType {
+	if m != nil && m.Compression != nil {
+		return *m.Compression
+	}
+	return Default_MessageMetadata_Compression
+}
+
+func (m *MessageMetadata) GetUncompressedSize() uint32 {
+	if m != nil && m.UncompressedSize != nil {
+		return *m.UncompressedSize
+	}
+	return Default_MessageMetadata_UncompressedSize
+}
+
+func (m *MessageMetadata) GetNumMessagesInBatch() int32 {
+	if m != nil && m.NumMessagesInBatch != nil {
+		return *m.NumMessagesInBatch
+	}
+	return Default_MessageMetadata_NumMessagesInBatch
+}
+
+func (m *MessageMetadata) GetEventTime() uint64 {
+	if m != nil && m.EventTime != nil {
+		return *m.EventTime
+	}
+	return Default_MessageMetadata_EventTime
+}
+
+func (m *MessageMetadata) GetEncryptionKeys() []*EncryptionKeys {
+	if m != nil {
+		return m.EncryptionKeys
+	}
+	return nil
+}
+
+func (m *MessageMetadata) GetEncryptionAlgo() string {
+	if m != nil && m.EncryptionAlgo != nil {
+		return *m.EncryptionAlgo
+	}
+	return ""
+}
+
+func (m *MessageMetadata) GetEncryptionParam() []byte {
+	if m != nil {
+		return m.EncryptionParam
+	}
+	return nil
+}
+
+func (m *MessageMetadata) GetSchemaVersion() []byte {
+	if m != nil {
+		return m.SchemaVersion
+	}
+	return nil
+}
+
+func (m *MessageMetadata) GetPartitionKeyB64Encoded() bool {
+	if m != nil && m.PartitionKeyB64Encoded != nil {
+		return *m.PartitionKeyB64Encoded
+	}
+	return Default_MessageMetadata_PartitionKeyB64Encoded
+}
+
+type SingleMessageMetadata struct {
+	Properties   []*KeyValue `protobuf:"bytes,1,rep,name=properties" json:"properties,omitempty"`
+	PartitionKey *string     `protobuf:"bytes,2,opt,name=partition_key,json=partitionKey" json:"partition_key,omitempty"`
+	PayloadSize  *int32      `protobuf:"varint,3,req,name=payload_size,json=payloadSize" json:"payload_size,omitempty"`
+	CompactedOut *bool       `protobuf:"varint,4,opt,name=compacted_out,json=compactedOut,def=0" json:"compacted_out,omitempty"`
+	// the timestamp that this event occurs. it is typically set by applications.
+	// if this field is omitted, `publish_time` can be used for the purpose of `event_time`.
+	EventTime              *uint64  `protobuf:"varint,5,opt,name=event_time,json=eventTime,def=0" json:"event_time,omitempty"`
+	PartitionKeyB64Encoded *bool    `protobuf:"varint,6,opt,name=partition_key_b64_encoded,json=partitionKeyB64Encoded,def=0" json:"partition_key_b64_encoded,omitempty"`
+	XXX_NoUnkeyedLiteral   struct{} `json:"-"`
+	XXX_unrecognized       []byte   `json:"-"`
+	XXX_sizecache          int32    `json:"-"`
+}
+
+func (m *SingleMessageMetadata) Reset()         { *m = SingleMessageMetadata{} }
+func (m *SingleMessageMetadata) String() string { return proto.CompactTextString(m) }
+func (*SingleMessageMetadata) ProtoMessage()    {}
+func (*SingleMessageMetadata) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{6}
+}
+func (m *SingleMessageMetadata) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_SingleMessageMetadata.Unmarshal(m, b)
+}
+func (m *SingleMessageMetadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_SingleMessageMetadata.Marshal(b, m, deterministic)
+}
+func (dst *SingleMessageMetadata) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_SingleMessageMetadata.Merge(dst, src)
+}
+func (m *SingleMessageMetadata) XXX_Size() int {
+	return xxx_messageInfo_SingleMessageMetadata.Size(m)
+}
+func (m *SingleMessageMetadata) XXX_DiscardUnknown() {
+	xxx_messageInfo_SingleMessageMetadata.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_SingleMessageMetadata proto.InternalMessageInfo
+
+const Default_SingleMessageMetadata_CompactedOut bool = false
+const Default_SingleMessageMetadata_EventTime uint64 = 0
+const Default_SingleMessageMetadata_PartitionKeyB64Encoded bool = false
+
+func (m *SingleMessageMetadata) GetProperties() []*KeyValue {
+	if m != nil {
+		return m.Properties
+	}
+	return nil
+}
+
+func (m *SingleMessageMetadata) GetPartitionKey() string {
+	if m != nil && m.PartitionKey != nil {
+		return *m.PartitionKey
+	}
+	return ""
+}
+
+func (m *SingleMessageMetadata) GetPayloadSize() int32 {
+	if m != nil && m.PayloadSize != nil {
+		return *m.PayloadSize
+	}
+	return 0
+}
+
+func (m *SingleMessageMetadata) GetCompactedOut() bool {
+	if m != nil && m.CompactedOut != nil {
+		return *m.CompactedOut
+	}
+	return Default_SingleMessageMetadata_CompactedOut
+}
+
+func (m *SingleMessageMetadata) GetEventTime() uint64 {
+	if m != nil && m.EventTime != nil {
+		return *m.EventTime
+	}
+	return Default_SingleMessageMetadata_EventTime
+}
+
+func (m *SingleMessageMetadata) GetPartitionKeyB64Encoded() bool {
+	if m != nil && m.PartitionKeyB64Encoded != nil {
+		return *m.PartitionKeyB64Encoded
+	}
+	return Default_SingleMessageMetadata_PartitionKeyB64Encoded
+}
+
+type CommandConnect struct {
+	ClientVersion   *string     `protobuf:"bytes,1,req,name=client_version,json=clientVersion" json:"client_version,omitempty"`
+	AuthMethod      *AuthMethod `protobuf:"varint,2,opt,name=auth_method,json=authMethod,enum=pulsar.proto.AuthMethod" json:"auth_method,omitempty"`
+	AuthMethodName  *string     `protobuf:"bytes,5,opt,name=auth_method_name,json=authMethodName" json:"auth_method_name,omitempty"`
+	AuthData        []byte      `protobuf:"bytes,3,opt,name=auth_data,json=authData" json:"auth_data,omitempty"`
+	ProtocolVersion *int32      `protobuf:"varint,4,opt,name=protocol_version,json=protocolVersion,def=0" json:"protocol_version,omitempty"`
+	// Client can ask to be proxyied to a specific broker
+	// This is only honored by a Pulsar proxy
+	ProxyToBrokerUrl *string `protobuf:"bytes,6,opt,name=proxy_to_broker_url,json=proxyToBrokerUrl" json:"proxy_to_broker_url,omitempty"`
+	// Original principal that was verified by
+	// a Pulsar proxy. In this case the auth info above
+	// will be the auth of the proxy itself
+	OriginalPrincipal *string `protobuf:"bytes,7,opt,name=original_principal,json=originalPrincipal" json:"original_principal,omitempty"`
+	// Original auth role and auth Method that was passed
+	// to the proxy. In this case the auth info above
+	// will be the auth of the proxy itself
+	OriginalAuthData     *string  `protobuf:"bytes,8,opt,name=original_auth_data,json=originalAuthData" json:"original_auth_data,omitempty"`
+	OriginalAuthMethod   *string  `protobuf:"bytes,9,opt,name=original_auth_method,json=originalAuthMethod" json:"original_auth_method,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CommandConnect) Reset()         { *m = CommandConnect{} }
+func (m *CommandConnect) String() string { return proto.CompactTextString(m) }
+func (*CommandConnect) ProtoMessage()    {}
+func (*CommandConnect) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{7}
+}
+func (m *CommandConnect) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandConnect.Unmarshal(m, b)
+}
+func (m *CommandConnect) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandConnect.Marshal(b, m, deterministic)
+}
+func (dst *CommandConnect) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandConnect.Merge(dst, src)
+}
+func (m *CommandConnect) XXX_Size() int {
+	return xxx_messageInfo_CommandConnect.Size(m)
+}
+func (m *CommandConnect) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandConnect.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandConnect proto.InternalMessageInfo
+
+const Default_CommandConnect_ProtocolVersion int32 = 0
+
+func (m *CommandConnect) GetClientVersion() string {
+	if m != nil && m.ClientVersion != nil {
+		return *m.ClientVersion
+	}
+	return ""
+}
+
+func (m *CommandConnect) GetAuthMethod() AuthMethod {
+	if m != nil && m.AuthMethod != nil {
+		return *m.AuthMethod
+	}
+	return AuthMethod_AuthMethodNone
+}
+
+func (m *CommandConnect) GetAuthMethodName() string {
+	if m != nil && m.AuthMethodName != nil {
+		return *m.AuthMethodName
+	}
+	return ""
+}
+
+func (m *CommandConnect) GetAuthData() []byte {
+	if m != nil {
+		return m.AuthData
+	}
+	return nil
+}
+
+func (m *CommandConnect) GetProtocolVersion() int32 {
+	if m != nil && m.ProtocolVersion != nil {
+		return *m.ProtocolVersion
+	}
+	return Default_CommandConnect_ProtocolVersion
+}
+
+func (m *CommandConnect) GetProxyToBrokerUrl() string {
+	if m != nil && m.ProxyToBrokerUrl != nil {
+		return *m.ProxyToBrokerUrl
+	}
+	return ""
+}
+
+func (m *CommandConnect) GetOriginalPrincipal() string {
+	if m != nil && m.OriginalPrincipal != nil {
+		return *m.OriginalPrincipal
+	}
+	return ""
+}
+
+func (m *CommandConnect) GetOriginalAuthData() string {
+	if m != nil && m.OriginalAuthData != nil {
+		return *m.OriginalAuthData
+	}
+	return ""
+}
+
+func (m *CommandConnect) GetOriginalAuthMethod() string {
+	if m != nil && m.OriginalAuthMethod != nil {
+		return *m.OriginalAuthMethod
+	}
+	return ""
+}
+
+type CommandConnected struct {
+	ServerVersion        *string  `protobuf:"bytes,1,req,name=server_version,json=serverVersion" json:"server_version,omitempty"`
+	ProtocolVersion      *int32   `protobuf:"varint,2,opt,name=protocol_version,json=protocolVersion,def=0" json:"protocol_version,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CommandConnected) Reset()         { *m = CommandConnected{} }
+func (m *CommandConnected) String() string { return proto.CompactTextString(m) }
+func (*CommandConnected) ProtoMessage()    {}
+func (*CommandConnected) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{8}
+}
+func (m *CommandConnected) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandConnected.Unmarshal(m, b)
+}
+func (m *CommandConnected) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandConnected.Marshal(b, m, deterministic)
+}
+func (dst *CommandConnected) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandConnected.Merge(dst, src)
+}
+func (m *CommandConnected) XXX_Size() int {
+	return xxx_messageInfo_CommandConnected.Size(m)
+}
+func (m *CommandConnected) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandConnected.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandConnected proto.InternalMessageInfo
+
+const Default_CommandConnected_ProtocolVersion int32 = 0
+
+func (m *CommandConnected) GetServerVersion() string {
+	if m != nil && m.ServerVersion != nil {
+		return *m.ServerVersion
+	}
+	return ""
+}
+
+func (m *CommandConnected) GetProtocolVersion() int32 {
+	if m != nil && m.ProtocolVersion != nil {
+		return *m.ProtocolVersion
+	}
+	return Default_CommandConnected_ProtocolVersion
+}
+
+type CommandSubscribe struct {
+	Topic         *string                   `protobuf:"bytes,1,req,name=topic" json:"topic,omitempty"`
+	Subscription  *string                   `protobuf:"bytes,2,req,name=subscription" json:"subscription,omitempty"`
+	SubType       *CommandSubscribe_SubType `protobuf:"varint,3,req,name=subType,enum=pulsar.proto.CommandSubscribe_SubType" json:"subType,omitempty"`
+	ConsumerId    *uint64                   `protobuf:"varint,4,req,name=consumer_id,json=consumerId" json:"consumer_id,omitempty"`
+	RequestId     *uint64                   `protobuf:"varint,5,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	ConsumerName  *string                   `protobuf:"bytes,6,opt,name=consumer_name,json=consumerName" json:"consumer_name,omitempty"`
+	PriorityLevel *int32                    `protobuf:"varint,7,opt,name=priority_level,json=priorityLevel" json:"priority_level,omitempty"`
+	// Signal wether the subscription should be backed by a
+	// durable cursor or not
+	Durable *bool `protobuf:"varint,8,opt,name=durable,def=1" json:"durable,omitempty"`
+	// If specified, the subscription will position the cursor
+	// markd-delete position  on the particular message id and
+	// will send messages from that point
+	StartMessageId *MessageIdData `protobuf:"bytes,9,opt,name=start_message_id,json=startMessageId" json:"start_message_id,omitempty"`
+	// / Add optional metadata key=value to this consumer
+	Metadata      []*KeyValue `protobuf:"bytes,10,rep,name=metadata" json:"metadata,omitempty"`
+	ReadCompacted *bool       `protobuf:"varint,11,opt,name=read_compacted,json=readCompacted" json:"read_compacted,omitempty"`
+	Schema        *Schema     `protobuf:"bytes,12,opt,name=schema" json:"schema,omitempty"`
+	// Signal wthether the subscription will initialize on latest
+	// or not -- earliest
+	InitialPosition      *CommandSubscribe_InitialPosition `protobuf:"varint,13,opt,name=initialPosition,enum=pulsar.proto.CommandSubscribe_InitialPosition,def=0" json:"initialPosition,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}                          `json:"-"`
+	XXX_unrecognized     []byte                            `json:"-"`
+	XXX_sizecache        int32                             `json:"-"`
+}
+
+func (m *CommandSubscribe) Reset()         { *m = CommandSubscribe{} }
+func (m *CommandSubscribe) String() string { return proto.CompactTextString(m) }
+func (*CommandSubscribe) ProtoMessage()    {}
+func (*CommandSubscribe) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{9}
+}
+func (m *CommandSubscribe) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandSubscribe.Unmarshal(m, b)
+}
+func (m *CommandSubscribe) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandSubscribe.Marshal(b, m, deterministic)
+}
+func (dst *CommandSubscribe) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandSubscribe.Merge(dst, src)
+}
+func (m *CommandSubscribe) XXX_Size() int {
+	return xxx_messageInfo_CommandSubscribe.Size(m)
+}
+func (m *CommandSubscribe) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandSubscribe.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandSubscribe proto.InternalMessageInfo
+
+const Default_CommandSubscribe_Durable bool = true
+const Default_CommandSubscribe_InitialPosition CommandSubscribe_InitialPosition = CommandSubscribe_Latest
+
+func (m *CommandSubscribe) GetTopic() string {
+	if m != nil && m.Topic != nil {
+		return *m.Topic
+	}
+	return ""
+}
+
+func (m *CommandSubscribe) GetSubscription() string {
+	if m != nil && m.Subscription != nil {
+		return *m.Subscription
+	}
+	return ""
+}
+
+func (m *CommandSubscribe) GetSubType() CommandSubscribe_SubType {
+	if m != nil && m.SubType != nil {
+		return *m.SubType
+	}
+	return CommandSubscribe_Exclusive
+}
+
+func (m *CommandSubscribe) GetConsumerId() uint64 {
+	if m != nil && m.ConsumerId != nil {
+		return *m.ConsumerId
+	}
+	return 0
+}
+
+func (m *CommandSubscribe) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+func (m *CommandSubscribe) GetConsumerName() string {
+	if m != nil && m.ConsumerName != nil {
+		return *m.ConsumerName
+	}
+	return ""
+}
+
+func (m *CommandSubscribe) GetPriorityLevel() int32 {
+	if m != nil && m.PriorityLevel != nil {
+		return *m.PriorityLevel
+	}
+	return 0
+}
+
+func (m *CommandSubscribe) GetDurable() bool {
+	if m != nil && m.Durable != nil {
+		return *m.Durable
+	}
+	return Default_CommandSubscribe_Durable
+}
+
+func (m *CommandSubscribe) GetStartMessageId() *MessageIdData {
+	if m != nil {
+		return m.StartMessageId
+	}
+	return nil
+}
+
+func (m *CommandSubscribe) GetMetadata() []*KeyValue {
+	if m != nil {
+		return m.Metadata
+	}
+	return nil
+}
+
+func (m *CommandSubscribe) GetReadCompacted() bool {
+	if m != nil && m.ReadCompacted != nil {
+		return *m.ReadCompacted
+	}
+	return false
+}
+
+func (m *CommandSubscribe) GetSchema() *Schema {
+	if m != nil {
+		return m.Schema
+	}
+	return nil
+}
+
+func (m *CommandSubscribe) GetInitialPosition() CommandSubscribe_InitialPosition {
+	if m != nil && m.InitialPosition != nil {
+		return *m.InitialPosition
+	}
+	return Default_CommandSubscribe_InitialPosition
+}
+
+type CommandPartitionedTopicMetadata struct {
+	Topic     *string `protobuf:"bytes,1,req,name=topic" json:"topic,omitempty"`
+	RequestId *uint64 `protobuf:"varint,2,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	// TODO - Remove original_principal, original_auth_data, original_auth_method
+	// Original principal that was verified by
+	// a Pulsar proxy.
+	OriginalPrincipal *string `protobuf:"bytes,3,opt,name=original_principal,json=originalPrincipal" json:"original_principal,omitempty"`
+	// Original auth role and auth Method that was passed
+	// to the proxy.
+	OriginalAuthData     *string  `protobuf:"bytes,4,opt,name=original_auth_data,json=originalAuthData" json:"original_auth_data,omitempty"`
+	OriginalAuthMethod   *string  `protobuf:"bytes,5,opt,name=original_auth_method,json=originalAuthMethod" json:"original_auth_method,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CommandPartitionedTopicMetadata) Reset()         { *m = CommandPartitionedTopicMetadata{} }
+func (m *CommandPartitionedTopicMetadata) String() string { return proto.CompactTextString(m) }
+func (*CommandPartitionedTopicMetadata) ProtoMessage()    {}
+func (*CommandPartitionedTopicMetadata) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{10}
+}
+func (m *CommandPartitionedTopicMetadata) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandPartitionedTopicMetadata.Unmarshal(m, b)
+}
+func (m *CommandPartitionedTopicMetadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandPartitionedTopicMetadata.Marshal(b, m, deterministic)
+}
+func (dst *CommandPartitionedTopicMetadata) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandPartitionedTopicMetadata.Merge(dst, src)
+}
+func (m *CommandPartitionedTopicMetadata) XXX_Size() int {
+	return xxx_messageInfo_CommandPartitionedTopicMetadata.Size(m)
+}
+func (m *CommandPartitionedTopicMetadata) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandPartitionedTopicMetadata.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandPartitionedTopicMetadata proto.InternalMessageInfo
+
+func (m *CommandPartitionedTopicMetadata) GetTopic() string {
+	if m != nil && m.Topic != nil {
+		return *m.Topic
+	}
+	return ""
+}
+
+func (m *CommandPartitionedTopicMetadata) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+func (m *CommandPartitionedTopicMetadata) GetOriginalPrincipal() string {
+	if m != nil && m.OriginalPrincipal != nil {
+		return *m.OriginalPrincipal
+	}
+	return ""
+}
+
+func (m *CommandPartitionedTopicMetadata) GetOriginalAuthData() string {
+	if m != nil && m.OriginalAuthData != nil {
+		return *m.OriginalAuthData
+	}
+	return ""
+}
+
+func (m *CommandPartitionedTopicMetadata) GetOriginalAuthMethod() string {
+	if m != nil && m.OriginalAuthMethod != nil {
+		return *m.OriginalAuthMethod
+	}
+	return ""
+}
+
+type CommandPartitionedTopicMetadataResponse struct {
+	Partitions           *uint32                                             `protobuf:"varint,1,opt,name=partitions" json:"partitions,omitempty"`
+	RequestId            *uint64                                             `protobuf:"varint,2,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	Response             *CommandPartitionedTopicMetadataResponse_LookupType `protobuf:"varint,3,opt,name=response,enum=pulsar.proto.CommandPartitionedTopicMetadataResponse_LookupType" json:"response,omitempty"`
+	Error                *ServerError                                        `protobuf:"varint,4,opt,name=error,enum=pulsar.proto.ServerError" json:"error,omitempty"`
+	Message              *string                                             `protobuf:"bytes,5,opt,name=message" json:"message,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}                                            `json:"-"`
+	XXX_unrecognized     []byte                                              `json:"-"`
+	XXX_sizecache        int32                                               `json:"-"`
+}
+
+func (m *CommandPartitionedTopicMetadataResponse) Reset() {
+	*m = CommandPartitionedTopicMetadataResponse{}
+}
+func (m *CommandPartitionedTopicMetadataResponse) String() string { return proto.CompactTextString(m) }
+func (*CommandPartitionedTopicMetadataResponse) ProtoMessage()    {}
+func (*CommandPartitionedTopicMetadataResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{11}
+}
+func (m *CommandPartitionedTopicMetadataResponse) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandPartitionedTopicMetadataResponse.Unmarshal(m, b)
+}
+func (m *CommandPartitionedTopicMetadataResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandPartitionedTopicMetadataResponse.Marshal(b, m, deterministic)
+}
+func (dst *CommandPartitionedTopicMetadataResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandPartitionedTopicMetadataResponse.Merge(dst, src)
+}
+func (m *CommandPartitionedTopicMetadataResponse) XXX_Size() int {
+	return xxx_messageInfo_CommandPartitionedTopicMetadataResponse.Size(m)
+}
+func (m *CommandPartitionedTopicMetadataResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandPartitionedTopicMetadataResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandPartitionedTopicMetadataResponse proto.InternalMessageInfo
+
+func (m *CommandPartitionedTopicMetadataResponse) GetPartitions() uint32 {
+	if m != nil && m.Partitions != nil {
+		return *m.Partitions
+	}
+	return 0
+}
+
+func (m *CommandPartitionedTopicMetadataResponse) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+func (m *CommandPartitionedTopicMetadataResponse) GetResponse() CommandPartitionedTopicMetadataResponse_LookupType {
+	if m != nil && m.Response != nil {
+		return *m.Response
+	}
+	return CommandPartitionedTopicMetadataResponse_Success
+}
+
+func (m *CommandPartitionedTopicMetadataResponse) GetError() ServerError {
+	if m != nil && m.Error != nil {
+		return *m.Error
+	}
+	return ServerError_UnknownError
+}
+
+func (m *CommandPartitionedTopicMetadataResponse) GetMessage() string {
+	if m != nil && m.Message != nil {
+		return *m.Message
+	}
+	return ""
+}
+
+type CommandLookupTopic struct {
+	Topic         *string `protobuf:"bytes,1,req,name=topic" json:"topic,omitempty"`
+	RequestId     *uint64 `protobuf:"varint,2,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	Authoritative *bool   `protobuf:"varint,3,opt,name=authoritative,def=0" json:"authoritative,omitempty"`
+	// TODO - Remove original_principal, original_auth_data, original_auth_method
+	// Original principal that was verified by
+	// a Pulsar proxy.
+	OriginalPrincipal *string `protobuf:"bytes,4,opt,name=original_principal,json=originalPrincipal" json:"original_principal,omitempty"`
+	// Original auth role and auth Method that was passed
+	// to the proxy.
+	OriginalAuthData     *string  `protobuf:"bytes,5,opt,name=original_auth_data,json=originalAuthData" json:"original_auth_data,omitempty"`
+	OriginalAuthMethod   *string  `protobuf:"bytes,6,opt,name=original_auth_method,json=originalAuthMethod" json:"original_auth_method,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CommandLookupTopic) Reset()         { *m = CommandLookupTopic{} }
+func (m *CommandLookupTopic) String() string { return proto.CompactTextString(m) }
+func (*CommandLookupTopic) ProtoMessage()    {}
+func (*CommandLookupTopic) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{12}
+}
+func (m *CommandLookupTopic) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandLookupTopic.Unmarshal(m, b)
+}
+func (m *CommandLookupTopic) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandLookupTopic.Marshal(b, m, deterministic)
+}
+func (dst *CommandLookupTopic) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandLookupTopic.Merge(dst, src)
+}
+func (m *CommandLookupTopic) XXX_Size() int {
+	return xxx_messageInfo_CommandLookupTopic.Size(m)
+}
+func (m *CommandLookupTopic) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandLookupTopic.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandLookupTopic proto.InternalMessageInfo
+
+const Default_CommandLookupTopic_Authoritative bool = false
+
+func (m *CommandLookupTopic) GetTopic() string {
+	if m != nil && m.Topic != nil {
+		return *m.Topic
+	}
+	return ""
+}
+
+func (m *CommandLookupTopic) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+func (m *CommandLookupTopic) GetAuthoritative() bool {
+	if m != nil && m.Authoritative != nil {
+		return *m.Authoritative
+	}
+	return Default_CommandLookupTopic_Authoritative
+}
+
+func (m *CommandLookupTopic) GetOriginalPrincipal() string {
+	if m != nil && m.OriginalPrincipal != nil {
+		return *m.OriginalPrincipal
+	}
+	return ""
+}
+
+func (m *CommandLookupTopic) GetOriginalAuthData() string {
+	if m != nil && m.OriginalAuthData != nil {
+		return *m.OriginalAuthData
+	}
+	return ""
+}
+
+func (m *CommandLookupTopic) GetOriginalAuthMethod() string {
+	if m != nil && m.OriginalAuthMethod != nil {
+		return *m.OriginalAuthMethod
+	}
+	return ""
+}
+
+type CommandLookupTopicResponse struct {
+	BrokerServiceUrl    *string                                `protobuf:"bytes,1,opt,name=brokerServiceUrl" json:"brokerServiceUrl,omitempty"`
+	BrokerServiceUrlTls *string                                `protobuf:"bytes,2,opt,name=brokerServiceUrlTls" json:"brokerServiceUrlTls,omitempty"`
+	Response            *CommandLookupTopicResponse_LookupType `protobuf:"varint,3,opt,name=response,enum=pulsar.proto.CommandLookupTopicResponse_LookupType" json:"response,omitempty"`
+	RequestId           *uint64                                `protobuf:"varint,4,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	Authoritative       *bool                                  `protobuf:"varint,5,opt,name=authoritative,def=0" json:"authoritative,omitempty"`
+	Error               *ServerError                           `protobuf:"varint,6,opt,name=error,enum=pulsar.proto.ServerError" json:"error,omitempty"`
+	Message             *string                                `protobuf:"bytes,7,opt,name=message" json:"message,omitempty"`
+	// If it's true, indicates to the client that it must
+	// always connect through the service url after the
+	// lookup has been completed.
+	ProxyThroughServiceUrl *bool    `protobuf:"varint,8,opt,name=proxy_through_service_url,json=proxyThroughServiceUrl,def=0" json:"proxy_through_service_url,omitempty"`
+	XXX_NoUnkeyedLiteral   struct{} `json:"-"`
+	XXX_unrecognized       []byte   `json:"-"`
+	XXX_sizecache          int32    `json:"-"`
+}
+
+func (m *CommandLookupTopicResponse) Reset()         { *m = CommandLookupTopicResponse{} }
+func (m *CommandLookupTopicResponse) String() string { return proto.CompactTextString(m) }
+func (*CommandLookupTopicResponse) ProtoMessage()    {}
+func (*CommandLookupTopicResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{13}
+}
+func (m *CommandLookupTopicResponse) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandLookupTopicResponse.Unmarshal(m, b)
+}
+func (m *CommandLookupTopicResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandLookupTopicResponse.Marshal(b, m, deterministic)
+}
+func (dst *CommandLookupTopicResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandLookupTopicResponse.Merge(dst, src)
+}
+func (m *CommandLookupTopicResponse) XXX_Size() int {
+	return xxx_messageInfo_CommandLookupTopicResponse.Size(m)
+}
+func (m *CommandLookupTopicResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandLookupTopicResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandLookupTopicResponse proto.InternalMessageInfo
+
+const Default_CommandLookupTopicResponse_Authoritative bool = false
+const Default_CommandLookupTopicResponse_ProxyThroughServiceUrl bool = false
+
+func (m *CommandLookupTopicResponse) GetBrokerServiceUrl() string {
+	if m != nil && m.BrokerServiceUrl != nil {
+		return *m.BrokerServiceUrl
+	}
+	return ""
+}
+
+func (m *CommandLookupTopicResponse) GetBrokerServiceUrlTls() string {
+	if m != nil && m.BrokerServiceUrlTls != nil {
+		return *m.BrokerServiceUrlTls
+	}
+	return ""
+}
+
+func (m *CommandLookupTopicResponse) GetResponse() CommandLookupTopicResponse_LookupType {
+	if m != nil && m.Response != nil {
+		return *m.Response
+	}
+	return CommandLookupTopicResponse_Redirect
+}
+
+func (m *CommandLookupTopicResponse) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+func (m *CommandLookupTopicResponse) GetAuthoritative() bool {
+	if m != nil && m.Authoritative != nil {
+		return *m.Authoritative
+	}
+	return Default_CommandLookupTopicResponse_Authoritative
+}
+
+func (m *CommandLookupTopicResponse) GetError() ServerError {
+	if m != nil && m.Error != nil {
+		return *m.Error
+	}
+	return ServerError_UnknownError
+}
+
+func (m *CommandLookupTopicResponse) GetMessage() string {
+	if m != nil && m.Message != nil {
+		return *m.Message
+	}
+	return ""
+}
+
+func (m *CommandLookupTopicResponse) GetProxyThroughServiceUrl() bool {
+	if m != nil && m.ProxyThroughServiceUrl != nil {
+		return *m.ProxyThroughServiceUrl
+	}
+	return Default_CommandLookupTopicResponse_ProxyThroughServiceUrl
+}
+
+// / Create a new Producer on a topic, assigning the given producer_id,
+// / all messages sent with this producer_id will be persisted on the topic
+type CommandProducer struct {
+	Topic      *string `protobuf:"bytes,1,req,name=topic" json:"topic,omitempty"`
+	ProducerId *uint64 `protobuf:"varint,2,req,name=producer_id,json=producerId" json:"producer_id,omitempty"`
+	RequestId  *uint64 `protobuf:"varint,3,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	// / If a producer name is specified, the name will be used,
+	// / otherwise the broker will generate a unique name
+	ProducerName *string `protobuf:"bytes,4,opt,name=producer_name,json=producerName" json:"producer_name,omitempty"`
+	Encrypted    *bool   `protobuf:"varint,5,opt,name=encrypted,def=0" json:"encrypted,omitempty"`
+	// / Add optional metadata key=value to this producer
+	Metadata             []*KeyValue `protobuf:"bytes,6,rep,name=metadata" json:"metadata,omitempty"`
+	Schema               *Schema     `protobuf:"bytes,7,opt,name=schema" json:"schema,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
+	XXX_unrecognized     []byte      `json:"-"`
+	XXX_sizecache        int32       `json:"-"`
+}
+
+func (m *CommandProducer) Reset()         { *m = CommandProducer{} }
+func (m *CommandProducer) String() string { return proto.CompactTextString(m) }
+func (*CommandProducer) ProtoMessage()    {}
+func (*CommandProducer) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{14}
+}
+func (m *CommandProducer) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandProducer.Unmarshal(m, b)
+}
+func (m *CommandProducer) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandProducer.Marshal(b, m, deterministic)
+}
+func (dst *CommandProducer) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandProducer.Merge(dst, src)
+}
+func (m *CommandProducer) XXX_Size() int {
+	return xxx_messageInfo_CommandProducer.Size(m)
+}
+func (m *CommandProducer) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandProducer.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandProducer proto.InternalMessageInfo
+
+const Default_CommandProducer_Encrypted bool = false
+
+func (m *CommandProducer) GetTopic() string {
+	if m != nil && m.Topic != nil {
+		return *m.Topic
+	}
+	return ""
+}
+
+func (m *CommandProducer) GetProducerId() uint64 {
+	if m != nil && m.ProducerId != nil {
+		return *m.ProducerId
+	}
+	return 0
+}
+
+func (m *CommandProducer) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+func (m *CommandProducer) GetProducerName() string {
+	if m != nil && m.ProducerName != nil {
+		return *m.ProducerName
+	}
+	return ""
+}
+
+func (m *CommandProducer) GetEncrypted() bool {
+	if m != nil && m.Encrypted != nil {
+		return *m.Encrypted
+	}
+	return Default_CommandProducer_Encrypted
+}
+
+func (m *CommandProducer) GetMetadata() []*KeyValue {
+	if m != nil {
+		return m.Metadata
+	}
+	return nil
+}
+
+func (m *CommandProducer) GetSchema() *Schema {
+	if m != nil {
+		return m.Schema
+	}
+	return nil
+}
+
+type CommandSend struct {
+	ProducerId           *uint64  `protobuf:"varint,1,req,name=producer_id,json=producerId" json:"producer_id,omitempty"`
+	SequenceId           *uint64  `protobuf:"varint,2,req,name=sequence_id,json=sequenceId" json:"sequence_id,omitempty"`
+	NumMessages          *int32   `protobuf:"varint,3,opt,name=num_messages,json=numMessages,def=1" json:"num_messages,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CommandSend) Reset()         { *m = CommandSend{} }
+func (m *CommandSend) String() string { return proto.CompactTextString(m) }
+func (*CommandSend) ProtoMessage()    {}
+func (*CommandSend) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{15}
+}
+func (m *CommandSend) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandSend.Unmarshal(m, b)
+}
+func (m *CommandSend) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandSend.Marshal(b, m, deterministic)
+}
+func (dst *CommandSend) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandSend.Merge(dst, src)
+}
+func (m *CommandSend) XXX_Size() int {
+	return xxx_messageInfo_CommandSend.Size(m)
+}
+func (m *CommandSend) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandSend.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandSend proto.InternalMessageInfo
+
+const Default_CommandSend_NumMessages int32 = 1
+
+func (m *CommandSend) GetProducerId() uint64 {
+	if m != nil && m.ProducerId != nil {
+		return *m.ProducerId
+	}
+	return 0
+}
+
+func (m *CommandSend) GetSequenceId() uint64 {
+	if m != nil && m.SequenceId != nil {
+		return *m.SequenceId
+	}
+	return 0
+}
+
+func (m *CommandSend) GetNumMessages() int32 {
+	if m != nil && m.NumMessages != nil {
+		return *m.NumMessages
+	}
+	return Default_CommandSend_NumMessages
+}
+
+type CommandSendReceipt struct {
+	ProducerId           *uint64        `protobuf:"varint,1,req,name=producer_id,json=producerId" json:"producer_id,omitempty"`
+	SequenceId           *uint64        `protobuf:"varint,2,req,name=sequence_id,json=sequenceId" json:"sequence_id,omitempty"`
+	MessageId            *MessageIdData `protobuf:"bytes,3,opt,name=message_id,json=messageId" json:"message_id,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}       `json:"-"`
+	XXX_unrecognized     []byte         `json:"-"`
+	XXX_sizecache        int32          `json:"-"`
+}
+
+func (m *CommandSendReceipt) Reset()         { *m = CommandSendReceipt{} }
+func (m *CommandSendReceipt) String() string { return proto.CompactTextString(m) }
+func (*CommandSendReceipt) ProtoMessage()    {}
+func (*CommandSendReceipt) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{16}
+}
+func (m *CommandSendReceipt) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandSendReceipt.Unmarshal(m, b)
+}
+func (m *CommandSendReceipt) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandSendReceipt.Marshal(b, m, deterministic)
+}
+func (dst *CommandSendReceipt) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandSendReceipt.Merge(dst, src)
+}
+func (m *CommandSendReceipt) XXX_Size() int {
+	return xxx_messageInfo_CommandSendReceipt.Size(m)
+}
+func (m *CommandSendReceipt) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandSendReceipt.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandSendReceipt proto.InternalMessageInfo
+
+func (m *CommandSendReceipt) GetProducerId() uint64 {
+	if m != nil && m.ProducerId != nil {
+		return *m.ProducerId
+	}
+	return 0
+}
+
+func (m *CommandSendReceipt) GetSequenceId() uint64 {
+	if m != nil && m.SequenceId != nil {
+		return *m.SequenceId
+	}
+	return 0
+}
+
+func (m *CommandSendReceipt) GetMessageId() *MessageIdData {
+	if m != nil {
+		return m.MessageId
+	}
+	return nil
+}
+
+type CommandSendError struct {
+	ProducerId           *uint64      `protobuf:"varint,1,req,name=producer_id,json=producerId" json:"producer_id,omitempty"`
+	SequenceId           *uint64      `protobuf:"varint,2,req,name=sequence_id,json=sequenceId" json:"sequence_id,omitempty"`
+	Error                *ServerError `protobuf:"varint,3,req,name=error,enum=pulsar.proto.ServerError" json:"error,omitempty"`
+	Message              *string      `protobuf:"bytes,4,req,name=message" json:"message,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}     `json:"-"`
+	XXX_unrecognized     []byte       `json:"-"`
+	XXX_sizecache        int32        `json:"-"`
+}
+
+func (m *CommandSendError) Reset()         { *m = CommandSendError{} }
+func (m *CommandSendError) String() string { return proto.CompactTextString(m) }
+func (*CommandSendError) ProtoMessage()    {}
+func (*CommandSendError) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{17}
+}
+func (m *CommandSendError) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandSendError.Unmarshal(m, b)
+}
+func (m *CommandSendError) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandSendError.Marshal(b, m, deterministic)
+}
+func (dst *CommandSendError) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandSendError.Merge(dst, src)
+}
+func (m *CommandSendError) XXX_Size() int {
+	return xxx_messageInfo_CommandSendError.Size(m)
+}
+func (m *CommandSendError) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandSendError.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandSendError proto.InternalMessageInfo
+
+func (m *CommandSendError) GetProducerId() uint64 {
+	if m != nil && m.ProducerId != nil {
+		return *m.ProducerId
+	}
+	return 0
+}
+
+func (m *CommandSendError) GetSequenceId() uint64 {
+	if m != nil && m.SequenceId != nil {
+		return *m.SequenceId
+	}
+	return 0
+}
+
+func (m *CommandSendError) GetError() ServerError {
+	if m != nil && m.Error != nil {
+		return *m.Error
+	}
+	return ServerError_UnknownError
+}
+
+func (m *CommandSendError) GetMessage() string {
+	if m != nil && m.Message != nil {
+		return *m.Message
+	}
+	return ""
+}
+
+type CommandMessage struct {
+	ConsumerId           *uint64        `protobuf:"varint,1,req,name=consumer_id,json=consumerId" json:"consumer_id,omitempty"`
+	MessageId            *MessageIdData `protobuf:"bytes,2,req,name=message_id,json=messageId" json:"message_id,omitempty"`
+	RedeliveryCount      *uint32        `protobuf:"varint,3,opt,name=redelivery_count,json=redeliveryCount,def=0" json:"redelivery_count,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}       `json:"-"`
+	XXX_unrecognized     []byte         `json:"-"`
+	XXX_sizecache        int32          `json:"-"`
+}
+
+func (m *CommandMessage) Reset()         { *m = CommandMessage{} }
+func (m *CommandMessage) String() string { return proto.CompactTextString(m) }
+func (*CommandMessage) ProtoMessage()    {}
+func (*CommandMessage) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{18}
+}
+func (m *CommandMessage) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandMessage.Unmarshal(m, b)
+}
+func (m *CommandMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandMessage.Marshal(b, m, deterministic)
+}
+func (dst *CommandMessage) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandMessage.Merge(dst, src)
+}
+func (m *CommandMessage) XXX_Size() int {
+	return xxx_messageInfo_CommandMessage.Size(m)
+}
+func (m *CommandMessage) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandMessage.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandMessage proto.InternalMessageInfo
+
+const Default_CommandMessage_RedeliveryCount uint32 = 0
+
+func (m *CommandMessage) GetConsumerId() uint64 {
+	if m != nil && m.ConsumerId != nil {
+		return *m.ConsumerId
+	}
+	return 0
+}
+
+func (m *CommandMessage) GetMessageId() *MessageIdData {
+	if m != nil {
+		return m.MessageId
+	}
+	return nil
+}
+
+func (m *CommandMessage) GetRedeliveryCount() uint32 {
+	if m != nil && m.RedeliveryCount != nil {
+		return *m.RedeliveryCount
+	}
+	return Default_CommandMessage_RedeliveryCount
+}
+
+type CommandAck struct {
+	ConsumerId *uint64             `protobuf:"varint,1,req,name=consumer_id,json=consumerId" json:"consumer_id,omitempty"`
+	AckType    *CommandAck_AckType `protobuf:"varint,2,req,name=ack_type,json=ackType,enum=pulsar.proto.CommandAck_AckType" json:"ack_type,omitempty"`
+	// In case of individual acks, the client can pass a list of message ids
+	MessageId            []*MessageIdData            `protobuf:"bytes,3,rep,name=message_id,json=messageId" json:"message_id,omitempty"`
+	ValidationError      *CommandAck_ValidationError `protobuf:"varint,4,opt,name=validation_error,json=validationError,enum=pulsar.proto.CommandAck_ValidationError" json:"validation_error,omitempty"`
+	Properties           []*KeyLongValue             `protobuf:"bytes,5,rep,name=properties" json:"properties,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}                    `json:"-"`
+	XXX_unrecognized     []byte                      `json:"-"`
+	XXX_sizecache        int32                       `json:"-"`
+}
+
+func (m *CommandAck) Reset()         { *m = CommandAck{} }
+func (m *CommandAck) String() string { return proto.CompactTextString(m) }
+func (*CommandAck) ProtoMessage()    {}
+func (*CommandAck) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{19}
+}
+func (m *CommandAck) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandAck.Unmarshal(m, b)
+}
+func (m *CommandAck) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandAck.Marshal(b, m, deterministic)
+}
+func (dst *CommandAck) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandAck.Merge(dst, src)
+}
+func (m *CommandAck) XXX_Size() int {
+	return xxx_messageInfo_CommandAck.Size(m)
+}
+func (m *CommandAck) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandAck.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandAck proto.InternalMessageInfo
+
+func (m *CommandAck) GetConsumerId() uint64 {
+	if m != nil && m.ConsumerId != nil {
+		return *m.ConsumerId
+	}
+	return 0
+}
+
+func (m *CommandAck) GetAckType() CommandAck_AckType {
+	if m != nil && m.AckType != nil {
+		return *m.AckType
+	}
+	return CommandAck_Individual
+}
+
+func (m *CommandAck) GetMessageId() []*MessageIdData {
+	if m != nil {
+		return m.MessageId
+	}
+	return nil
+}
+
+func (m *CommandAck) GetValidationError() CommandAck_ValidationError {
+	if m != nil && m.ValidationError != nil {
+		return *m.ValidationError
+	}
+	return CommandAck_UncompressedSizeCorruption
+}
+
+func (m *CommandAck) GetProperties() []*KeyLongValue {
+	if m != nil {
+		return m.Properties
+	}
+	return nil
+}
+
+// changes on active consumer
+type CommandActiveConsumerChange struct {
+	ConsumerId           *uint64  `protobuf:"varint,1,req,name=consumer_id,json=consumerId" json:"consumer_id,omitempty"`
+	IsActive             *bool    `protobuf:"varint,2,opt,name=is_active,json=isActive,def=0" json:"is_active,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CommandActiveConsumerChange) Reset()         { *m = CommandActiveConsumerChange{} }
+func (m *CommandActiveConsumerChange) String() string { return proto.CompactTextString(m) }
+func (*CommandActiveConsumerChange) ProtoMessage()    {}
+func (*CommandActiveConsumerChange) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{20}
+}
+func (m *CommandActiveConsumerChange) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandActiveConsumerChange.Unmarshal(m, b)
+}
+func (m *CommandActiveConsumerChange) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandActiveConsumerChange.Marshal(b, m, deterministic)
+}
+func (dst *CommandActiveConsumerChange) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandActiveConsumerChange.Merge(dst, src)
+}
+func (m *CommandActiveConsumerChange) XXX_Size() int {
+	return xxx_messageInfo_CommandActiveConsumerChange.Size(m)
+}
+func (m *CommandActiveConsumerChange) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandActiveConsumerChange.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandActiveConsumerChange proto.InternalMessageInfo
+
+const Default_CommandActiveConsumerChange_IsActive bool = false
+
+func (m *CommandActiveConsumerChange) GetConsumerId() uint64 {
+	if m != nil && m.ConsumerId != nil {
+		return *m.ConsumerId
+	}
+	return 0
+}
+
+func (m *CommandActiveConsumerChange) GetIsActive() bool {
+	if m != nil && m.IsActive != nil {
+		return *m.IsActive
+	}
+	return Default_CommandActiveConsumerChange_IsActive
+}
+
+type CommandFlow struct {
+	ConsumerId *uint64 `protobuf:"varint,1,req,name=consumer_id,json=consumerId" json:"consumer_id,omitempty"`
+	// Max number of messages to prefetch, in addition
+	// of any number previously specified
+	MessagePermits       *uint32  `protobuf:"varint,2,req,name=messagePermits" json:"messagePermits,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CommandFlow) Reset()         { *m = CommandFlow{} }
+func (m *CommandFlow) String() string { return proto.CompactTextString(m) }
+func (*CommandFlow) ProtoMessage()    {}
+func (*CommandFlow) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{21}
+}
+func (m *CommandFlow) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandFlow.Unmarshal(m, b)
+}
+func (m *CommandFlow) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandFlow.Marshal(b, m, deterministic)
+}
+func (dst *CommandFlow) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandFlow.Merge(dst, src)
+}
+func (m *CommandFlow) XXX_Size() int {
+	return xxx_messageInfo_CommandFlow.Size(m)
+}
+func (m *CommandFlow) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandFlow.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandFlow proto.InternalMessageInfo
+
+func (m *CommandFlow) GetConsumerId() uint64 {
+	if m != nil && m.ConsumerId != nil {
+		return *m.ConsumerId
+	}
+	return 0
+}
+
+func (m *CommandFlow) GetMessagePermits() uint32 {
+	if m != nil && m.MessagePermits != nil {
+		return *m.MessagePermits
+	}
+	return 0
+}
+
+type CommandUnsubscribe struct {
+	ConsumerId           *uint64  `protobuf:"varint,1,req,name=consumer_id,json=consumerId" json:"consumer_id,omitempty"`
+	RequestId            *uint64  `protobuf:"varint,2,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CommandUnsubscribe) Reset()         { *m = CommandUnsubscribe{} }
+func (m *CommandUnsubscribe) String() string { return proto.CompactTextString(m) }
+func (*CommandUnsubscribe) ProtoMessage()    {}
+func (*CommandUnsubscribe) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{22}
+}
+func (m *CommandUnsubscribe) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandUnsubscribe.Unmarshal(m, b)
+}
+func (m *CommandUnsubscribe) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandUnsubscribe.Marshal(b, m, deterministic)
+}
+func (dst *CommandUnsubscribe) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandUnsubscribe.Merge(dst, src)
+}
+func (m *CommandUnsubscribe) XXX_Size() int {
+	return xxx_messageInfo_CommandUnsubscribe.Size(m)
+}
+func (m *CommandUnsubscribe) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandUnsubscribe.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandUnsubscribe proto.InternalMessageInfo
+
+func (m *CommandUnsubscribe) GetConsumerId() uint64 {
+	if m != nil && m.ConsumerId != nil {
+		return *m.ConsumerId
+	}
+	return 0
+}
+
+func (m *CommandUnsubscribe) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+// Reset an existing consumer to a particular message id
+type CommandSeek struct {
+	ConsumerId           *uint64        `protobuf:"varint,1,req,name=consumer_id,json=consumerId" json:"consumer_id,omitempty"`
+	RequestId            *uint64        `protobuf:"varint,2,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	MessageId            *MessageIdData `protobuf:"bytes,3,opt,name=message_id,json=messageId" json:"message_id,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}       `json:"-"`
+	XXX_unrecognized     []byte         `json:"-"`
+	XXX_sizecache        int32          `json:"-"`
+}
+
+func (m *CommandSeek) Reset()         { *m = CommandSeek{} }
+func (m *CommandSeek) String() string { return proto.CompactTextString(m) }
+func (*CommandSeek) ProtoMessage()    {}
+func (*CommandSeek) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{23}
+}
+func (m *CommandSeek) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandSeek.Unmarshal(m, b)
+}
+func (m *CommandSeek) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandSeek.Marshal(b, m, deterministic)
+}
+func (dst *CommandSeek) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandSeek.Merge(dst, src)
+}
+func (m *CommandSeek) XXX_Size() int {
+	return xxx_messageInfo_CommandSeek.Size(m)
+}
+func (m *CommandSeek) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandSeek.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandSeek proto.InternalMessageInfo
+
+func (m *CommandSeek) GetConsumerId() uint64 {
+	if m != nil && m.ConsumerId != nil {
+		return *m.ConsumerId
+	}
+	return 0
+}
+
+func (m *CommandSeek) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+func (m *CommandSeek) GetMessageId() *MessageIdData {
+	if m != nil {
+		return m.MessageId
+	}
+	return nil
+}
+
+// Message sent by broker to client when a topic
+// has been forcefully terminated and there are no more
+// messages left to consume
+type CommandReachedEndOfTopic struct {
+	ConsumerId           *uint64  `protobuf:"varint,1,req,name=consumer_id,json=consumerId" json:"consumer_id,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CommandReachedEndOfTopic) Reset()         { *m = CommandReachedEndOfTopic{} }
+func (m *CommandReachedEndOfTopic) String() string { return proto.CompactTextString(m) }
+func (*CommandReachedEndOfTopic) ProtoMessage()    {}
+func (*CommandReachedEndOfTopic) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{24}
+}
+func (m *CommandReachedEndOfTopic) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandReachedEndOfTopic.Unmarshal(m, b)
+}
+func (m *CommandReachedEndOfTopic) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandReachedEndOfTopic.Marshal(b, m, deterministic)
+}
+func (dst *CommandReachedEndOfTopic) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandReachedEndOfTopic.Merge(dst, src)
+}
+func (m *CommandReachedEndOfTopic) XXX_Size() int {
+	return xxx_messageInfo_CommandReachedEndOfTopic.Size(m)
+}
+func (m *CommandReachedEndOfTopic) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandReachedEndOfTopic.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandReachedEndOfTopic proto.InternalMessageInfo
+
+func (m *CommandReachedEndOfTopic) GetConsumerId() uint64 {
+	if m != nil && m.ConsumerId != nil {
+		return *m.ConsumerId
+	}
+	return 0
+}
+
+type CommandCloseProducer struct {
+	ProducerId           *uint64  `protobuf:"varint,1,req,name=producer_id,json=producerId" json:"producer_id,omitempty"`
+	RequestId            *uint64  `protobuf:"varint,2,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CommandCloseProducer) Reset()         { *m = CommandCloseProducer{} }
+func (m *CommandCloseProducer) String() string { return proto.CompactTextString(m) }
+func (*CommandCloseProducer) ProtoMessage()    {}
+func (*CommandCloseProducer) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{25}
+}
+func (m *CommandCloseProducer) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandCloseProducer.Unmarshal(m, b)
+}
+func (m *CommandCloseProducer) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandCloseProducer.Marshal(b, m, deterministic)
+}
+func (dst *CommandCloseProducer) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandCloseProducer.Merge(dst, src)
+}
+func (m *CommandCloseProducer) XXX_Size() int {
+	return xxx_messageInfo_CommandCloseProducer.Size(m)
+}
+func (m *CommandCloseProducer) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandCloseProducer.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandCloseProducer proto.InternalMessageInfo
+
+func (m *CommandCloseProducer) GetProducerId() uint64 {
+	if m != nil && m.ProducerId != nil {
+		return *m.ProducerId
+	}
+	return 0
+}
+
+func (m *CommandCloseProducer) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+type CommandCloseConsumer struct {
+	ConsumerId           *uint64  `protobuf:"varint,1,req,name=consumer_id,json=consumerId" json:"consumer_id,omitempty"`
+	RequestId            *uint64  `protobuf:"varint,2,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CommandCloseConsumer) Reset()         { *m = CommandCloseConsumer{} }
+func (m *CommandCloseConsumer) String() string { return proto.CompactTextString(m) }
+func (*CommandCloseConsumer) ProtoMessage()    {}
+func (*CommandCloseConsumer) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{26}
+}
+func (m *CommandCloseConsumer) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandCloseConsumer.Unmarshal(m, b)
+}
+func (m *CommandCloseConsumer) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandCloseConsumer.Marshal(b, m, deterministic)
+}
+func (dst *CommandCloseConsumer) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandCloseConsumer.Merge(dst, src)
+}
+func (m *CommandCloseConsumer) XXX_Size() int {
+	return xxx_messageInfo_CommandCloseConsumer.Size(m)
+}
+func (m *CommandCloseConsumer) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandCloseConsumer.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandCloseConsumer proto.InternalMessageInfo
+
+func (m *CommandCloseConsumer) GetConsumerId() uint64 {
+	if m != nil && m.ConsumerId != nil {
+		return *m.ConsumerId
+	}
+	return 0
+}
+
+func (m *CommandCloseConsumer) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+type CommandRedeliverUnacknowledgedMessages struct {
+	ConsumerId           *uint64          `protobuf:"varint,1,req,name=consumer_id,json=consumerId" json:"consumer_id,omitempty"`
+	MessageIds           []*MessageIdData `protobuf:"bytes,2,rep,name=message_ids,json=messageIds" json:"message_ids,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}         `json:"-"`
+	XXX_unrecognized     []byte           `json:"-"`
+	XXX_sizecache        int32            `json:"-"`
+}
+
+func (m *CommandRedeliverUnacknowledgedMessages) Reset() {
+	*m = CommandRedeliverUnacknowledgedMessages{}
+}
+func (m *CommandRedeliverUnacknowledgedMessages) String() string { return proto.CompactTextString(m) }
+func (*CommandRedeliverUnacknowledgedMessages) ProtoMessage()    {}
+func (*CommandRedeliverUnacknowledgedMessages) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{27}
+}
+func (m *CommandRedeliverUnacknowledgedMessages) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandRedeliverUnacknowledgedMessages.Unmarshal(m, b)
+}
+func (m *CommandRedeliverUnacknowledgedMessages) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandRedeliverUnacknowledgedMessages.Marshal(b, m, deterministic)
+}
+func (dst *CommandRedeliverUnacknowledgedMessages) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandRedeliverUnacknowledgedMessages.Merge(dst, src)
+}
+func (m *CommandRedeliverUnacknowledgedMessages) XXX_Size() int {
+	return xxx_messageInfo_CommandRedeliverUnacknowledgedMessages.Size(m)
+}
+func (m *CommandRedeliverUnacknowledgedMessages) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandRedeliverUnacknowledgedMessages.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandRedeliverUnacknowledgedMessages proto.InternalMessageInfo
+
+func (m *CommandRedeliverUnacknowledgedMessages) GetConsumerId() uint64 {
+	if m != nil && m.ConsumerId != nil {
+		return *m.ConsumerId
+	}
+	return 0
+}
+
+func (m *CommandRedeliverUnacknowledgedMessages) GetMessageIds() []*MessageIdData {
+	if m != nil {
+		return m.MessageIds
+	}
+	return nil
+}
+
+type CommandSuccess struct {
+	RequestId            *uint64  `protobuf:"varint,1,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	Schema               *Schema  `protobuf:"bytes,2,opt,name=schema" json:"schema,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CommandSuccess) Reset()         { *m = CommandSuccess{} }
+func (m *CommandSuccess) String() string { return proto.CompactTextString(m) }
+func (*CommandSuccess) ProtoMessage()    {}
+func (*CommandSuccess) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{28}
+}
+func (m *CommandSuccess) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandSuccess.Unmarshal(m, b)
+}
+func (m *CommandSuccess) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandSuccess.Marshal(b, m, deterministic)
+}
+func (dst *CommandSuccess) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandSuccess.Merge(dst, src)
+}
+func (m *CommandSuccess) XXX_Size() int {
+	return xxx_messageInfo_CommandSuccess.Size(m)
+}
+func (m *CommandSuccess) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandSuccess.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandSuccess proto.InternalMessageInfo
+
+func (m *CommandSuccess) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+func (m *CommandSuccess) GetSchema() *Schema {
+	if m != nil {
+		return m.Schema
+	}
+	return nil
+}
+
+// / Response from CommandProducer
+type CommandProducerSuccess struct {
+	RequestId    *uint64 `protobuf:"varint,1,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	ProducerName *string `protobuf:"bytes,2,req,name=producer_name,json=producerName" json:"producer_name,omitempty"`
+	// The last sequence id that was stored by this producer in the previous session
+	// This will only be meaningful if deduplication has been enabled.
+	LastSequenceId       *int64   `protobuf:"varint,3,opt,name=last_sequence_id,json=lastSequenceId,def=-1" json:"last_sequence_id,omitempty"`
+	SchemaVersion        []byte   `protobuf:"bytes,4,opt,name=schema_version,json=schemaVersion" json:"schema_version,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CommandProducerSuccess) Reset()         { *m = CommandProducerSuccess{} }
+func (m *CommandProducerSuccess) String() string { return proto.CompactTextString(m) }
+func (*CommandProducerSuccess) ProtoMessage()    {}
+func (*CommandProducerSuccess) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{29}
+}
+func (m *CommandProducerSuccess) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandProducerSuccess.Unmarshal(m, b)
+}
+func (m *CommandProducerSuccess) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandProducerSuccess.Marshal(b, m, deterministic)
+}
+func (dst *CommandProducerSuccess) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandProducerSuccess.Merge(dst, src)
+}
+func (m *CommandProducerSuccess) XXX_Size() int {
+	return xxx_messageInfo_CommandProducerSuccess.Size(m)
+}
+func (m *CommandProducerSuccess) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandProducerSuccess.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandProducerSuccess proto.InternalMessageInfo
+
+const Default_CommandProducerSuccess_LastSequenceId int64 = -1
+
+func (m *CommandProducerSuccess) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+func (m *CommandProducerSuccess) GetProducerName() string {
+	if m != nil && m.ProducerName != nil {
+		return *m.ProducerName
+	}
+	return ""
+}
+
+func (m *CommandProducerSuccess) GetLastSequenceId() int64 {
+	if m != nil && m.LastSequenceId != nil {
+		return *m.LastSequenceId
+	}
+	return Default_CommandProducerSuccess_LastSequenceId
+}
+
+func (m *CommandProducerSuccess) GetSchemaVersion() []byte {
+	if m != nil {
+		return m.SchemaVersion
+	}
+	return nil
+}
+
+type CommandError struct {
+	RequestId            *uint64      `protobuf:"varint,1,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	Error                *ServerError `protobuf:"varint,2,req,name=error,enum=pulsar.proto.ServerError" json:"error,omitempty"`
+	Message              *string      `protobuf:"bytes,3,req,name=message" json:"message,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}     `json:"-"`
+	XXX_unrecognized     []byte       `json:"-"`
+	XXX_sizecache        int32        `json:"-"`
+}
+
+func (m *CommandError) Reset()         { *m = CommandError{} }
+func (m *CommandError) String() string { return proto.CompactTextString(m) }
+func (*CommandError) ProtoMessage()    {}
+func (*CommandError) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{30}
+}
+func (m *CommandError) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandError.Unmarshal(m, b)
+}
+func (m *CommandError) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandError.Marshal(b, m, deterministic)
+}
+func (dst *CommandError) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandError.Merge(dst, src)
+}
+func (m *CommandError) XXX_Size() int {
+	return xxx_messageInfo_CommandError.Size(m)
+}
+func (m *CommandError) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandError.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandError proto.InternalMessageInfo
+
+func (m *CommandError) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+func (m *CommandError) GetError() ServerError {
+	if m != nil && m.Error != nil {
+		return *m.Error
+	}
+	return ServerError_UnknownError
+}
+
+func (m *CommandError) GetMessage() string {
+	if m != nil && m.Message != nil {
+		return *m.Message
+	}
+	return ""
+}
+
+// Commands to probe the state of connection.
+// When either client or broker doesn't receive commands for certain
+// amount of time, they will send a Ping probe.
+type CommandPing struct {
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CommandPing) Reset()         { *m = CommandPing{} }
+func (m *CommandPing) String() string { return proto.CompactTextString(m) }
+func (*CommandPing) ProtoMessage()    {}
+func (*CommandPing) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{31}
+}
+func (m *CommandPing) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandPing.Unmarshal(m, b)
+}
+func (m *CommandPing) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandPing.Marshal(b, m, deterministic)
+}
+func (dst *CommandPing) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandPing.Merge(dst, src)
+}
+func (m *CommandPing) XXX_Size() int {
+	return xxx_messageInfo_CommandPing.Size(m)
+}
+func (m *CommandPing) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandPing.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandPing proto.InternalMessageInfo
+
+type CommandPong struct {
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CommandPong) Reset()         { *m = CommandPong{} }
+func (m *CommandPong) String() string { return proto.CompactTextString(m) }
+func (*CommandPong) ProtoMessage()    {}
+func (*CommandPong) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{32}
+}
+func (m *CommandPong) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandPong.Unmarshal(m, b)
+}
+func (m *CommandPong) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandPong.Marshal(b, m, deterministic)
+}
+func (dst *CommandPong) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandPong.Merge(dst, src)
+}
+func (m *CommandPong) XXX_Size() int {
+	return xxx_messageInfo_CommandPong.Size(m)
+}
+func (m *CommandPong) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandPong.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandPong proto.InternalMessageInfo
+
+type CommandConsumerStats struct {
+	RequestId *uint64 `protobuf:"varint,1,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	// required string topic_name         = 2;
+	// required string subscription_name  = 3;
+	ConsumerId           *uint64  `protobuf:"varint,4,req,name=consumer_id,json=consumerId" json:"consumer_id,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CommandConsumerStats) Reset()         { *m = CommandConsumerStats{} }
+func (m *CommandConsumerStats) String() string { return proto.CompactTextString(m) }
+func (*CommandConsumerStats) ProtoMessage()    {}
+func (*CommandConsumerStats) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{33}
+}
+func (m *CommandConsumerStats) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandConsumerStats.Unmarshal(m, b)
+}
+func (m *CommandConsumerStats) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandConsumerStats.Marshal(b, m, deterministic)
+}
+func (dst *CommandConsumerStats) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandConsumerStats.Merge(dst, src)
+}
+func (m *CommandConsumerStats) XXX_Size() int {
+	return xxx_messageInfo_CommandConsumerStats.Size(m)
+}
+func (m *CommandConsumerStats) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandConsumerStats.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandConsumerStats proto.InternalMessageInfo
+
+func (m *CommandConsumerStats) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+func (m *CommandConsumerStats) GetConsumerId() uint64 {
+	if m != nil && m.ConsumerId != nil {
+		return *m.ConsumerId
+	}
+	return 0
+}
+
+type CommandConsumerStatsResponse struct {
+	RequestId    *uint64      `protobuf:"varint,1,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	ErrorCode    *ServerError `protobuf:"varint,2,opt,name=error_code,json=errorCode,enum=pulsar.proto.ServerError" json:"error_code,omitempty"`
+	ErrorMessage *string      `protobuf:"bytes,3,opt,name=error_message,json=errorMessage" json:"error_message,omitempty"`
+	// / Total rate of messages delivered to the consumer. msg/s
+	MsgRateOut *float64 `protobuf:"fixed64,4,opt,name=msgRateOut" json:"msgRateOut,omitempty"`
+	// / Total throughput delivered to the consumer. bytes/s
+	MsgThroughputOut *float64 `protobuf:"fixed64,5,opt,name=msgThroughputOut" json:"msgThroughputOut,omitempty"`
+	// / Total rate of messages redelivered by this consumer. msg/s
+	MsgRateRedeliver *float64 `protobuf:"fixed64,6,opt,name=msgRateRedeliver" json:"msgRateRedeliver,omitempty"`
+	// / Name of the consumer
+	ConsumerName *string `protobuf:"bytes,7,opt,name=consumerName" json:"consumerName,omitempty"`
+	// / Number of available message permits for the consumer
+	AvailablePermits *uint64 `protobuf:"varint,8,opt,name=availablePermits" json:"availablePermits,omitempty"`
+	// / Number of unacknowledged messages for the consumer
+	UnackedMessages *uint64 `protobuf:"varint,9,opt,name=unackedMessages" json:"unackedMessages,omitempty"`
+	// / Flag to verify if consumer is blocked due to reaching threshold of unacked messages
+	BlockedConsumerOnUnackedMsgs *bool `protobuf:"varint,10,opt,name=blockedConsumerOnUnackedMsgs" json:"blockedConsumerOnUnackedMsgs,omitempty"`
+	// / Address of this consumer
+	Address *string `protobuf:"bytes,11,opt,name=address" json:"address,omitempty"`
+	// / Timestamp of connection
+	ConnectedSince *string `protobuf:"bytes,12,opt,name=connectedSince" json:"connectedSince,omitempty"`
+	// / Whether this subscription is Exclusive or Shared or Failover
+	Type *string `protobuf:"bytes,13,opt,name=type" json:"type,omitempty"`
+	// / Total rate of messages expired on this subscription. msg/s
+	MsgRateExpired *float64 `protobuf:"fixed64,14,opt,name=msgRateExpired" json:"msgRateExpired,omitempty"`
+	// / Number of messages in the subscription backlog
+	MsgBacklog           *uint64  `protobuf:"varint,15,opt,name=msgBacklog" json:"msgBacklog,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CommandConsumerStatsResponse) Reset()         { *m = CommandConsumerStatsResponse{} }
+func (m *CommandConsumerStatsResponse) String() string { return proto.CompactTextString(m) }
+func (*CommandConsumerStatsResponse) ProtoMessage()    {}
+func (*CommandConsumerStatsResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{34}
+}
+func (m *CommandConsumerStatsResponse) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandConsumerStatsResponse.Unmarshal(m, b)
+}
+func (m *CommandConsumerStatsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandConsumerStatsResponse.Marshal(b, m, deterministic)
+}
+func (dst *CommandConsumerStatsResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandConsumerStatsResponse.Merge(dst, src)
+}
+func (m *CommandConsumerStatsResponse) XXX_Size() int {
+	return xxx_messageInfo_CommandConsumerStatsResponse.Size(m)
+}
+func (m *CommandConsumerStatsResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandConsumerStatsResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandConsumerStatsResponse proto.InternalMessageInfo
+
+func (m *CommandConsumerStatsResponse) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+func (m *CommandConsumerStatsResponse) GetErrorCode() ServerError {
+	if m != nil && m.ErrorCode != nil {
+		return *m.ErrorCode
+	}
+	return ServerError_UnknownError
+}
+
+func (m *CommandConsumerStatsResponse) GetErrorMessage() string {
+	if m != nil && m.ErrorMessage != nil {
+		return *m.ErrorMessage
+	}
+	return ""
+}
+
+func (m *CommandConsumerStatsResponse) GetMsgRateOut() float64 {
+	if m != nil && m.MsgRateOut != nil {
+		return *m.MsgRateOut
+	}
+	return 0
+}
+
+func (m *CommandConsumerStatsResponse) GetMsgThroughputOut() float64 {
+	if m != nil && m.MsgThroughputOut != nil {
+		return *m.MsgThroughputOut
+	}
+	return 0
+}
+
+func (m *CommandConsumerStatsResponse) GetMsgRateRedeliver() float64 {
+	if m != nil && m.MsgRateRedeliver != nil {
+		return *m.MsgRateRedeliver
+	}
+	return 0
+}
+
+func (m *CommandConsumerStatsResponse) GetConsumerName() string {
+	if m != nil && m.ConsumerName != nil {
+		return *m.ConsumerName
+	}
+	return ""
+}
+
+func (m *CommandConsumerStatsResponse) GetAvailablePermits() uint64 {
+	if m != nil && m.AvailablePermits != nil {
+		return *m.AvailablePermits
+	}
+	return 0
+}
+
+func (m *CommandConsumerStatsResponse) GetUnackedMessages() uint64 {
+	if m != nil && m.UnackedMessages != nil {
+		return *m.UnackedMessages
+	}
+	return 0
+}
+
+func (m *CommandConsumerStatsResponse) GetBlockedConsumerOnUnackedMsgs() bool {
+	if m != nil && m.BlockedConsumerOnUnackedMsgs != nil {
+		return *m.BlockedConsumerOnUnackedMsgs
+	}
+	return false
+}
+
+func (m *CommandConsumerStatsResponse) GetAddress() string {
+	if m != nil && m.Address != nil {
+		return *m.Address
+	}
+	return ""
+}
+
+func (m *CommandConsumerStatsResponse) GetConnectedSince() string {
+	if m != nil && m.ConnectedSince != nil {
+		return *m.ConnectedSince
+	}
+	return ""
+}
+
+func (m *CommandConsumerStatsResponse) GetType() string {
+	if m != nil && m.Type != nil {
+		return *m.Type
+	}
+	return ""
+}
+
+func (m *CommandConsumerStatsResponse) GetMsgRateExpired() float64 {
+	if m != nil && m.MsgRateExpired != nil {
+		return *m.MsgRateExpired
+	}
+	return 0
+}
+
+func (m *CommandConsumerStatsResponse) GetMsgBacklog() uint64 {
+	if m != nil && m.MsgBacklog != nil {
+		return *m.MsgBacklog
+	}
+	return 0
+}
+
+type CommandGetLastMessageId struct {
+	ConsumerId           *uint64  `protobuf:"varint,1,req,name=consumer_id,json=consumerId" json:"consumer_id,omitempty"`
+	RequestId            *uint64  `protobuf:"varint,2,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CommandGetLastMessageId) Reset()         { *m = CommandGetLastMessageId{} }
+func (m *CommandGetLastMessageId) String() string { return proto.CompactTextString(m) }
+func (*CommandGetLastMessageId) ProtoMessage()    {}
+func (*CommandGetLastMessageId) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{35}
+}
+func (m *CommandGetLastMessageId) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandGetLastMessageId.Unmarshal(m, b)
+}
+func (m *CommandGetLastMessageId) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandGetLastMessageId.Marshal(b, m, deterministic)
+}
+func (dst *CommandGetLastMessageId) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandGetLastMessageId.Merge(dst, src)
+}
+func (m *CommandGetLastMessageId) XXX_Size() int {
+	return xxx_messageInfo_CommandGetLastMessageId.Size(m)
+}
+func (m *CommandGetLastMessageId) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandGetLastMessageId.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandGetLastMessageId proto.InternalMessageInfo
+
+func (m *CommandGetLastMessageId) GetConsumerId() uint64 {
+	if m != nil && m.ConsumerId != nil {
+		return *m.ConsumerId
+	}
+	return 0
+}
+
+func (m *CommandGetLastMessageId) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+type CommandGetLastMessageIdResponse struct {
+	LastMessageId        *MessageIdData `protobuf:"bytes,1,req,name=last_message_id,json=lastMessageId" json:"last_message_id,omitempty"`
+	RequestId            *uint64        `protobuf:"varint,2,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}       `json:"-"`
+	XXX_unrecognized     []byte         `json:"-"`
+	XXX_sizecache        int32          `json:"-"`
+}
+
+func (m *CommandGetLastMessageIdResponse) Reset()         { *m = CommandGetLastMessageIdResponse{} }
+func (m *CommandGetLastMessageIdResponse) String() string { return proto.CompactTextString(m) }
+func (*CommandGetLastMessageIdResponse) ProtoMessage()    {}
+func (*CommandGetLastMessageIdResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{36}
+}
+func (m *CommandGetLastMessageIdResponse) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandGetLastMessageIdResponse.Unmarshal(m, b)
+}
+func (m *CommandGetLastMessageIdResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandGetLastMessageIdResponse.Marshal(b, m, deterministic)
+}
+func (dst *CommandGetLastMessageIdResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandGetLastMessageIdResponse.Merge(dst, src)
+}
+func (m *CommandGetLastMessageIdResponse) XXX_Size() int {
+	return xxx_messageInfo_CommandGetLastMessageIdResponse.Size(m)
+}
+func (m *CommandGetLastMessageIdResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandGetLastMessageIdResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandGetLastMessageIdResponse proto.InternalMessageInfo
+
+func (m *CommandGetLastMessageIdResponse) GetLastMessageId() *MessageIdData {
+	if m != nil {
+		return m.LastMessageId
+	}
+	return nil
+}
+
+func (m *CommandGetLastMessageIdResponse) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+type CommandGetTopicsOfNamespace struct {
+	RequestId            *uint64                           `protobuf:"varint,1,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	Namespace            *string                           `protobuf:"bytes,2,req,name=namespace" json:"namespace,omitempty"`
+	Mode                 *CommandGetTopicsOfNamespace_Mode `protobuf:"varint,3,opt,name=mode,enum=pulsar.proto.CommandGetTopicsOfNamespace_Mode,def=0" json:"mode,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}                          `json:"-"`
+	XXX_unrecognized     []byte                            `json:"-"`
+	XXX_sizecache        int32                             `json:"-"`
+}
+
+func (m *CommandGetTopicsOfNamespace) Reset()         { *m = CommandGetTopicsOfNamespace{} }
+func (m *CommandGetTopicsOfNamespace) String() string { return proto.CompactTextString(m) }
+func (*CommandGetTopicsOfNamespace) ProtoMessage()    {}
+func (*CommandGetTopicsOfNamespace) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{37}
+}
+func (m *CommandGetTopicsOfNamespace) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandGetTopicsOfNamespace.Unmarshal(m, b)
+}
+func (m *CommandGetTopicsOfNamespace) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandGetTopicsOfNamespace.Marshal(b, m, deterministic)
+}
+func (dst *CommandGetTopicsOfNamespace) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandGetTopicsOfNamespace.Merge(dst, src)
+}
+func (m *CommandGetTopicsOfNamespace) XXX_Size() int {
+	return xxx_messageInfo_CommandGetTopicsOfNamespace.Size(m)
+}
+func (m *CommandGetTopicsOfNamespace) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandGetTopicsOfNamespace.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandGetTopicsOfNamespace proto.InternalMessageInfo
+
+const Default_CommandGetTopicsOfNamespace_Mode CommandGetTopicsOfNamespace_Mode = CommandGetTopicsOfNamespace_PERSISTENT
+
+func (m *CommandGetTopicsOfNamespace) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+func (m *CommandGetTopicsOfNamespace) GetNamespace() string {
+	if m != nil && m.Namespace != nil {
+		return *m.Namespace
+	}
+	return ""
+}
+
+func (m *CommandGetTopicsOfNamespace) GetMode() CommandGetTopicsOfNamespace_Mode {
+	if m != nil && m.Mode != nil {
+		return *m.Mode
+	}
+	return Default_CommandGetTopicsOfNamespace_Mode
+}
+
+type CommandGetTopicsOfNamespaceResponse struct {
+	RequestId            *uint64  `protobuf:"varint,1,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	Topics               []string `protobuf:"bytes,2,rep,name=topics" json:"topics,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CommandGetTopicsOfNamespaceResponse) Reset()         { *m = CommandGetTopicsOfNamespaceResponse{} }
+func (m *CommandGetTopicsOfNamespaceResponse) String() string { return proto.CompactTextString(m) }
+func (*CommandGetTopicsOfNamespaceResponse) ProtoMessage()    {}
+func (*CommandGetTopicsOfNamespaceResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{38}
+}
+func (m *CommandGetTopicsOfNamespaceResponse) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandGetTopicsOfNamespaceResponse.Unmarshal(m, b)
+}
+func (m *CommandGetTopicsOfNamespaceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandGetTopicsOfNamespaceResponse.Marshal(b, m, deterministic)
+}
+func (dst *CommandGetTopicsOfNamespaceResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandGetTopicsOfNamespaceResponse.Merge(dst, src)
+}
+func (m *CommandGetTopicsOfNamespaceResponse) XXX_Size() int {
+	return xxx_messageInfo_CommandGetTopicsOfNamespaceResponse.Size(m)
+}
+func (m *CommandGetTopicsOfNamespaceResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandGetTopicsOfNamespaceResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandGetTopicsOfNamespaceResponse proto.InternalMessageInfo
+
+func (m *CommandGetTopicsOfNamespaceResponse) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+func (m *CommandGetTopicsOfNamespaceResponse) GetTopics() []string {
+	if m != nil {
+		return m.Topics
+	}
+	return nil
+}
+
+type CommandGetSchema struct {
+	RequestId            *uint64  `protobuf:"varint,1,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	Topic                *string  `protobuf:"bytes,2,req,name=topic" json:"topic,omitempty"`
+	SchemaVersion        []byte   `protobuf:"bytes,3,opt,name=schema_version,json=schemaVersion" json:"schema_version,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CommandGetSchema) Reset()         { *m = CommandGetSchema{} }
+func (m *CommandGetSchema) String() string { return proto.CompactTextString(m) }
+func (*CommandGetSchema) ProtoMessage()    {}
+func (*CommandGetSchema) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{39}
+}
+func (m *CommandGetSchema) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandGetSchema.Unmarshal(m, b)
+}
+func (m *CommandGetSchema) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandGetSchema.Marshal(b, m, deterministic)
+}
+func (dst *CommandGetSchema) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandGetSchema.Merge(dst, src)
+}
+func (m *CommandGetSchema) XXX_Size() int {
+	return xxx_messageInfo_CommandGetSchema.Size(m)
+}
+func (m *CommandGetSchema) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandGetSchema.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandGetSchema proto.InternalMessageInfo
+
+func (m *CommandGetSchema) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+func (m *CommandGetSchema) GetTopic() string {
+	if m != nil && m.Topic != nil {
+		return *m.Topic
+	}
+	return ""
+}
+
+func (m *CommandGetSchema) GetSchemaVersion() []byte {
+	if m != nil {
+		return m.SchemaVersion
+	}
+	return nil
+}
+
+type CommandGetSchemaResponse struct {
+	RequestId            *uint64      `protobuf:"varint,1,req,name=request_id,json=requestId" json:"request_id,omitempty"`
+	ErrorCode            *ServerError `protobuf:"varint,2,opt,name=error_code,json=errorCode,enum=pulsar.proto.ServerError" json:"error_code,omitempty"`
+	ErrorMessage         *string      `protobuf:"bytes,3,opt,name=error_message,json=errorMessage" json:"error_message,omitempty"`
+	Schema               *Schema      `protobuf:"bytes,4,opt,name=schema" json:"schema,omitempty"`
+	SchemaVersion        []byte       `protobuf:"bytes,5,opt,name=schema_version,json=schemaVersion" json:"schema_version,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}     `json:"-"`
+	XXX_unrecognized     []byte       `json:"-"`
+	XXX_sizecache        int32        `json:"-"`
+}
+
+func (m *CommandGetSchemaResponse) Reset()         { *m = CommandGetSchemaResponse{} }
+func (m *CommandGetSchemaResponse) String() string { return proto.CompactTextString(m) }
+func (*CommandGetSchemaResponse) ProtoMessage()    {}
+func (*CommandGetSchemaResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{40}
+}
+func (m *CommandGetSchemaResponse) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CommandGetSchemaResponse.Unmarshal(m, b)
+}
+func (m *CommandGetSchemaResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CommandGetSchemaResponse.Marshal(b, m, deterministic)
+}
+func (dst *CommandGetSchemaResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CommandGetSchemaResponse.Merge(dst, src)
+}
+func (m *CommandGetSchemaResponse) XXX_Size() int {
+	return xxx_messageInfo_CommandGetSchemaResponse.Size(m)
+}
+func (m *CommandGetSchemaResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_CommandGetSchemaResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CommandGetSchemaResponse proto.InternalMessageInfo
+
+func (m *CommandGetSchemaResponse) GetRequestId() uint64 {
+	if m != nil && m.RequestId != nil {
+		return *m.RequestId
+	}
+	return 0
+}
+
+func (m *CommandGetSchemaResponse) GetErrorCode() ServerError {
+	if m != nil && m.ErrorCode != nil {
+		return *m.ErrorCode
+	}
+	return ServerError_UnknownError
+}
+
+func (m *CommandGetSchemaResponse) GetErrorMessage() string {
+	if m != nil && m.ErrorMessage != nil {
+		return *m.ErrorMessage
+	}
+	return ""
+}
+
+func (m *CommandGetSchemaResponse) GetSchema() *Schema {
+	if m != nil {
+		return m.Schema
+	}
+	return nil
+}
+
+func (m *CommandGetSchemaResponse) GetSchemaVersion() []byte {
+	if m != nil {
+		return m.SchemaVersion
+	}
+	return nil
+}
+
+type BaseCommand struct {
+	Type                            *BaseCommand_Type                        `protobuf:"varint,1,req,name=type,enum=pulsar.proto.BaseCommand_Type" json:"type,omitempty"`
+	Connect                         *CommandConnect                          `protobuf:"bytes,2,opt,name=connect" json:"connect,omitempty"`
+	Connected                       *CommandConnected                        `protobuf:"bytes,3,opt,name=connected" json:"connected,omitempty"`
+	Subscribe                       *CommandSubscribe                        `protobuf:"bytes,4,opt,name=subscribe" json:"subscribe,omitempty"`
+	Producer                        *CommandProducer                         `protobuf:"bytes,5,opt,name=producer" json:"producer,omitempty"`
+	Send                            *CommandSend                             `protobuf:"bytes,6,opt,name=send" json:"send,omitempty"`
+	SendReceipt                     *CommandSendReceipt                      `protobuf:"bytes,7,opt,name=send_receipt,json=sendReceipt" json:"send_receipt,omitempty"`
+	SendError                       *CommandSendError                        `protobuf:"bytes,8,opt,name=send_error,json=sendError" json:"send_error,omitempty"`
+	Message                         *CommandMessage                          `protobuf:"bytes,9,opt,name=message" json:"message,omitempty"`
+	Ack                             *CommandAck                              `protobuf:"bytes,10,opt,name=ack" json:"ack,omitempty"`
+	Flow                            *CommandFlow                             `protobuf:"bytes,11,opt,name=flow" json:"flow,omitempty"`
+	Unsubscribe                     *CommandUnsubscribe                      `protobuf:"bytes,12,opt,name=unsubscribe" json:"unsubscribe,omitempty"`
+	Success                         *CommandSuccess                          `protobuf:"bytes,13,opt,name=success" json:"success,omitempty"`
+	Error                           *CommandError                            `protobuf:"bytes,14,opt,name=error" json:"error,omitempty"`
+	CloseProducer                   *CommandCloseProducer                    `protobuf:"bytes,15,opt,name=close_producer,json=closeProducer" json:"close_producer,omitempty"`
+	CloseConsumer                   *CommandCloseConsumer                    `protobuf:"bytes,16,opt,name=close_consumer,json=closeConsumer" json:"close_consumer,omitempty"`
+	ProducerSuccess                 *CommandProducerSuccess                  `protobuf:"bytes,17,opt,name=producer_success,json=producerSuccess" json:"producer_success,omitempty"`
+	Ping                            *CommandPing                             `protobuf:"bytes,18,opt,name=ping" json:"ping,omitempty"`
+	Pong                            *CommandPong                             `protobuf:"bytes,19,opt,name=pong" json:"pong,omitempty"`
+	RedeliverUnacknowledgedMessages *CommandRedeliverUnacknowledgedMessages  `protobuf:"bytes,20,opt,name=redeliverUnacknowledgedMessages" json:"redeliverUnacknowledgedMessages,omitempty"`
+	PartitionMetadata               *CommandPartitionedTopicMetadata         `protobuf:"bytes,21,opt,name=partitionMetadata" json:"partitionMetadata,omitempty"`
+	PartitionMetadataResponse       *CommandPartitionedTopicMetadataResponse `protobuf:"bytes,22,opt,name=partitionMetadataResponse" json:"partitionMetadataResponse,omitempty"`
+	LookupTopic                     *CommandLookupTopic                      `protobuf:"bytes,23,opt,name=lookupTopic" json:"lookupTopic,omitempty"`
+	LookupTopicResponse             *CommandLookupTopicResponse              `protobuf:"bytes,24,opt,name=lookupTopicResponse" json:"lookupTopicResponse,omitempty"`
+	ConsumerStats                   *CommandConsumerStats                    `protobuf:"bytes,25,opt,name=consumerStats" json:"consumerStats,omitempty"`
+	ConsumerStatsResponse           *CommandConsumerStatsResponse            `protobuf:"bytes,26,opt,name=consumerStatsResponse" json:"consumerStatsResponse,omitempty"`
+	ReachedEndOfTopic               *CommandReachedEndOfTopic                `protobuf:"bytes,27,opt,name=reachedEndOfTopic" json:"reachedEndOfTopic,omitempty"`
+	Seek                            *CommandSeek                             `protobuf:"bytes,28,opt,name=seek" json:"seek,omitempty"`
+	GetLastMessageId                *CommandGetLastMessageId                 `protobuf:"bytes,29,opt,name=getLastMessageId" json:"getLastMessageId,omitempty"`
+	GetLastMessageIdResponse        *CommandGetLastMessageIdResponse         `protobuf:"bytes,30,opt,name=getLastMessageIdResponse" json:"getLastMessageIdResponse,omitempty"`
+	ActiveConsumerChange            *CommandActiveConsumerChange             `protobuf:"bytes,31,opt,name=active_consumer_change,json=activeConsumerChange" json:"active_consumer_change,omitempty"`
+	GetTopicsOfNamespace            *CommandGetTopicsOfNamespace             `protobuf:"bytes,32,opt,name=getTopicsOfNamespace" json:"getTopicsOfNamespace,omitempty"`
+	GetTopicsOfNamespaceResponse    *CommandGetTopicsOfNamespaceResponse     `protobuf:"bytes,33,opt,name=getTopicsOfNamespaceResponse" json:"getTopicsOfNamespaceResponse,omitempty"`
+	GetSchema                       *CommandGetSchema                        `protobuf:"bytes,34,opt,name=getSchema" json:"getSchema,omitempty"`
+	GetSchemaResponse               *CommandGetSchemaResponse                `protobuf:"bytes,35,opt,name=getSchemaResponse" json:"getSchemaResponse,omitempty"`
+	XXX_NoUnkeyedLiteral            struct{}                                 `json:"-"`
+	XXX_unrecognized                []byte                                   `json:"-"`
+	XXX_sizecache                   int32                                    `json:"-"`
+}
+
+func (m *BaseCommand) Reset()         { *m = BaseCommand{} }
+func (m *BaseCommand) String() string { return proto.CompactTextString(m) }
+func (*BaseCommand) ProtoMessage()    {}
+func (*BaseCommand) Descriptor() ([]byte, []int) {
+	return fileDescriptor_PulsarApi_9ff3bd5e091a4809, []int{41}
+}
+func (m *BaseCommand) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_BaseCommand.Unmarshal(m, b)
+}
+func (m *BaseCommand) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_BaseCommand.Marshal(b, m, deterministic)
+}
+func (dst *BaseCommand) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_BaseCommand.Merge(dst, src)
+}
+func (m *BaseCommand) XXX_Size() int {
+	return xxx_messageInfo_BaseCommand.Size(m)
+}
+func (m *BaseCommand) XXX_DiscardUnknown() {
+	xxx_messageInfo_BaseCommand.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_BaseCommand proto.InternalMessageInfo
+
+func (m *BaseCommand) GetType() BaseCommand_Type {
+	if m != nil && m.Type != nil {
+		return *m.Type
+	}
+	return BaseCommand_CONNECT
+}
+
+func (m *BaseCommand) GetConnect() *CommandConnect {
+	if m != nil {
+		return m.Connect
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetConnected() *CommandConnected {
+	if m != nil {
+		return m.Connected
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetSubscribe() *CommandSubscribe {
+	if m != nil {
+		return m.Subscribe
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetProducer() *CommandProducer {
+	if m != nil {
+		return m.Producer
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetSend() *CommandSend {
+	if m != nil {
+		return m.Send
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetSendReceipt() *CommandSendReceipt {
+	if m != nil {
+		return m.SendReceipt
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetSendError() *CommandSendError {
+	if m != nil {
+		return m.SendError
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetMessage() *CommandMessage {
+	if m != nil {
+		return m.Message
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetAck() *CommandAck {
+	if m != nil {
+		return m.Ack
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetFlow() *CommandFlow {
+	if m != nil {
+		return m.Flow
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetUnsubscribe() *CommandUnsubscribe {
+	if m != nil {
+		return m.Unsubscribe
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetSuccess() *CommandSuccess {
+	if m != nil {
+		return m.Success
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetError() *CommandError {
+	if m != nil {
+		return m.Error
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetCloseProducer() *CommandCloseProducer {
+	if m != nil {
+		return m.CloseProducer
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetCloseConsumer() *CommandCloseConsumer {
+	if m != nil {
+		return m.CloseConsumer
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetProducerSuccess() *CommandProducerSuccess {
+	if m != nil {
+		return m.ProducerSuccess
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetPing() *CommandPing {
+	if m != nil {
+		return m.Ping
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetPong() *CommandPong {
+	if m != nil {
+		return m.Pong
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetRedeliverUnacknowledgedMessages() *CommandRedeliverUnacknowledgedMessages {
+	if m != nil {
+		return m.RedeliverUnacknowledgedMessages
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetPartitionMetadata() *CommandPartitionedTopicMetadata {
+	if m != nil {
+		return m.PartitionMetadata
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetPartitionMetadataResponse() *CommandPartitionedTopicMetadataResponse {
+	if m != nil {
+		return m.PartitionMetadataResponse
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetLookupTopic() *CommandLookupTopic {
+	if m != nil {
+		return m.LookupTopic
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetLookupTopicResponse() *CommandLookupTopicResponse {
+	if m != nil {
+		return m.LookupTopicResponse
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetConsumerStats() *CommandConsumerStats {
+	if m != nil {
+		return m.ConsumerStats
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetConsumerStatsResponse() *CommandConsumerStatsResponse {
+	if m != nil {
+		return m.ConsumerStatsResponse
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetReachedEndOfTopic() *CommandReachedEndOfTopic {
+	if m != nil {
+		return m.ReachedEndOfTopic
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetSeek() *CommandSeek {
+	if m != nil {
+		return m.Seek
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetGetLastMessageId() *CommandGetLastMessageId {
+	if m != nil {
+		return m.GetLastMessageId
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetGetLastMessageIdResponse() *CommandGetLastMessageIdResponse {
+	if m != nil {
+		return m.GetLastMessageIdResponse
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetActiveConsumerChange() *CommandActiveConsumerChange {
+	if m != nil {
+		return m.ActiveConsumerChange
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetGetTopicsOfNamespace() *CommandGetTopicsOfNamespace {
+	if m != nil {
+		return m.GetTopicsOfNamespace
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetGetTopicsOfNamespaceResponse() *CommandGetTopicsOfNamespaceResponse {
+	if m != nil {
+		return m.GetTopicsOfNamespaceResponse
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetGetSchema() *CommandGetSchema {
+	if m != nil {
+		return m.GetSchema
+	}
+	return nil
+}
+
+func (m *BaseCommand) GetGetSchemaResponse() *CommandGetSchemaResponse {
+	if m != nil {
+		return m.GetSchemaResponse
+	}
+	return nil
+}
+
+func init() {
+	proto.RegisterType((*Schema)(nil), "pulsar.proto.Schema")
+	proto.RegisterType((*MessageIdData)(nil), "pulsar.proto.MessageIdData")
+	proto.RegisterType((*KeyValue)(nil), "pulsar.proto.KeyValue")
+	proto.RegisterType((*KeyLongValue)(nil), "pulsar.proto.KeyLongValue")
+	proto.RegisterType((*EncryptionKeys)(nil), "pulsar.proto.EncryptionKeys")
+	proto.RegisterType((*MessageMetadata)(nil), "pulsar.proto.MessageMetadata")
+	proto.RegisterType((*SingleMessageMetadata)(nil), "pulsar.proto.SingleMessageMetadata")
+	proto.RegisterType((*CommandConnect)(nil), "pulsar.proto.CommandConnect")
+	proto.RegisterType((*CommandConnected)(nil), "pulsar.proto.CommandConnected")
+	proto.RegisterType((*CommandSubscribe)(nil), "pulsar.proto.CommandSubscribe")
+	proto.RegisterType((*CommandPartitionedTopicMetadata)(nil), "pulsar.proto.CommandPartitionedTopicMetadata")
+	proto.RegisterType((*CommandPartitionedTopicMetadataResponse)(nil), "pulsar.proto.CommandPartitionedTopicMetadataResponse")
+	proto.RegisterType((*CommandLookupTopic)(nil), "pulsar.proto.CommandLookupTopic")
+	proto.RegisterType((*CommandLookupTopicResponse)(nil), "pulsar.proto.CommandLookupTopicResponse")
+	proto.RegisterType((*CommandProducer)(nil), "pulsar.proto.CommandProducer")
+	proto.RegisterType((*CommandSend)(nil), "pulsar.proto.CommandSend")
+	proto.RegisterType((*CommandSendReceipt)(nil), "pulsar.proto.CommandSendReceipt")
+	proto.RegisterType((*CommandSendError)(nil), "pulsar.proto.CommandSendError")
+	proto.RegisterType((*CommandMessage)(nil), "pulsar.proto.CommandMessage")
+	proto.RegisterType((*CommandAck)(nil), "pulsar.proto.CommandAck")
+	proto.RegisterType((*CommandActiveConsumerChange)(nil), "pulsar.proto.CommandActiveConsumerChange")
+	proto.RegisterType((*CommandFlow)(nil), "pulsar.proto.CommandFlow")
+	proto.RegisterType((*CommandUnsubscribe)(nil), "pulsar.proto.CommandUnsubscribe")
+	proto.RegisterType((*CommandSeek)(nil), "pulsar.proto.CommandSeek")
+	proto.RegisterType((*CommandReachedEndOfTopic)(nil), "pulsar.proto.CommandReachedEndOfTopic")
+	proto.RegisterType((*CommandCloseProducer)(nil), "pulsar.proto.CommandCloseProducer")
+	proto.RegisterType((*CommandCloseConsumer)(nil), "pulsar.proto.CommandCloseConsumer")
+	proto.RegisterType((*CommandRedeliverUnacknowledgedMessages)(nil), "pulsar.proto.CommandRedeliverUnacknowledgedMessages")
+	proto.RegisterType((*CommandSuccess)(nil), "pulsar.proto.CommandSuccess")
+	proto.RegisterType((*CommandProducerSuccess)(nil), "pulsar.proto.CommandProducerSuccess")
+	proto.RegisterType((*CommandError)(nil), "pulsar.proto.CommandError")
+	proto.RegisterType((*CommandPing)(nil), "pulsar.proto.CommandPing")
+	proto.RegisterType((*CommandPong)(nil), "pulsar.proto.CommandPong")
+	proto.RegisterType((*CommandConsumerStats)(nil), "pulsar.proto.CommandConsumerStats")
+	proto.RegisterType((*CommandConsumerStatsResponse)(nil), "pulsar.proto.CommandConsumerStatsResponse")
+	proto.RegisterType((*CommandGetLastMessageId)(nil), "pulsar.proto.CommandGetLastMessageId")
+	proto.RegisterType((*CommandGetLastMessageIdResponse)(nil), "pulsar.proto.CommandGetLastMessageIdResponse")
+	proto.RegisterType((*CommandGetTopicsOfNamespace)(nil), "pulsar.proto.CommandGetTopicsOfNamespace")
+	proto.RegisterType((*CommandGetTopicsOfNamespaceResponse)(nil), "pulsar.proto.CommandGetTopicsOfNamespaceResponse")
+	proto.RegisterType((*CommandGetSchema)(nil), "pulsar.proto.CommandGetSchema")
+	proto.RegisterType((*CommandGetSchemaResponse)(nil), "pulsar.proto.CommandGetSchemaResponse")
+	proto.RegisterType((*BaseCommand)(nil), "pulsar.proto.BaseCommand")
+	proto.RegisterEnum("pulsar.proto.CompressionType", CompressionType_name, CompressionType_value)
+	proto.RegisterEnum("pulsar.proto.ServerError", ServerError_name, ServerError_value)
+	proto.RegisterEnum("pulsar.proto.AuthMethod", AuthMethod_name, AuthMethod_value)
+	proto.RegisterEnum("pulsar.proto.ProtocolVersion", ProtocolVersion_name, ProtocolVersion_value)
+	proto.RegisterEnum("pulsar.proto.Schema_Type", Schema_Type_name, Schema_Type_value)
+	proto.RegisterEnum("pulsar.proto.CommandSubscribe_SubType", CommandSubscribe_SubType_name, CommandSubscribe_SubType_value)
+	proto.RegisterEnum("pulsar.proto.CommandSubscribe_InitialPosition", CommandSubscribe_InitialPosition_name, CommandSubscribe_InitialPosition_value)
+	proto.RegisterEnum("pulsar.proto.CommandPartitionedTopicMetadataResponse_LookupType", CommandPartitionedTopicMetadataResponse_LookupType_name, CommandPartitionedTopicMetadataResponse_LookupType_value)
+	proto.RegisterEnum("pulsar.proto.CommandLookupTopicResponse_LookupType", CommandLookupTopicResponse_LookupType_name, CommandLookupTopicResponse_LookupType_value)
+	proto.RegisterEnum("pulsar.proto.CommandAck_AckType", CommandAck_AckType_name, CommandAck_AckType_value)
+	proto.RegisterEnum("pulsar.proto.CommandAck_ValidationError", CommandAck_ValidationError_name, CommandAck_ValidationError_value)
+	proto.RegisterEnum("pulsar.proto.CommandGetTopicsOfNamespace_Mode", CommandGetTopicsOfNamespace_Mode_name, CommandGetTopicsOfNamespace_Mode_value)
+	proto.RegisterEnum("pulsar.proto.BaseCommand_Type", BaseCommand_Type_name, BaseCommand_Type_value)
+}
+
+func init() { proto.RegisterFile("PulsarApi.proto", fileDescriptor_PulsarApi_9ff3bd5e091a4809) }
+
+var fileDescriptor_PulsarApi_9ff3bd5e091a4809 = []byte{
+	// 4065 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x3b, 0x4d, 0x6f, 0x1b, 0x49,
+	0x76, 0x6e, 0x7e, 0x48, 0xe4, 0xe3, 0x57, 0xb9, 0x2c, 0x6b, 0xda, 0x1f, 0x63, 0xd3, 0xed, 0xb5,
+	0x47, 0xe3, 0x1d, 0x2b, 0xb6, 0xec, 0x75, 0x66, 0xbc, 0x9b, 0x60, 0x28, 0xaa, 0x6d, 0x33, 0x96,
+	0x48, 0x6d, 0x91, 0xf2, 0x62, 0x27, 0xbb, 0xe8, 0x6d, 0x75, 0x97, 0xa9, 0x86, 0x9a, 0xdd, 0x4c,
+	0x7f, 0x68, 0xac, 0x39, 0xe4, 0x36, 0x08, 0x02, 0x04, 0x08, 0x90, 0x1c, 0x73, 0xc8, 0x21, 0x08,
+	0x72, 0xce, 0x2d, 0x40, 0x7e, 0x43, 0x6e, 0xb9, 0xe4, 0x94, 0x4b, 0x72, 0x0c, 0x90, 0x43, 0x90,
+	0x73, 0x50, 0xd5, 0xd5, 0x5f, 0x24, 0x45, 0xca, 0x3b, 0x7b, 0xc8, 0x89, 0xdd, 0xaf, 0x5e, 0xbd,
+	0x7a, 0xf5, 0xde, 0xab, 0xf7, 0x55, 0x4d, 0x68, 0x1d, 0x86, 0xb6, 0xaf, 0x7b, 0x9d, 0xa9, 0xb5,
+	0x3d, 0xf5, 0xdc, 0xc0, 0xc5, 0xf5, 0x29, 0x07, 0x44, 0x6f, 0xca, 0x7f, 0x48, 0xb0, 0x36, 0x34,
+	0x4e, 0xe8, 0x44, 0xc7, 0x18, 0x4a, 0x8e, 0x3e, 0xa1, 0xb2, 0xd4, 0x2e, 0x6c, 0x55, 0x09, 0x7f,
+	0xc6, 0x77, 0xa1, 0xe6, 0xf3, 0x51, 0xcd, 0xd4, 0x03, 0x5d, 0x2e, 0xb6, 0x0b, 0x5b, 0x75, 0x02,
+	0x11, 0x68, 0x4f, 0x0f, 0x74, 0xfc, 0x18, 0x4a, 0xc1, 0xf9, 0x94, 0xca, 0xa5, 0x76, 0x61, 0xab,
+	0xb9, 0x73, 0x63, 0x3b, 0x4b, 0x7c, 0x3b, 0x22, 0xbc, 0x3d, 0x3a, 0x9f, 0x52, 0xc2, 0xd1, 0xf0,
+	0x0b, 0x80, 0xa9, 0xe7, 0x4e, 0xa9, 0x17, 0x58, 0xd4, 0x97, 0xcb, 0xed, 0xe2, 0x56, 0x6d, 0x67,
+	0x33, 0x3f, 0xe9, 0x2d, 0x3d, 0x7f, 0xa7, 0xdb, 0x21, 0x25, 0x19, 0x4c, 0xe5, 0x0f, 0xa1, 0xc4,
+	0xa8, 0xe0, 0x0a, 0x94, 0xfa, 0xae, 0x43, 0xd1, 0x15, 0x0c, 0xb0, 0x36, 0x0c, 0x3c, 0xcb, 0x19,
+	0x23, 0x89, 0x41, 0xff, 0xc8, 0x77, 0x1d, 0x54, 0xc0, 0x75, 0xa8, 0x1c, 0x32, 0x2a, 0xc7, 0xe1,
+	0x7b, 0x54, 0x64, 0xf0, 0xce, 0x99, 0xe7, 0xa2, 0x92, 0xf2, 0x17, 0x12, 0x34, 0x0e, 0xa8, 0xef,
+	0xeb, 0x63, 0xda, 0x33, 0x39, 0xe3, 0x37, 0xa1, 0x62, 0x53, 0x73, 0x4c, 0xbd, 0x9e, 0xc9, 0x77,
+	0x5c, 0x22, 0xc9, 0x3b, 0x96, 0x61, 0x9d, 0x3a, 0x81, 0x77, 0xde, 0x33, 0xe5, 0x02, 0x1f, 0x8a,
+	0x5f, 0x71, 0x1b, 0xaa, 0x53, 0xdd, 0x0b, 0xac, 0xc0, 0x72, 0x1d, 0xb9, 0xd8, 0x96, 0xb6, 0xca,
+	0x2f, 0x0b, 0x8f, 0x9f, 0x92, 0x14, 0x88, 0xef, 0x43, 0xed, 0x58, 0x0f, 0x8c, 0x13, 0xcd, 0x72,
+	0x4c, 0xfa, 0x41, 0x2e, 0x25, 0x38, 0xc0, 0xc1, 0x3d, 0x06, 0x55, 0x76, 0xa0, 0x12, 0x6f, 0x13,
+	0x23, 0x28, 0x9e, 0xd2, 0x73, 0x21, 0x75, 0xf6, 0x88, 0x37, 0xa0, 0x7c, 0xc6, 0x86, 0xf8, 0xe2,
+	0x55, 0x12, 0xbd, 0x28, 0x2f, 0xa0, 0xfe, 0x96, 0x9e, 0xef, 0xbb, 0xce, 0xf8, 0x52, 0xf3, 0x4a,
+	0xf1, 0x3c, 0x1b, 0x9a, 0xaa, 0x63, 0x78, 0xe7, 0x53, 0xc6, 0xde, 0x5b, 0x7a, 0xee, 0xaf, 0x9a,
+	0x59, 0x17, 0x33, 0xf1, 0x0e, 0x54, 0x26, 0x34, 0xd0, 0x85, 0xe6, 0x97, 0xa9, 0x2a, 0xc1, 0x53,
+	0xfe, 0xb7, 0x0c, 0x2d, 0x21, 0xe8, 0x03, 0x01, 0xc3, 0xf7, 0xa1, 0x31, 0xf5, 0x5c, 0x33, 0x34,
+	0xa8, 0xa7, 0x65, 0x2c, 0xac, 0x1e, 0x03, 0xfb, 0xb1, 0xa5, 0xd1, 0x3f, 0x09, 0xa9, 0x63, 0x50,
+	0xcd, 0x8a, 0xe5, 0x0e, 0x31, 0xa8, 0x67, 0xe2, 0x7b, 0x50, 0x9f, 0x86, 0xc7, 0xb6, 0xe5, 0x9f,
+	0x68, 0x81, 0x35, 0xa1, 0xdc, 0x16, 0x4b, 0xa4, 0x26, 0x60, 0x23, 0x6b, 0x32, 0x6b, 0x5d, 0xa5,
+	0xcb, 0x5a, 0x17, 0xfe, 0x0c, 0x5a, 0x1e, 0x9d, 0xda, 0x96, 0xa1, 0x07, 0xd4, 0xd4, 0xde, 0x7b,
+	0xee, 0x44, 0x2e, 0xb7, 0xa5, 0xad, 0x2a, 0x69, 0xa6, 0xe0, 0x57, 0x9e, 0x3b, 0xe1, 0x3b, 0x89,
+	0x35, 0xad, 0x31, 0x19, 0xae, 0x71, 0xb4, 0x7a, 0x02, 0x7c, 0x4b, 0xcf, 0x19, 0xa3, 0xc9, 0x34,
+	0x2d, 0x70, 0xe5, 0xf5, 0x76, 0x71, 0xab, 0x4a, 0x6a, 0x09, 0x6c, 0xe4, 0x62, 0x15, 0x6a, 0x86,
+	0x3b, 0x99, 0x7a, 0xd4, 0xf7, 0x99, 0x21, 0x55, 0xda, 0xd2, 0x56, 0x73, 0xe7, 0xd3, 0x3c, 0xa7,
+	0xdd, 0x14, 0x81, 0x99, 0xfe, 0xcb, 0x52, 0x7f, 0xd0, 0x57, 0x49, 0x76, 0x1e, 0xde, 0x86, 0xab,
+	0xa1, 0x13, 0x03, 0xa8, 0xa9, 0xf9, 0xd6, 0x77, 0x54, 0xae, 0xb6, 0xa5, 0xad, 0xc6, 0x4b, 0xe9,
+	0x09, 0x41, 0xd9, 0xb1, 0xa1, 0xf5, 0x1d, 0xc5, 0xcf, 0xe1, 0xba, 0x13, 0x4e, 0xb4, 0x49, 0xa4,
+	0x1f, 0x5f, 0xb3, 0x1c, 0x8d, 0x1b, 0xa5, 0x5c, 0xe3, 0x56, 0x2a, 0x3d, 0x25, 0xd8, 0x09, 0x27,
+	0x42, 0x7d, 0x7e, 0xcf, 0xd9, 0x65, 0x83, 0xb8, 0x0d, 0x40, 0xcf, 0xa8, 0x13, 0x44, 0x62, 0xaf,
+	0xb7, 0xa5, 0xad, 0x12, 0x23, 0x5f, 0xe5, 0x40, 0x2e, 0x77, 0x15, 0x5a, 0x34, 0x31, 0x31, 0x26,
+	0x17, 0x5f, 0x6e, 0x70, 0xe1, 0xdf, 0xce, 0x6f, 0x29, 0x6f, 0x87, 0xa4, 0x49, 0xf3, 0x76, 0xf9,
+	0x59, 0x8e, 0x8c, 0x6e, 0x8f, 0x5d, 0xb9, 0x19, 0xa9, 0x21, 0x05, 0x77, 0xec, 0xb1, 0x8b, 0x3f,
+	0x07, 0x94, 0x41, 0x9c, 0xea, 0x9e, 0x3e, 0x91, 0x5b, 0x6d, 0x69, 0xab, 0x4e, 0x32, 0x04, 0x0e,
+	0x19, 0x18, 0x3f, 0x80, 0xa6, 0x70, 0x60, 0x67, 0xd4, 0xe3, 0xc2, 0x46, 0x1c, 0xb1, 0x11, 0x41,
+	0xdf, 0x45, 0x40, 0xfc, 0x35, 0xdc, 0xc8, 0x29, 0x56, 0x3b, 0x7e, 0xf1, 0x5c, 0xa3, 0x8e, 0xe1,
+	0x9a, 0xd4, 0x94, 0xaf, 0xb6, 0xa5, 0xad, 0xca, 0xcb, 0xf2, 0x7b, 0xdd, 0xf6, 0x29, 0xd9, 0xcc,
+	0xea, 0x7a, 0xf7, 0xc5, 0x73, 0x35, 0x42, 0x52, 0xfe, 0xa1, 0x00, 0xd7, 0x87, 0x96, 0x33, 0xb6,
+	0xe9, 0xac, 0xf9, 0xe7, 0xad, 0x52, 0xba, 0xb4, 0x55, 0xce, 0x19, 0x5b, 0x61, 0xb1, 0xb1, 0x4d,
+	0xf5, 0x73, 0xdb, 0xd5, 0x85, 0xf6, 0xd9, 0xa9, 0x28, 0x93, 0x9a, 0x80, 0x71, 0xad, 0x3f, 0x82,
+	0x06, 0xb3, 0x03, 0xdd, 0x60, 0xc6, 0xed, 0x86, 0x01, 0xf7, 0x49, 0xc9, 0x7e, 0xea, 0xc9, 0xd8,
+	0x20, 0x0c, 0x66, 0x74, 0x5d, 0x5e, 0xa0, 0xeb, 0xa5, 0x92, 0x5a, 0xbb, 0x8c, 0xa4, 0xfe, 0xbe,
+	0x08, 0xcd, 0xae, 0x3b, 0x99, 0xe8, 0x8e, 0xd9, 0x75, 0x1d, 0x87, 0x1a, 0x01, 0xd3, 0x92, 0x61,
+	0x5b, 0x6c, 0xdd, 0x58, 0x4b, 0x91, 0x8b, 0x68, 0x44, 0xd0, 0x58, 0x4b, 0x5f, 0x41, 0x4d, 0x0f,
+	0x83, 0x13, 0x6d, 0x42, 0x83, 0x13, 0xd7, 0xe4, 0xf2, 0x68, 0xee, 0xc8, 0x79, 0x51, 0x76, 0xc2,
+	0xe0, 0xe4, 0x80, 0x8f, 0x13, 0xd0, 0x93, 0x67, 0xbc, 0x05, 0x28, 0x33, 0x35, 0x72, 0x43, 0xe2,
+	0x8c, 0xa7, 0x58, 0xdc, 0x11, 0xdd, 0x82, 0x2a, 0xc7, 0x14, 0x6e, 0x8f, 0x19, 0x4b, 0x85, 0x01,
+	0x78, 0xd4, 0xf8, 0x02, 0x10, 0x5f, 0xc6, 0x70, 0xed, 0x84, 0xd5, 0xc8, 0xc5, 0x4b, 0x4f, 0x48,
+	0x2b, 0x1e, 0x8a, 0xf9, 0x7d, 0x0c, 0xd7, 0xa6, 0x9e, 0xfb, 0xe1, 0x5c, 0x0b, 0x5c, 0xed, 0xd8,
+	0x73, 0x4f, 0xa9, 0xa7, 0x85, 0x9e, 0x2d, 0x9c, 0x06, 0xe2, 0x43, 0x23, 0x77, 0x97, 0x0f, 0x1c,
+	0x79, 0x36, 0x7e, 0x0c, 0xd8, 0xf5, 0xac, 0xb1, 0xe5, 0xe8, 0xb6, 0x36, 0xf5, 0x2c, 0xc7, 0xb0,
+	0xa6, 0xba, 0x2d, 0xaf, 0x73, 0xec, 0xab, 0xf1, 0xc8, 0x61, 0x3c, 0x80, 0xbf, 0xc8, 0xa0, 0xa7,
+	0x1c, 0x57, 0x22, 0xe2, 0xf1, 0x48, 0x27, 0xe6, 0xfc, 0x09, 0x6c, 0xe4, 0xb1, 0x85, 0x10, 0xab,
+	0x1c, 0x1f, 0x67, 0xf1, 0x23, 0x61, 0x28, 0x63, 0x40, 0x79, 0x35, 0x51, 0x93, 0x1f, 0x27, 0xea,
+	0x9d, 0x51, 0x6f, 0x56, 0x51, 0x11, 0x34, 0xde, 0xf8, 0x22, 0x31, 0x15, 0x2e, 0x12, 0x93, 0xf2,
+	0x2f, 0xe5, 0x64, 0xa5, 0x61, 0x78, 0xec, 0x1b, 0x9e, 0x75, 0x4c, 0x59, 0x48, 0x0a, 0xdc, 0xa9,
+	0x65, 0x88, 0x05, 0xa2, 0x17, 0xac, 0x40, 0xdd, 0x8f, 0x50, 0xf8, 0x19, 0x17, 0x11, 0x32, 0x07,
+	0xc3, 0x5f, 0xc3, 0xba, 0x1f, 0x1e, 0x33, 0x9f, 0xc9, 0x4f, 0x43, 0x73, 0xe7, 0xe1, 0x9c, 0x63,
+	0xcd, 0x2d, 0xb5, 0x3d, 0x8c, 0xb0, 0x49, 0x3c, 0x8d, 0xc5, 0x22, 0xc3, 0x75, 0xfc, 0x70, 0x42,
+	0x3d, 0x16, 0x8b, 0x4a, 0x51, 0x2c, 0x8a, 0x41, 0x3d, 0x13, 0x7f, 0x0a, 0xe0, 0xb1, 0xc8, 0xe4,
+	0x07, 0x6c, 0xbc, 0xcc, 0xc7, 0xab, 0x02, 0xd2, 0x33, 0xd9, 0xc9, 0x4d, 0xe6, 0x73, 0x4b, 0x13,
+	0x61, 0x22, 0x06, 0x72, 0x3b, 0x7b, 0x00, 0xcd, 0xa9, 0x67, 0xb9, 0x9e, 0x15, 0x9c, 0x6b, 0x36,
+	0x3d, 0xa3, 0x91, 0xa6, 0xcb, 0xa4, 0x11, 0x43, 0xf7, 0x19, 0x10, 0xdf, 0x81, 0x75, 0x33, 0xf4,
+	0xf4, 0x63, 0x9b, 0x72, 0xd5, 0x56, 0x5e, 0x96, 0x02, 0x2f, 0xa4, 0x24, 0x06, 0x62, 0x15, 0x90,
+	0x1f, 0xe8, 0x5e, 0x10, 0x7b, 0x75, 0xc6, 0x10, 0xd3, 0x69, 0x6d, 0xe7, 0x56, 0x7e, 0xdb, 0xb9,
+	0xf4, 0x87, 0x34, 0xf9, 0xa4, 0x04, 0x96, 0x8b, 0xf5, 0x70, 0xb9, 0x58, 0xcf, 0x76, 0xe0, 0x51,
+	0xdd, 0xd4, 0x12, 0x0f, 0xc2, 0xe3, 0x48, 0x85, 0x34, 0x18, 0xb4, 0x1b, 0x03, 0xf1, 0x17, 0xb0,
+	0x16, 0x39, 0x5b, 0x1e, 0x3b, 0x6a, 0x3b, 0x1b, 0x8b, 0x92, 0x44, 0x22, 0x70, 0xf0, 0x6f, 0xa0,
+	0x65, 0x39, 0x56, 0x60, 0xe9, 0xf6, 0xa1, 0xeb, 0x47, 0x79, 0x56, 0x83, 0x9f, 0xf3, 0xed, 0x15,
+	0x5a, 0xec, 0xe5, 0x67, 0xbd, 0x5c, 0xdb, 0xd7, 0x03, 0xea, 0x07, 0x64, 0x96, 0x9c, 0xb2, 0x03,
+	0xeb, 0x42, 0xe3, 0xb8, 0x01, 0x55, 0xf5, 0x83, 0x61, 0x87, 0xbe, 0x75, 0x16, 0xe7, 0x94, 0x27,
+	0xba, 0x47, 0x4d, 0x24, 0xb1, 0x4c, 0xf2, 0x95, 0x6e, 0xd9, 0xee, 0x19, 0xf5, 0x50, 0x41, 0xf9,
+	0x31, 0xb4, 0x66, 0xe8, 0x33, 0xe4, 0x68, 0x05, 0x74, 0x85, 0x21, 0xab, 0xba, 0x67, 0x5b, 0xec,
+	0x4d, 0x52, 0xfe, 0x53, 0x82, 0xbb, 0x82, 0xbd, 0xc3, 0xd8, 0x05, 0x52, 0x73, 0xc4, 0x0c, 0x38,
+	0x09, 0x0a, 0x8b, 0xcd, 0x3b, 0x6f, 0x57, 0x85, 0x59, 0xbb, 0x5a, 0xec, 0x20, 0x8a, 0x1f, 0xe7,
+	0x20, 0x4a, 0x1f, 0xe9, 0x20, 0xca, 0x17, 0x3a, 0x88, 0x7f, 0x2a, 0xc0, 0x67, 0x2b, 0xf6, 0x49,
+	0xa8, 0x3f, 0x75, 0x1d, 0x9f, 0xe2, 0x3b, 0x00, 0x49, 0x38, 0x60, 0x41, 0x50, 0xda, 0x6a, 0x90,
+	0x0c, 0x64, 0xd5, 0xce, 0x7f, 0x05, 0x15, 0x4f, 0x90, 0xe2, 0xfb, 0x6d, 0xee, 0x7c, 0xbd, 0xd0,
+	0x1c, 0x56, 0xf1, 0xb1, 0xbd, 0xef, 0xba, 0xa7, 0xe1, 0x94, 0x1f, 0xf7, 0x84, 0x22, 0xfe, 0x3d,
+	0x28, 0x53, 0xcf, 0x73, 0x3d, 0x2e, 0x9b, 0xf9, 0x2a, 0x86, 0xbb, 0x36, 0x95, 0x21, 0x90, 0x08,
+	0x8f, 0x15, 0x08, 0xe2, 0xb8, 0x09, 0xf1, 0xc4, 0xaf, 0xca, 0x03, 0x80, 0x74, 0x09, 0x5c, 0x63,
+	0xa6, 0x66, 0x18, 0xd4, 0xf7, 0x23, 0xeb, 0x62, 0x16, 0xc5, 0xac, 0x4b, 0xf9, 0xbe, 0x00, 0x58,
+	0xb0, 0x2c, 0xd0, 0xb9, 0xfe, 0x7f, 0x2b, 0xab, 0xf8, 0x31, 0x34, 0x98, 0xbe, 0x98, 0xcf, 0xd0,
+	0x03, 0xeb, 0x2c, 0x12, 0x50, 0x12, 0x85, 0xf3, 0x63, 0x17, 0x98, 0x50, 0xe9, 0xe3, 0x4c, 0xa8,
+	0xfc, 0x91, 0x26, 0xb4, 0x76, 0xa1, 0x09, 0xfd, 0x5b, 0x11, 0x6e, 0xce, 0xcb, 0x21, 0xb1, 0x9a,
+	0x47, 0x80, 0xa2, 0xb8, 0xc9, 0x74, 0x60, 0x19, 0xf4, 0xc8, 0xb3, 0xb9, 0xed, 0x54, 0xc9, 0x1c,
+	0x1c, 0x3f, 0x81, 0x6b, 0xb3, 0xb0, 0x91, 0xed, 0x8b, 0xa4, 0x69, 0xd1, 0x10, 0x1e, 0xcc, 0x19,
+	0xd5, 0xb3, 0x85, 0x46, 0xb5, 0x80, 0xb3, 0xc5, 0x76, 0x94, 0x57, 0x54, 0x69, 0xa5, 0xa2, 0xca,
+	0x4b, 0x14, 0x95, 0xd8, 0xe4, 0xda, 0xc7, 0xdb, 0xe4, 0x7a, 0xce, 0x26, 0x79, 0xca, 0x16, 0xa5,
+	0x21, 0x27, 0x9e, 0x1b, 0x8e, 0x4f, 0x34, 0x3f, 0x12, 0x03, 0x4f, 0x46, 0x2a, 0xf9, 0x94, 0x8d,
+	0xe7, 0x24, 0x11, 0x5a, 0x2a, 0x2c, 0xe5, 0x59, 0xce, 0xaa, 0xeb, 0x50, 0x21, 0xd4, 0xb4, 0x3c,
+	0x6a, 0x30, 0xdf, 0x57, 0x83, 0x75, 0x91, 0x1f, 0x20, 0x29, 0x63, 0xe3, 0x05, 0xe5, 0xaf, 0x0b,
+	0xd0, 0x8a, 0x8f, 0xa5, 0xa8, 0xf4, 0x2e, 0x30, 0xf0, 0xbb, 0x50, 0x4b, 0x0a, 0xc4, 0xb4, 0xf6,
+	0x8b, 0x41, 0x73, 0xf1, 0xb6, 0xb8, 0x20, 0xde, 0xe6, 0x0b, 0xcc, 0x92, 0xc8, 0x94, 0xb3, 0x05,
+	0xe6, 0x7d, 0xa8, 0x8a, 0xe2, 0x80, 0x9a, 0x79, 0xc9, 0xa7, 0xf0, 0x5c, 0x18, 0x5c, 0xbb, 0x64,
+	0x18, 0x4c, 0xe3, 0xdb, 0xfa, 0xea, 0xf8, 0xa6, 0x84, 0x50, 0x8b, 0x43, 0x17, 0x75, 0xcc, 0xd9,
+	0xad, 0x4b, 0x73, 0x5b, 0x5f, 0x59, 0x17, 0xff, 0x08, 0xea, 0xd9, 0xa2, 0x4e, 0x74, 0x25, 0xa4,
+	0xa7, 0xa4, 0x96, 0xa9, 0xe5, 0x94, 0xbf, 0x92, 0x12, 0x87, 0xc3, 0xd6, 0x25, 0xd4, 0xa0, 0xd6,
+	0x34, 0xf8, 0x1d, 0x2c, 0xff, 0x12, 0x20, 0x93, 0x79, 0x14, 0x57, 0x67, 0x1e, 0xd5, 0x49, 0xfc,
+	0xaa, 0xfc, 0xad, 0x94, 0x26, 0x7e, 0xd4, 0x31, 0xb9, 0x39, 0xff, 0x0e, 0x58, 0x4a, 0x8e, 0x4e,
+	0x71, 0x61, 0x53, 0x6a, 0xe9, 0xd1, 0x29, 0x71, 0xbb, 0x4c, 0xdc, 0xf9, 0xdf, 0x48, 0x49, 0xad,
+	0x22, 0x76, 0x31, 0x9b, 0x1c, 0x4a, 0x73, 0xc9, 0x61, 0x5e, 0x22, 0x8c, 0xbd, 0x4b, 0x4b, 0x84,
+	0x25, 0xce, 0x1e, 0x35, 0xa9, 0x6d, 0x9d, 0x51, 0xef, 0x5c, 0x33, 0xdc, 0xd0, 0x09, 0xb8, 0x4c,
+	0x79, 0x41, 0xdf, 0x4a, 0x87, 0xba, 0x6c, 0x44, 0xf9, 0x9f, 0x22, 0x80, 0xe0, 0xae, 0x63, 0x9c,
+	0xae, 0xe6, 0xec, 0xa7, 0x50, 0xd1, 0x8d, 0x53, 0x8d, 0x37, 0xec, 0x0a, 0x5c, 0x36, 0xed, 0x85,
+	0x0e, 0xaf, 0x63, 0x9c, 0x6e, 0x77, 0x8c, 0xd3, 0x28, 0x29, 0xd6, 0xa3, 0x87, 0x39, 0x45, 0x17,
+	0x3f, 0x62, 0x5b, 0x43, 0x40, 0x67, 0xba, 0x6d, 0x99, 0x3a, 0xaf, 0x1a, 0xb3, 0xb1, 0x76, 0xeb,
+	0x42, 0x06, 0xde, 0x25, 0x13, 0x22, 0x5d, 0xb5, 0xce, 0xf2, 0x00, 0xc6, 0xd0, 0x5c, 0x2f, 0xf1,
+	0xe6, 0xdc, 0x69, 0x4d, 0x1a, 0x66, 0xb9, 0x7e, 0xe2, 0xe7, 0xb0, 0x2e, 0x36, 0x88, 0x9b, 0x00,
+	0x3d, 0xc7, 0xb4, 0xce, 0x2c, 0x33, 0xd4, 0x6d, 0x74, 0x85, 0xbd, 0x77, 0xc3, 0x49, 0x68, 0x73,
+	0x37, 0x8c, 0x24, 0xe5, 0x2f, 0x25, 0x68, 0xcd, 0xf0, 0x82, 0xef, 0xc0, 0xcd, 0xa3, 0x99, 0xe6,
+	0x4a, 0xd7, 0xf5, 0xbc, 0x90, 0x17, 0x20, 0xe8, 0x0a, 0xde, 0x04, 0xbc, 0x47, 0x33, 0x9d, 0x1a,
+	0x3e, 0x0b, 0x49, 0x78, 0x03, 0x50, 0xf7, 0x84, 0x1a, 0xa7, 0x7e, 0x38, 0x39, 0xb0, 0xfc, 0x89,
+	0x1e, 0x18, 0x27, 0xa8, 0x80, 0x6f, 0xc0, 0x75, 0xde, 0x69, 0xd9, 0xa3, 0x43, 0xea, 0x59, 0xba,
+	0x6d, 0x7d, 0x47, 0xa3, 0x09, 0x45, 0x7c, 0x0d, 0x5a, 0x7b, 0x34, 0xee, 0x68, 0x44, 0xc0, 0x92,
+	0x72, 0x0c, 0xb7, 0x12, 0x39, 0x31, 0x26, 0xbb, 0x42, 0xc3, 0xdd, 0x13, 0xdd, 0xb9, 0x8c, 0x81,
+	0x2a, 0x50, 0xb5, 0x7c, 0x4d, 0xe7, 0x73, 0x79, 0x7c, 0x4c, 0x3c, 0x61, 0xc5, 0xf2, 0x23, 0x92,
+	0xca, 0xbb, 0xc4, 0x4d, 0xbd, 0xb2, 0xdd, 0x6f, 0x57, 0xd3, 0x7c, 0x08, 0x4d, 0xa1, 0xee, 0x43,
+	0xea, 0x4d, 0xac, 0xc0, 0xe7, 0x06, 0xd6, 0x20, 0x33, 0x50, 0x65, 0x94, 0xb8, 0xa1, 0x23, 0xc7,
+	0x4f, 0x8a, 0xbd, 0x95, 0xe4, 0x97, 0xa7, 0x40, 0xca, 0x9f, 0x4b, 0x19, 0xaf, 0x4a, 0x4f, 0x7f,
+	0x28, 0xbd, 0x1f, 0xe4, 0xd4, 0x7e, 0x0a, 0xb2, 0x60, 0x85, 0x50, 0xdd, 0x38, 0xa1, 0xa6, 0xea,
+	0x98, 0x83, 0xf7, 0xa3, 0x38, 0xd0, 0x2d, 0xe5, 0x4b, 0x79, 0x07, 0x1b, 0x71, 0xcd, 0x6d, 0xbb,
+	0x3e, 0x4d, 0xe2, 0xe6, 0x4a, 0xa7, 0xb8, 0x42, 0x40, 0x33, 0x74, 0x63, 0x8b, 0xf9, 0xc1, 0x82,
+	0xff, 0x33, 0x09, 0x1e, 0x26, 0xbb, 0x15, 0xce, 0xe9, 0xc8, 0xd1, 0x8d, 0x53, 0xc7, 0xfd, 0x96,
+	0xb7, 0xd3, 0x63, 0xb7, 0xe9, 0xaf, 0x5e, 0xea, 0x67, 0x50, 0x4b, 0x85, 0xce, 0xec, 0x67, 0xa5,
+	0x87, 0x81, 0x44, 0xea, 0xbe, 0xf2, 0xeb, 0xc4, 0x51, 0x8b, 0x8c, 0x7b, 0x86, 0x75, 0x69, 0x56,
+	0xc7, 0x69, 0xd8, 0x2e, 0x5c, 0x22, 0x6c, 0xff, 0xa3, 0x04, 0x9b, 0x33, 0xc9, 0xcc, 0x25, 0xd7,
+	0x99, 0x4b, 0x4e, 0x0a, 0x0b, 0xba, 0xdf, 0x5f, 0x00, 0xb2, 0x75, 0x3f, 0xd0, 0xb2, 0x81, 0x8d,
+	0x99, 0x5d, 0x91, 0x5f, 0x1d, 0x34, 0xd9, 0xd8, 0x30, 0x0d, 0x70, 0xf3, 0x4d, 0xcd, 0xd2, 0x82,
+	0xa6, 0xa6, 0xf2, 0x01, 0xea, 0x82, 0xe5, 0xc8, 0x6b, 0xad, 0x60, 0x34, 0x09, 0x9b, 0x85, 0x8f,
+	0x0f, 0x9b, 0xc5, 0x7c, 0xd8, 0x6c, 0x24, 0xc7, 0xf1, 0xd0, 0x72, 0xc6, 0xd9, 0x57, 0xd7, 0x19,
+	0x67, 0x8d, 0x51, 0x68, 0x7f, 0x18, 0xe8, 0xc1, 0x4a, 0x41, 0xae, 0xea, 0xca, 0x28, 0xff, 0x5d,
+	0x82, 0xdb, 0x8b, 0x08, 0x93, 0xc5, 0xf9, 0xf9, 0xdc, 0x02, 0x5f, 0x02, 0xf0, 0x8d, 0x69, 0x86,
+	0x6b, 0x52, 0xd1, 0x5d, 0x5c, 0x22, 0x85, 0x2a, 0x47, 0xee, 0xba, 0x26, 0xcb, 0x2d, 0x1b, 0xd1,
+	0xcc, 0x54, 0x1e, 0x3c, 0x01, 0xe5, 0xc0, 0x38, 0x71, 0xb8, 0x03, 0x30, 0xf1, 0xc7, 0x44, 0x0f,
+	0xe8, 0x40, 0x34, 0x61, 0x25, 0x92, 0x81, 0xb0, 0x62, 0x67, 0xe2, 0x8f, 0x45, 0xf2, 0x3d, 0x0d,
+	0x03, 0x86, 0x55, 0xe6, 0x58, 0x73, 0x70, 0x81, 0xcb, 0x66, 0x26, 0xc7, 0x8e, 0x17, 0x0a, 0x11,
+	0x6e, 0x0e, 0x8e, 0x15, 0xc8, 0x35, 0x9e, 0x44, 0x75, 0x90, 0x6f, 0x46, 0x3d, 0x02, 0xa4, 0x9f,
+	0xe9, 0x96, 0xad, 0x1f, 0xdb, 0x89, 0x03, 0x67, 0x95, 0x41, 0x89, 0xcc, 0xc1, 0xf1, 0x16, 0xb4,
+	0x42, 0x76, 0xc4, 0xd3, 0xb3, 0xcd, 0x1b, 0x4e, 0x25, 0x32, 0x0b, 0xc6, 0xbb, 0x70, 0xfb, 0xd8,
+	0x76, 0x19, 0x28, 0xd6, 0xc7, 0xc0, 0x39, 0x12, 0x38, 0xfe, 0xd8, 0x97, 0x81, 0xb7, 0x8b, 0x96,
+	0xe2, 0x30, 0x23, 0xd3, 0x4d, 0x93, 0xc5, 0x51, 0xde, 0x5d, 0xaa, 0x92, 0xf8, 0x95, 0x85, 0x1c,
+	0x23, 0x6e, 0x4c, 0x0e, 0x2d, 0xc7, 0x88, 0xee, 0x26, 0xaa, 0x64, 0x06, 0x8a, 0xb1, 0xb8, 0xa2,
+	0x6c, 0xf0, 0xd1, 0xe8, 0x1e, 0x92, 0x85, 0xab, 0x48, 0x4e, 0xea, 0x87, 0xa9, 0xe5, 0x51, 0x93,
+	0xdf, 0x34, 0x48, 0x64, 0x06, 0x2a, 0x74, 0xb6, 0xab, 0x1b, 0xa7, 0xb6, 0x3b, 0xe6, 0x77, 0x0c,
+	0x25, 0x92, 0x81, 0x28, 0xbf, 0x84, 0x4f, 0x84, 0xc5, 0xbd, 0xa6, 0xc1, 0xbe, 0xee, 0x67, 0x3a,
+	0x6a, 0x3f, 0xd4, 0xb5, 0x7e, 0x9f, 0x76, 0x91, 0x66, 0x69, 0x27, 0x06, 0xdd, 0x85, 0x16, 0x77,
+	0x1b, 0x99, 0x60, 0x25, 0xad, 0xce, 0x37, 0x1b, 0x76, 0x8e, 0xd1, 0x15, 0x7c, 0xfc, 0xbb, 0x94,
+	0xa4, 0x1b, 0xaf, 0x69, 0xc0, 0xe3, 0x98, 0x3f, 0x78, 0xcf, 0xac, 0xc6, 0x9f, 0xea, 0xc6, 0xca,
+	0x43, 0x75, 0x1b, 0xaa, 0x4e, 0x8c, 0x2b, 0x5c, 0x5f, 0x0a, 0xc0, 0x7d, 0x28, 0x4d, 0xd8, 0x61,
+	0x2b, 0x2e, 0x69, 0xf1, 0x2d, 0x5a, 0x75, 0xfb, 0xc0, 0x35, 0xe9, 0x4b, 0x38, 0x54, 0xc9, 0xb0,
+	0x37, 0x1c, 0xa9, 0xfd, 0x11, 0xe1, 0x74, 0x94, 0x67, 0x50, 0x62, 0x23, 0x2c, 0x89, 0x4b, 0xc7,
+	0xd0, 0x15, 0x8c, 0xa1, 0xd9, 0x1f, 0xf4, 0xb5, 0x0c, 0x4c, 0xc2, 0xeb, 0x50, 0xec, 0xec, 0xef,
+	0xa3, 0x82, 0xf2, 0x2b, 0xb8, 0xbf, 0x64, 0xa9, 0xcb, 0x7a, 0x8f, 0x4d, 0x58, 0xe3, 0xd5, 0x6c,
+	0x14, 0xb9, 0xaa, 0x44, 0xbc, 0x29, 0x4e, 0x52, 0xe3, 0xbc, 0xa6, 0x81, 0xb8, 0x6a, 0x5f, 0x41,
+	0x2a, 0xa9, 0x92, 0x0b, 0xd9, 0x2a, 0x79, 0xde, 0xeb, 0x17, 0x17, 0x79, 0xfd, 0xff, 0x92, 0x92,
+	0x04, 0x24, 0x59, 0xf0, 0xff, 0x89, 0x07, 0x4c, 0x43, 0x6e, 0xe9, 0x12, 0x9d, 0xe0, 0xf9, 0xfd,
+	0x96, 0x17, 0xed, 0xf7, 0x9f, 0x6f, 0x40, 0x6d, 0x57, 0x67, 0x39, 0x0d, 0xdf, 0x33, 0xde, 0x11,
+	0xc7, 0x5d, 0xe2, 0x51, 0xec, 0x4e, 0x7e, 0x89, 0x0c, 0x62, 0xfe, 0xb3, 0x84, 0x75, 0xe1, 0x34,
+	0x44, 0x32, 0x70, 0x7b, 0xa1, 0x25, 0x8a, 0x3e, 0x07, 0x89, 0x91, 0xf1, 0xcf, 0xa0, 0x9a, 0x38,
+	0x1b, 0x91, 0x26, 0xde, 0x59, 0x36, 0x93, 0x9a, 0x24, 0x9d, 0xc0, 0x66, 0x27, 0x29, 0xb0, 0x90,
+	0xc8, 0x9d, 0xe5, 0x4d, 0x6e, 0x92, 0x4e, 0xc0, 0x5f, 0x41, 0x25, 0x4e, 0x21, 0xb8, 0x60, 0x6a,
+	0x0b, 0x2e, 0x90, 0xb3, 0xe9, 0x0a, 0x49, 0xd0, 0xf1, 0x63, 0x28, 0xf9, 0xd4, 0x89, 0xfa, 0x72,
+	0xb5, 0x59, 0x05, 0x67, 0xbb, 0x04, 0x1c, 0x0d, 0x77, 0xa1, 0xce, 0x7e, 0x35, 0x2f, 0x6a, 0x1a,
+	0x88, 0x36, 0x47, 0xfb, 0xe2, 0x69, 0x11, 0x1e, 0xa9, 0xf9, 0x99, 0x4e, 0xc3, 0x1f, 0x00, 0x70,
+	0x22, 0x51, 0x8a, 0x51, 0x59, 0xb6, 0xdb, 0xb8, 0x15, 0x40, 0xaa, 0x7e, 0xd2, 0x15, 0x78, 0x91,
+	0xe6, 0x1a, 0xd5, 0x25, 0x1a, 0x12, 0x96, 0x96, 0xf6, 0xbe, 0x1e, 0x41, 0x51, 0x37, 0x4e, 0x79,
+	0xa4, 0xa9, 0xcd, 0x5e, 0x15, 0xa6, 0xc5, 0x26, 0x61, 0x48, 0x4c, 0x2c, 0xef, 0x6d, 0xf7, 0x5b,
+	0x1e, 0x67, 0x2e, 0x12, 0x0b, 0xab, 0x86, 0x08, 0x47, 0xc3, 0xbb, 0x50, 0x0b, 0xd3, 0x1a, 0x46,
+	0x5c, 0x6e, 0x2c, 0x96, 0x4a, 0xa6, 0xd6, 0x21, 0xd9, 0x49, 0x6c, 0x5b, 0x7e, 0x94, 0x46, 0xf2,
+	0xf0, 0x74, 0xd1, 0xb6, 0x44, 0xaa, 0x49, 0x62, 0x64, 0xfc, 0x24, 0xce, 0xd5, 0x9a, 0x7c, 0xd6,
+	0xcd, 0x85, 0xb3, 0x72, 0xc9, 0x5a, 0x0f, 0x9a, 0x06, 0x4b, 0xfd, 0xb5, 0xc4, 0x68, 0x5a, 0x7c,
+	0xaa, 0xb2, 0xd8, 0x5e, 0xb3, 0xd5, 0x07, 0x69, 0x18, 0xb9, 0x62, 0x24, 0x21, 0x15, 0x07, 0x33,
+	0x7e, 0xa7, 0xbe, 0x94, 0x54, 0x1c, 0xdb, 0x05, 0xa9, 0xa4, 0xfe, 0x18, 0xf0, 0x8b, 0xc2, 0x28,
+	0x39, 0x8e, 0x05, 0x71, 0x95, 0x13, 0xfb, 0xd1, 0x52, 0x63, 0x8e, 0x05, 0xd2, 0x9a, 0xce, 0x24,
+	0xe3, 0x8f, 0xa1, 0x34, 0xb5, 0x9c, 0xb1, 0x8c, 0x97, 0xe8, 0x90, 0xe5, 0xa4, 0x84, 0xa3, 0x71,
+	0x74, 0xd7, 0x19, 0xcb, 0xd7, 0x96, 0xa1, 0xbb, 0x1c, 0xdd, 0x75, 0xc6, 0xf8, 0x4f, 0xe1, 0xae,
+	0xb7, 0xbc, 0xcc, 0x91, 0x37, 0x38, 0xa5, 0xe7, 0x0b, 0x29, 0xad, 0x28, 0x91, 0xc8, 0x2a, 0xe2,
+	0xf8, 0x8f, 0xe1, 0x6a, 0x72, 0x67, 0x12, 0x5f, 0x6d, 0xc8, 0xd7, 0xf9, 0x8a, 0x8f, 0x3f, 0xee,
+	0x3e, 0x64, 0x9e, 0x0e, 0xf6, 0x33, 0x37, 0xfb, 0xb3, 0xf7, 0x26, 0xf2, 0x26, 0x5f, 0xe4, 0x27,
+	0xbf, 0xd5, 0xa5, 0x0b, 0xb9, 0x98, 0x2e, 0x3b, 0x44, 0x76, 0xda, 0x5e, 0x97, 0x3f, 0x59, 0x72,
+	0x88, 0xb2, 0x6d, 0xf8, 0xec, 0x24, 0xfc, 0x0d, 0x5c, 0xb3, 0xe7, 0x5b, 0xf4, 0xb2, 0xcc, 0x69,
+	0x6d, 0x5d, 0xb6, 0xa5, 0x4f, 0x16, 0x11, 0xc1, 0x6f, 0xd2, 0xab, 0x5c, 0x5e, 0x4b, 0xc8, 0x37,
+	0x96, 0x99, 0x7a, 0xae, 0xea, 0xc8, 0x4f, 0xc4, 0xbf, 0x81, 0xeb, 0xc6, 0xa2, 0xaa, 0x44, 0xbe,
+	0xc9, 0x29, 0x3e, 0xba, 0x04, 0xc5, 0x98, 0xd3, 0xc5, 0x84, 0xf0, 0x08, 0xae, 0x7a, 0xb3, 0x2d,
+	0x07, 0xf9, 0x16, 0xa7, 0xfe, 0xf0, 0x02, 0x7b, 0x9c, 0xc1, 0x26, 0xf3, 0x04, 0xa2, 0x60, 0x41,
+	0x4f, 0xe5, 0xdb, 0x4b, 0x83, 0x05, 0x3d, 0x25, 0x1c, 0x0d, 0xff, 0x1c, 0xd0, 0x78, 0x26, 0x5d,
+	0x95, 0x3f, 0xe5, 0x53, 0x1f, 0x5c, 0x94, 0xdd, 0xe5, 0x73, 0xdb, 0xb9, 0xe9, 0xd8, 0x02, 0x79,
+	0x7c, 0x41, 0x06, 0x2c, 0xdf, 0x59, 0x62, 0xfc, 0x17, 0xa5, 0xcd, 0xe4, 0x42, 0x72, 0x58, 0x83,
+	0xcd, 0xa8, 0x2f, 0x96, 0xf8, 0x36, 0xcd, 0xe0, 0x5d, 0x35, 0xf9, 0x2e, 0x5f, 0xe8, 0xf3, 0x0b,
+	0x22, 0xc8, 0x7c, 0x1b, 0x8e, 0x6c, 0xe8, 0x8b, 0x9a, 0x73, 0xbf, 0x86, 0x8d, 0xf1, 0x82, 0x24,
+	0x53, 0x6e, 0x2f, 0x21, 0xbf, 0x30, 0x2b, 0x5d, 0x48, 0x06, 0x87, 0x70, 0x7b, 0xbc, 0x24, 0x87,
+	0x95, 0xef, 0xf1, 0x65, 0x9e, 0x5e, 0x7e, 0x99, 0x58, 0x64, 0x4b, 0xc9, 0xb2, 0x4c, 0x66, 0x1c,
+	0xe7, 0x9a, 0xb2, 0xb2, 0x24, 0xb6, 0xa7, 0x19, 0x69, 0x3a, 0x81, 0xd9, 0xed, 0x78, 0x36, 0x53,
+	0x95, 0xef, 0x2f, 0xb1, 0xdb, 0xb9, 0xbc, 0x96, 0xcc, 0x13, 0x50, 0xfe, 0xae, 0x2c, 0xbe, 0x19,
+	0xad, 0xc1, 0x7a, 0x77, 0xd0, 0xef, 0xab, 0xdd, 0x11, 0x2a, 0xe0, 0x06, 0x54, 0xc5, 0x8b, 0xba,
+	0x87, 0x8a, 0xec, 0x75, 0x78, 0xb4, 0x3b, 0xec, 0x92, 0xde, 0xae, 0x8a, 0x4a, 0xfc, 0xf3, 0x51,
+	0x32, 0xd8, 0x3b, 0xea, 0xaa, 0x04, 0x95, 0x71, 0x05, 0x4a, 0x43, 0xb5, 0xbf, 0x87, 0xd6, 0x30,
+	0x82, 0x3a, 0x7b, 0xd2, 0x88, 0xda, 0x55, 0x7b, 0x87, 0x23, 0xb4, 0xce, 0x0a, 0x0c, 0x0e, 0x51,
+	0x09, 0x19, 0x10, 0x54, 0x61, 0x8b, 0x1c, 0xa8, 0xc3, 0x61, 0xe7, 0xb5, 0x8a, 0xaa, 0xbc, 0xb2,
+	0xe8, 0xbe, 0x45, 0xc0, 0x28, 0xbc, 0xda, 0x1f, 0xfc, 0x02, 0xd5, 0x70, 0x0b, 0x6a, 0x47, 0xfd,
+	0x74, 0xa9, 0x3a, 0xbf, 0x1a, 0x3e, 0xea, 0x76, 0xd5, 0xe1, 0x10, 0x35, 0x70, 0x15, 0xca, 0x11,
+	0xa1, 0x26, 0xab, 0x54, 0xba, 0xfb, 0x83, 0xa1, 0xaa, 0x25, 0x8c, 0xb4, 0x52, 0x58, 0x77, 0xd0,
+	0x1f, 0x1e, 0x1d, 0xa8, 0x04, 0x21, 0xbc, 0x01, 0x28, 0xc6, 0xd0, 0x62, 0x42, 0x57, 0xd9, 0x82,
+	0x87, 0xbd, 0xfe, 0x6b, 0x84, 0xf9, 0xd3, 0xa0, 0xff, 0x1a, 0x5d, 0xc3, 0x0f, 0xe0, 0x1e, 0x51,
+	0xf7, 0xd4, 0xfd, 0xde, 0x3b, 0x95, 0x68, 0x47, 0xfd, 0x4e, 0xf7, 0x6d, 0x7f, 0xf0, 0x8b, 0x7d,
+	0x75, 0xef, 0xb5, 0xba, 0xa7, 0x09, 0x9e, 0x87, 0x68, 0x03, 0xcb, 0xb0, 0x71, 0xd8, 0x21, 0xa3,
+	0xde, 0xa8, 0x37, 0xe8, 0xf3, 0x91, 0x51, 0x67, 0xaf, 0x33, 0xea, 0xa0, 0xeb, 0xf8, 0x1e, 0x7c,
+	0xba, 0x68, 0x44, 0x23, 0xea, 0xf0, 0x70, 0xd0, 0x1f, 0xaa, 0x68, 0x93, 0x7f, 0x0c, 0x31, 0x18,
+	0xbc, 0x3d, 0x3a, 0x44, 0x9f, 0xe0, 0x6b, 0xd0, 0x8a, 0x9e, 0x53, 0x04, 0x99, 0x6f, 0x41, 0x30,
+	0xaf, 0x0d, 0x47, 0x9d, 0xd1, 0x10, 0xdd, 0xc0, 0xb7, 0xe0, 0x93, 0x3c, 0x2c, 0x9d, 0x70, 0x93,
+	0xb1, 0x43, 0xd4, 0x4e, 0xf7, 0x8d, 0xba, 0xa7, 0x31, 0x39, 0x0f, 0x5e, 0x69, 0xa3, 0xc1, 0x61,
+	0xaf, 0x8b, 0x6e, 0x45, 0x6a, 0x51, 0xdf, 0xa2, 0xdb, 0xf8, 0x13, 0xb8, 0xf6, 0x5a, 0x1d, 0x69,
+	0xfb, 0x9d, 0xe1, 0x28, 0xde, 0x89, 0xd6, 0xdb, 0x43, 0x9f, 0xe2, 0x36, 0xdc, 0x5e, 0x30, 0x90,
+	0x92, 0xbf, 0x83, 0x6f, 0xc2, 0x66, 0xa7, 0x3b, 0xea, 0xbd, 0x4b, 0x65, 0xaa, 0x75, 0xdf, 0x74,
+	0xfa, 0xaf, 0x55, 0x74, 0x97, 0xf1, 0xc5, 0x66, 0xf3, 0xf5, 0x86, 0x6c, 0xe5, 0x7e, 0xe7, 0x40,
+	0x1d, 0x1e, 0x76, 0xba, 0x2a, 0x6a, 0xe3, 0x1f, 0x41, 0xfb, 0x82, 0xc1, 0x94, 0xfc, 0x3d, 0x66,
+	0x1e, 0x0c, 0x6b, 0xd8, 0x7d, 0xa3, 0x1e, 0x74, 0x90, 0x12, 0x73, 0x1a, 0xbd, 0xa7, 0x88, 0xf7,
+	0x1f, 0x7d, 0xc9, 0xef, 0x48, 0xb3, 0x1f, 0x7a, 0xf2, 0x6f, 0x9c, 0x07, 0x7d, 0x15, 0x5d, 0x61,
+	0x76, 0xb4, 0xff, 0xcd, 0xf3, 0xe8, 0x03, 0xe7, 0x6f, 0xf6, 0x7b, 0xbb, 0xa8, 0xc0, 0x9f, 0x86,
+	0xa3, 0x3d, 0x54, 0x7c, 0xf4, 0xaf, 0x45, 0xa8, 0x65, 0x8a, 0x31, 0x66, 0xa3, 0x47, 0x0e, 0xcb,
+	0x19, 0xc4, 0x3d, 0xc1, 0x15, 0x7c, 0x15, 0x1a, 0x71, 0xbc, 0xcd, 0x5c, 0x40, 0x1c, 0xb2, 0xba,
+	0xc9, 0x0f, 0xa8, 0x63, 0x88, 0x5b, 0x86, 0x02, 0xe3, 0xae, 0x13, 0x06, 0x27, 0xd4, 0x09, 0x2c,
+	0x23, 0xbd, 0xe5, 0x40, 0x45, 0xbc, 0x09, 0xb8, 0x13, 0xdd, 0x4a, 0x7f, 0x97, 0x81, 0x97, 0xd8,
+	0x5a, 0xb1, 0x5f, 0xdb, 0x0d, 0xfd, 0x73, 0x54, 0x66, 0x4a, 0x17, 0xf7, 0xc5, 0x7d, 0x37, 0x20,
+	0x54, 0x37, 0xcf, 0xd1, 0x1a, 0xb3, 0xbc, 0x38, 0x61, 0xdb, 0x8d, 0x7a, 0x3c, 0x3f, 0x0f, 0xdd,
+	0x40, 0x57, 0x3f, 0x18, 0x94, 0x9a, 0x34, 0xca, 0x4f, 0xd1, 0x3a, 0xfe, 0x1c, 0x1e, 0x2c, 0x45,
+	0xfb, 0x60, 0xd0, 0xe8, 0x62, 0xa5, 0xc2, 0xb6, 0x14, 0x5f, 0xa0, 0x44, 0xb3, 0xab, 0x4c, 0x5b,
+	0x2c, 0xbd, 0x9e, 0x4e, 0x5d, 0x2f, 0xa0, 0xa6, 0xa8, 0x0a, 0xa3, 0x41, 0x60, 0xf8, 0xdc, 0x6b,
+	0xf5, 0xdd, 0xe0, 0x95, 0x1b, 0x3a, 0x26, 0xaa, 0x31, 0xc3, 0x1a, 0x66, 0x3e, 0x17, 0x4b, 0x46,
+	0xea, 0xfc, 0x76, 0x26, 0x6e, 0x8a, 0xc5, 0xd0, 0x06, 0xdb, 0xd9, 0xc8, 0x75, 0x0f, 0x74, 0xe7,
+	0x9c, 0x44, 0x75, 0xb2, 0x8f, 0x9a, 0x8c, 0x08, 0xa7, 0x3b, 0xa2, 0xde, 0xc4, 0x72, 0xf4, 0x20,
+	0xde, 0x4c, 0x8b, 0x89, 0x26, 0xd9, 0x0c, 0x13, 0x0d, 0x3f, 0xa9, 0x3d, 0x87, 0x5f, 0x5e, 0x45,
+	0xac, 0xe8, 0x13, 0x8a, 0xae, 0x32, 0xd1, 0xf6, 0xf8, 0x15, 0x92, 0x1e, 0x58, 0xc7, 0x36, 0x8d,
+	0x9c, 0x17, 0xc2, 0x8f, 0xde, 0x02, 0xa4, 0xdf, 0x47, 0xb0, 0x63, 0x93, 0xbe, 0x89, 0x2f, 0xdf,
+	0xaf, 0x41, 0x2b, 0x85, 0xfd, 0xd2, 0xd0, 0xdf, 0x3d, 0x8d, 0x14, 0x9b, 0x02, 0x3b, 0x4c, 0x97,
+	0x3e, 0x2a, 0x3c, 0xfa, 0x5e, 0x82, 0xd6, 0xe1, 0xcc, 0x47, 0x89, 0x6b, 0x50, 0x38, 0x7b, 0x82,
+	0xae, 0xf0, 0x5f, 0x36, 0x93, 0xfd, 0xee, 0xa0, 0x02, 0xff, 0x7d, 0x86, 0x8a, 0xfc, 0xf7, 0x39,
+	0x2a, 0xf1, 0xdf, 0x9f, 0xa0, 0x32, 0xff, 0x7d, 0x81, 0xd6, 0xf8, 0xef, 0xef, 0xa3, 0x75, 0xfe,
+	0xfb, 0x25, 0xaa, 0xf0, 0xdf, 0xaf, 0x22, 0x67, 0x77, 0xf6, 0xf4, 0x09, 0x82, 0xe8, 0xe1, 0x29,
+	0xaa, 0x45, 0x0f, 0x3b, 0xa8, 0x1e, 0x3d, 0x3c, 0x43, 0x8d, 0xdd, 0x87, 0xa0, 0xb8, 0xde, 0x78,
+	0x5b, 0x9f, 0xb2, 0xe4, 0x22, 0x76, 0xe9, 0x86, 0x3b, 0x99, 0xb8, 0xce, 0xb6, 0x1e, 0xff, 0x33,
+	0xe1, 0x4d, 0xf1, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0x3f, 0x99, 0x3c, 0x38, 0xad, 0x30, 0x00,
+	0x00,
+}
diff --git a/pulsar/reader.go b/pulsar/reader.go
new file mode 100644
index 0000000..5592630
--- /dev/null
+++ b/pulsar/reader.go
@@ -0,0 +1,84 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package pulsar
+
+import "context"
+
+type ReaderMessage struct {
+	Reader
+	Message
+}
+
+type ReaderOptions struct {
+	// Specify the topic this consumer will subscribe on.
+	// This argument is required when constructing the reader.
+	Topic string
+
+	// Set the reader name.
+	Name string
+
+	// The initial reader positioning is done by specifying a message id. The options are:
+	//  * `pulsar.EarliestMessage` : Start reading from the earliest message available in the topic
+	//  * `pulsar.LatestMessage` : Start reading from the end topic, only getting messages published after the
+	//                           reader was created
+	//  * `MessageID` : Start reading from a particular message id, the reader will position itself on that
+	//                  specific position. The first message to be read will be the message next to the specified
+	//                  messageID
+	StartMessageID MessageID
+
+	// Sets a `MessageChannel` for the consumer
+	// When a message is received, it will be pushed to the channel for consumption
+	MessageChannel chan ReaderMessage
+
+	// Sets the size of the consumer receive queue.
+	// The consumer receive queue controls how many messages can be accumulated by the Reader before the
+	// application calls Reader.readNext(). Using a higher value could potentially increase the consumer
+	// throughput at the expense of bigger memory utilization.
+	//
+	// Default value is {@code 1000} messages and should be good for most use cases.
+	ReceiverQueueSize int
+
+	// Set the subscription role prefix. The default prefix is "reader".
+	SubscriptionRolePrefix string
+
+	// If enabled, the reader will read messages from the compacted topic rather than reading the full message backlog
+	// of the topic. This means that, if the topic has been compacted, the reader will only see the latest value for
+	// each key in the topic, up until the point in the topic message backlog that has been compacted. Beyond that
+	// point, the messages will be sent as normal.
+	//
+	// ReadCompacted can only be enabled when reading from a persistent topic. Attempting to enable it on non-persistent
+	// topics will lead to the reader create call throwing a PulsarClientException.
+	ReadCompacted bool
+}
+
+// A Reader can be used to scan through all the messages currently available in a topic.
+type Reader interface {
+	// The topic from which this reader is reading from
+	Topic() string
+
+	// Read the next message in the topic, blocking until a message is available
+	Next(context.Context) (Message, error)
+
+	// Check if there is any message available to read from the current position
+	HasNext() (bool, error)
+
+	// Close the reader and stop the broker to push more messages
+	Close() error
+}


[pulsar-client-go] 08/38: Added serialize method for string map

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit 0fe868ab5bff52ffc33430f74490beee8d608934
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Wed Apr 3 07:50:08 2019 -0700

    Added serialize method for string map
---
 pulsar/impl/commands.go      | 25 +++++++++++++++++++++++++
 pulsar/impl/commands_test.go | 26 ++++++++++++++++++++++++++
 2 files changed, 51 insertions(+)

diff --git a/pulsar/impl/commands.go b/pulsar/impl/commands.go
index c803602..ed9f6b9 100644
--- a/pulsar/impl/commands.go
+++ b/pulsar/impl/commands.go
@@ -32,3 +32,28 @@ func baseCommand(cmdType pb.BaseCommand_Type, msg proto.Message) *pb.BaseCommand
 	return cmd
 }
 
+func ConvertFromStringMap(m map[string]string) []*pb.KeyValue {
+	list := make([]*pb.KeyValue, len(m))
+
+	i := 0
+	for k, v := range m {
+		list[i] = &pb.KeyValue{
+			Key:   proto.String(k),
+			Value: proto.String(v),
+		}
+
+		i += 1
+	}
+
+	return list
+}
+
+func ConvertToStringMap(pbb []*pb.KeyValue) map[string]string {
+	m := make(map[string]string)
+
+	for _, kv := range pbb {
+		m[*kv.Key] = *kv.Value
+	}
+
+	return m
+}
diff --git a/pulsar/impl/commands_test.go b/pulsar/impl/commands_test.go
new file mode 100644
index 0000000..d9f94e6
--- /dev/null
+++ b/pulsar/impl/commands_test.go
@@ -0,0 +1,26 @@
+package impl
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestConvertStringMap(t *testing.T) {
+	m := make(map[string]string)
+	m["a"] = "1"
+	m["b"] = "2"
+
+	pbm := ConvertFromStringMap(m)
+
+	assert.Equal(t, 2, len(pbm))
+	assert.Equal(t, "a", *pbm[0].Key)
+	assert.Equal(t, "1", *pbm[0].Value)
+	assert.Equal(t, "b", *pbm[1].Key)
+	assert.Equal(t, "2", *pbm[1].Value)
+
+	m2 := ConvertToStringMap(pbm)
+	assert.Equal(t, 2, len(m2))
+	assert.Equal(t, "1", m2["a"])
+	assert.Equal(t, "2", m2["b"])
+}
+


[pulsar-client-go] 19/38: Support compression in producer

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit 0eb04d510ed5817685b3e1f7280ff4eb4fd2856e
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Fri May 3 12:34:36 2019 -0700

    Support compression in producer
---
 pulsar/impl/batch_builder.go      | 37 ++++++++++++++++--
 pulsar/impl_partition_producer.go |  8 ++--
 pulsar/producer_test.go           | 80 +++++++++++++++++++++++++++++++++++++++
 pulsar/util_test.go               | 12 ++++++
 4 files changed, 129 insertions(+), 8 deletions(-)

diff --git a/pulsar/impl/batch_builder.go b/pulsar/impl/batch_builder.go
index e8c0af8..0619154 100644
--- a/pulsar/impl/batch_builder.go
+++ b/pulsar/impl/batch_builder.go
@@ -3,6 +3,7 @@ package impl
 import (
 	"github.com/golang/protobuf/proto"
 	log "github.com/sirupsen/logrus"
+	"pulsar-client-go-native/pulsar/impl/compression"
 	pb "pulsar-client-go-native/pulsar/pulsar_proto"
 	"time"
 )
@@ -28,13 +29,16 @@ type BatchBuilder struct {
 	cmdSend     *pb.BaseCommand
 	msgMetadata *pb.MessageMetadata
 	callbacks   []interface{}
+
+	compressionProvider compression.Provider
 }
 
-func NewBatchBuilder(maxMessages uint, producerName string, producerId uint64) *BatchBuilder {
+func NewBatchBuilder(maxMessages uint, producerName string, producerId uint64,
+	compressionType pb.CompressionType) *BatchBuilder {
 	if maxMessages == 0 {
 		maxMessages = DefaultMaxMessagesPerBatch
 	}
-	return &BatchBuilder{
+	bb := &BatchBuilder{
 		buffer:       NewBuffer(4096),
 		numMessages:  0,
 		maxMessages:  maxMessages,
@@ -47,8 +51,15 @@ func NewBatchBuilder(maxMessages uint, producerName string, producerId uint64) *
 		msgMetadata: &pb.MessageMetadata{
 			ProducerName: &producerName,
 		},
-		callbacks: []interface{}{},
+		callbacks:           []interface{}{},
+		compressionProvider: getCompressionProvider(compressionType),
+	}
+
+	if compressionType != pb.CompressionType_NONE {
+		bb.msgMetadata.Compression = &compressionType
 	}
+
+	return bb
 }
 
 func (bb *BatchBuilder) IsFull() bool {
@@ -108,11 +119,29 @@ func (bb *BatchBuilder) Flush() (batchData []byte, sequenceId uint64, callbacks
 	bb.msgMetadata.NumMessagesInBatch = proto.Int32(int32(bb.numMessages))
 	bb.cmdSend.Send.NumMessages = proto.Int32(int32(bb.numMessages))
 
+	compressed := bb.compressionProvider.Compress(bb.buffer.ReadableSlice())
+
 	buffer := NewBuffer(4096)
-	serializeBatch(buffer, bb.cmdSend, bb.msgMetadata, bb.buffer.ReadableSlice())
+	serializeBatch(buffer, bb.cmdSend, bb.msgMetadata, compressed)
 
 	callbacks = bb.callbacks
 	sequenceId = bb.cmdSend.Send.GetSequenceId()
 	bb.reset()
 	return buffer.ReadableSlice(), sequenceId, callbacks
 }
+
+func getCompressionProvider(compressionType pb.CompressionType) compression.Provider {
+	switch compressionType {
+	case pb.CompressionType_NONE:
+		return compression.NoopProvider
+	case pb.CompressionType_LZ4:
+		return compression.Lz4Provider
+	case pb.CompressionType_ZLIB:
+		return compression.ZLibProvider
+	case pb.CompressionType_ZSTD:
+		return compression.ZStdProvider
+	default:
+		log.Panic("Unsupported compression type: ", compressionType)
+		return nil
+	}
+}
diff --git a/pulsar/impl_partition_producer.go b/pulsar/impl_partition_producer.go
index 2557d1d..b87384d 100644
--- a/pulsar/impl_partition_producer.go
+++ b/pulsar/impl_partition_producer.go
@@ -115,7 +115,8 @@ func (p *partitionProducer) grabCnx() error {
 
 	p.producerName = res.Response.ProducerSuccess.ProducerName
 	if p.batchBuilder == nil {
-		p.batchBuilder = impl.NewBatchBuilder(p.options.BatchingMaxMessages, *p.producerName, p.producerId)
+		p.batchBuilder = impl.NewBatchBuilder(p.options.BatchingMaxMessages, *p.producerName,
+			p.producerId, pb.CompressionType(p.options.CompressionType))
 	}
 	if p.sequenceIdGenerator == nil {
 		nextSequenceId := uint64(res.Response.ProducerSuccess.GetLastSequenceId() + 1)
@@ -235,8 +236,8 @@ func (p *partitionProducer) internalSend(request *sendRequest) {
 }
 
 type pendingItem struct {
-	batchData   []byte
-	sequenceId  uint64
+	batchData    []byte
+	sequenceId   uint64
 	sendRequests []interface{}
 }
 
@@ -393,4 +394,3 @@ type flushRequest struct {
 	waitGroup *sync.WaitGroup
 	err       error
 }
-
diff --git a/pulsar/producer_test.go b/pulsar/producer_test.go
new file mode 100644
index 0000000..ab3d739
--- /dev/null
+++ b/pulsar/producer_test.go
@@ -0,0 +1,80 @@
+package pulsar
+
+import (
+	"context"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestSimpleProducer(t *testing.T) {
+	client, err := NewClient(ClientOptions{
+		URL: serviceUrl,
+	})
+	assert.NoError(t, err)
+
+	producer, err := client.CreateProducer(ProducerOptions{
+		Topic: newTopicName(),
+	})
+
+	assert.NoError(t, err)
+	assert.NotNil(t, producer)
+
+	for i := 0; i < 10; i++ {
+		err := producer.Send(context.Background(), &ProducerMessage{
+			Payload: []byte("hello"),
+		})
+
+		assert.NoError(t, err)
+	}
+
+	err = producer.Close()
+	assert.NoError(t, err)
+
+	err = client.Close()
+	assert.NoError(t, err)
+}
+
+func TestProducerCompression(t *testing.T) {
+
+	type testProvider struct {
+		name            string
+		compressionType CompressionType
+	}
+
+	var providers = []testProvider{
+		{"zlib", ZLib},
+		{"lz4", LZ4},
+		{"zstd", ZSTD},
+	}
+
+	for _, p := range providers {
+		t.Run(p.name, func(t *testing.T) {
+			client, err := NewClient(ClientOptions{
+				URL: serviceUrl,
+			})
+			assert.NoError(t, err)
+
+			producer, err := client.CreateProducer(ProducerOptions{
+				Topic:           newTopicName(),
+				CompressionType: p.compressionType,
+			})
+
+			assert.NoError(t, err)
+			assert.NotNil(t, producer)
+
+			for i := 0; i < 10; i++ {
+				err := producer.Send(context.Background(), &ProducerMessage{
+					Payload: []byte("hello"),
+				})
+
+				assert.NoError(t, err)
+			}
+
+			err = producer.Close()
+			assert.NoError(t, err)
+
+			err = client.Close()
+			assert.NoError(t, err)
+		})
+	}
+}
diff --git a/pulsar/util_test.go b/pulsar/util_test.go
new file mode 100644
index 0000000..4bc67d3
--- /dev/null
+++ b/pulsar/util_test.go
@@ -0,0 +1,12 @@
+package pulsar
+
+import (
+	"fmt"
+	"time"
+)
+
+const serviceUrl = "pulsar://localhost:6650"
+
+func newTopicName() string {
+	return fmt.Sprintf("my-topic-%v", time.Now().Nanosecond())
+}


[pulsar-client-go] 04/38: Create producer session

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit ccc596a716e0a15944fc3d270f842cffad8ec1fb
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Sat Mar 30 07:47:31 2019 -0700

    Create producer session
---
 pulsar/impl/commands.go           |   4 ++
 pulsar/impl/connection.go         |   7 ++-
 pulsar/impl/connection_pool.go    |  14 +++--
 pulsar/impl/lookup_service.go     |  34 ++++++++++--
 pulsar/impl/rpc_client.go         |  30 +++++++---
 pulsar/impl_client.go             |  10 +++-
 pulsar/impl_partition_producer.go | 112 ++++++++++++++++++++++++++++----------
 pulsar/impl_producer.go           |  50 ++++++++---------
 pulsar/message.go                 |   4 +-
 pulsar/producer.go                |   4 +-
 10 files changed, 186 insertions(+), 83 deletions(-)

diff --git a/pulsar/impl/commands.go b/pulsar/impl/commands.go
index 90760e1..16e5b60 100644
--- a/pulsar/impl/commands.go
+++ b/pulsar/impl/commands.go
@@ -24,6 +24,10 @@ func baseCommand(cmdType pb.BaseCommand_Type, msg proto.Message) *pb.BaseCommand
 		cmd.PartitionMetadata = msg.(*pb.CommandPartitionedTopicMetadata)
 		break
 
+	case pb.BaseCommand_PRODUCER:
+		cmd.Producer = msg.(*pb.CommandProducer)
+		break
+
 	default:
 		log.Panic("Missing command type: ", cmdType)
 	}
diff --git a/pulsar/impl/connection.go b/pulsar/impl/connection.go
index 6379f6a..28871cb 100644
--- a/pulsar/impl/connection.go
+++ b/pulsar/impl/connection.go
@@ -5,6 +5,7 @@ import (
 	"github.com/golang/protobuf/proto"
 	log "github.com/sirupsen/logrus"
 	"net"
+	"net/url"
 	pb "pulsar-client-go-native/pulsar/pulsar_proto"
 	"sync"
 	"sync/atomic"
@@ -51,11 +52,11 @@ type connection struct {
 	pendingReqs      map[uint64]*request
 }
 
-func newConnection(logicalAddr string, physicalAddr string) *connection {
+func newConnection(logicalAddr *url.URL, physicalAddr *url.URL) *connection {
 	cnx := &connection{
 		state:        connectionInit,
-		logicalAddr:  logicalAddr,
-		physicalAddr: physicalAddr,
+		logicalAddr:  logicalAddr.Host,
+		physicalAddr: physicalAddr.Host,
 		writeBuffer:  NewBuffer(4096),
 		log:          log.WithField("raddr", physicalAddr),
 		pendingReqs:  make(map[uint64]*request),
diff --git a/pulsar/impl/connection_pool.go b/pulsar/impl/connection_pool.go
index 676f205..1fa647f 100644
--- a/pulsar/impl/connection_pool.go
+++ b/pulsar/impl/connection_pool.go
@@ -1,11 +1,13 @@
 package impl
 
 import (
+	log "github.com/sirupsen/logrus"
+	"net/url"
 	"sync"
 )
 
 type ConnectionPool interface {
-	GetConnection(logicalAddr string, physicalAddr string) (Connection, error)
+	GetConnection(logicalAddr *url.URL, physicalAddr *url.URL) (Connection, error)
 
 	// Close all the connections in the pool
 	Close()
@@ -19,11 +21,13 @@ func NewConnectionPool() ConnectionPool {
 	return &connectionPool{}
 }
 
-func (p *connectionPool) GetConnection(logicalAddr string, physicalAddr string) (Connection, error) {
-	cachedCnx, found := p.pool.Load(logicalAddr)
+func (p *connectionPool) GetConnection(logicalAddr *url.URL, physicalAddr *url.URL) (Connection, error) {
+	cachedCnx, found := p.pool.Load(logicalAddr.Host)
 	if found {
 		cnx := cachedCnx.(*connection)
-		if err := cnx.waitUntilReady(); err != nil {
+		log.Debug("Found connection in cache:", cnx.logicalAddr, cnx.physicalAddr)
+
+		if err := cnx.waitUntilReady(); err == nil {
 			// Connection is ready to be used
 			return cnx, nil
 		} else {
@@ -33,7 +37,7 @@ func (p *connectionPool) GetConnection(logicalAddr string, physicalAddr string)
 	}
 
 	// Try to create a new connection
-	newCnx, wasCached := p.pool.LoadOrStore(logicalAddr, newConnection(logicalAddr, physicalAddr))
+	newCnx, wasCached := p.pool.LoadOrStore(logicalAddr.Host, newConnection(logicalAddr, physicalAddr))
 	cnx := newCnx.(*connection)
 	if !wasCached {
 		cnx.start()
diff --git a/pulsar/impl/lookup_service.go b/pulsar/impl/lookup_service.go
index 0ebf424..afd0dda 100644
--- a/pulsar/impl/lookup_service.go
+++ b/pulsar/impl/lookup_service.go
@@ -1,6 +1,8 @@
 package impl
 
 import (
+	"errors"
+	"fmt"
 	log "github.com/sirupsen/logrus"
 	"net/url"
 	pb "pulsar-client-go-native/pulsar/pulsar_proto"
@@ -16,18 +18,22 @@ type LookupService interface {
 }
 
 type lookupService struct {
-	rpcClient RpcClient
+	rpcClient  RpcClient
+	serviceUrl *url.URL
 }
 
-func NewLookupService(rpcClient RpcClient) LookupService {
+func NewLookupService(rpcClient RpcClient, serviceUrl *url.URL) LookupService {
 	return &lookupService{
-		rpcClient: rpcClient,
+		rpcClient:  rpcClient,
+		serviceUrl: serviceUrl,
 	}
 }
 
 func (ls *lookupService) Lookup(topic string) (*LookupResult, error) {
 	// Follow brokers redirect up to certain number of times
-	for i := 0; i < 20; i++ {
+	const lookupResultMaxRedirect = 20
+
+	for i := 0; i < lookupResultMaxRedirect; i++ {
 		id := ls.rpcClient.NewRequestId()
 		res, err := ls.rpcClient.RequestToAnyBroker(id, pb.BaseCommand_LOOKUP, &pb.CommandLookupTopic{
 			RequestId: &id,
@@ -42,6 +48,7 @@ func (ls *lookupService) Lookup(topic string) (*LookupResult, error) {
 		lr := res.Response.LookupTopicResponse
 		switch *lr.Response {
 		case pb.CommandLookupTopicResponse_Redirect:
+			// TODO: Handle redirects
 			log.WithField("topic", topic).Infof("Follow redirect to broker. %v / %v - Use proxy: %v",
 				lr.BrokerServiceUrl, lr.BrokerServiceUrlTls, lr.ProxyThroughServiceUrl)
 			break
@@ -49,12 +56,27 @@ func (ls *lookupService) Lookup(topic string) (*LookupResult, error) {
 		case pb.CommandLookupTopicResponse_Connect:
 			log.WithField("topic", topic).Infof("Successfully looked up topic on broker. %s / %s - Use proxy: %t",
 				lr.GetBrokerServiceUrl(), lr.GetBrokerServiceUrlTls(), lr.GetProxyThroughServiceUrl())
-			return nil, nil
+
+			logicalAddress, err := url.Parse(lr.GetBrokerServiceUrl())
+			if err != nil {
+				return nil, err
+			}
+
+			var physicalAddr *url.URL
+			if lr.GetProxyThroughServiceUrl() {
+				physicalAddr = ls.serviceUrl
+			} else {
+				physicalAddr = logicalAddress
+			}
+			return &LookupResult{
+				LogicalAddr:  logicalAddress,
+				PhysicalAddr: physicalAddr,
+			}, nil
 
 		case pb.CommandLookupTopicResponse_Failed:
 			log.WithField("topic", topic).Warn("Failed to lookup topic",
 				lr.Error.String())
-			return nil, nil
+			return nil, errors.New(fmt.Sprintf("failed to lookup topic: %s", lr.Error.String()))
 		}
 	}
 
diff --git a/pulsar/impl/rpc_client.go b/pulsar/impl/rpc_client.go
index fd76569..2aa6106 100644
--- a/pulsar/impl/rpc_client.go
+++ b/pulsar/impl/rpc_client.go
@@ -17,31 +17,37 @@ type RpcClient interface {
 	// Create a new unique request id
 	NewRequestId() uint64
 
+	NewProducerId() uint64
+
+	NewConsumerId() uint64
+
 	// Send a request and block until the result is available
 	RequestToAnyBroker(requestId uint64, cmdType pb.BaseCommand_Type, message proto.Message) (*RpcResult, error)
 
-	Request(logicalAddr string, physicalAddr string, requestId uint64,
+	Request(logicalAddr *url.URL, physicalAddr *url.URL, requestId uint64,
 		cmdType pb.BaseCommand_Type, message proto.Message) (*RpcResult, error)
 }
 
 type rpcClient struct {
-	hostPort           string
-	pool               ConnectionPool
-	requestIdGenerator uint64
+	serviceUrl          *url.URL
+	pool                ConnectionPool
+	requestIdGenerator  uint64
+	producerIdGenerator uint64
+	consumerIdGenerator uint64
 }
 
 func NewRpcClient(serviceUrl *url.URL, pool ConnectionPool) RpcClient {
 	return &rpcClient{
-		hostPort: serviceUrl.Host,
-		pool:     pool,
+		serviceUrl: serviceUrl,
+		pool:       pool,
 	}
 }
 
 func (c *rpcClient) RequestToAnyBroker(requestId uint64, cmdType pb.BaseCommand_Type, message proto.Message) (*RpcResult, error) {
-	return c.Request(c.hostPort, c.hostPort, requestId, cmdType, message)
+	return c.Request(c.serviceUrl, c.serviceUrl, requestId, cmdType, message)
 }
 
-func (c *rpcClient) Request(logicalAddr string, physicalAddr string, requestId uint64,
+func (c *rpcClient) Request(logicalAddr *url.URL, physicalAddr *url.URL, requestId uint64,
 	cmdType pb.BaseCommand_Type, message proto.Message) (*RpcResult, error) {
 	// TODO: Add retry logic in case of connection issues
 	cnx, err := c.pool.GetConnection(logicalAddr, physicalAddr)
@@ -69,3 +75,11 @@ func (c *rpcClient) Request(logicalAddr string, physicalAddr string, requestId u
 func (c *rpcClient) NewRequestId() uint64 {
 	return atomic.AddUint64(&c.requestIdGenerator, 1)
 }
+
+func (c *rpcClient) NewProducerId() uint64 {
+	return atomic.AddUint64(&c.producerIdGenerator, 1)
+}
+
+func (c *rpcClient) NewConsumerId() uint64 {
+	return atomic.AddUint64(&c.consumerIdGenerator, 1)
+}
diff --git a/pulsar/impl_client.go b/pulsar/impl_client.go
index 4a7047f..a1f6e10 100644
--- a/pulsar/impl_client.go
+++ b/pulsar/impl_client.go
@@ -15,7 +15,9 @@ type client struct {
 	rpcClient     impl.RpcClient
 	lookupService impl.LookupService
 
-	handlers map[impl.Closable]bool
+	handlers            map[impl.Closable]bool
+	producerIdGenerator uint64
+	consumerIdGenerator uint64
 }
 
 func newClient(options ClientOptions) (Client, error) {
@@ -37,12 +39,13 @@ func newClient(options ClientOptions) (Client, error) {
 		cnxPool: impl.NewConnectionPool(),
 	}
 	c.rpcClient = impl.NewRpcClient(url, c.cnxPool)
-	c.lookupService = impl.NewLookupService(c.rpcClient)
+	c.lookupService = impl.NewLookupService(c.rpcClient, url)
+	c.handlers = make(map[impl.Closable]bool)
 	return c, nil
 }
 
 func (client *client) CreateProducer(options ProducerOptions) (Producer, error) {
-	producer, err := newProducer(client, options)
+	producer, err := newProducer(client, &options)
 	if err == nil {
 		client.handlers[producer] = true
 	}
@@ -92,6 +95,7 @@ func (client *client) TopicPartitions(topic string) ([]string, error) {
 	}
 }
 
+
 func (client *client) Close() error {
 	for handler := range client.handlers {
 		if err := handler.Close(); err != nil {
diff --git a/pulsar/impl_partition_producer.go b/pulsar/impl_partition_producer.go
index d035ad1..4ba5506 100644
--- a/pulsar/impl_partition_producer.go
+++ b/pulsar/impl_partition_producer.go
@@ -3,8 +3,8 @@ package pulsar
 import (
 	"context"
 	log "github.com/sirupsen/logrus"
-	//"pulsar-client-go-native/pulsar/impl"
-	//pb "pulsar-client-go-native/pulsar/pulsar_proto"
+	"pulsar-client-go-native/pulsar/impl"
+	pb "pulsar-client-go-native/pulsar/pulsar_proto"
 	"sync"
 )
 
@@ -14,12 +14,27 @@ type partitionProducer struct {
 	log    *log.Entry
 	mutex  sync.Mutex
 	cond   *sync.Cond
+	cnx    impl.Connection
+
+	producerName *string
+	producerId   uint64
+
+	// Channel where app is posting messages to be published
+	eventsChan chan interface{}
 }
 
 func newPartitionProducer(client *client, topic string, options *ProducerOptions) (*partitionProducer, error) {
 
 	p := &partitionProducer{
-		log: log.WithField("topic", topic),
+		log:        log.WithField("topic", topic),
+		client:     client,
+		topic:      topic,
+		producerId: client.rpcClient.NewProducerId(),
+		eventsChan: make(chan interface{}),
+	}
+
+	if options.Name != "" {
+		p.producerName = &options.Name
 	}
 
 	err := p.grabCnx()
@@ -27,55 +42,95 @@ func newPartitionProducer(client *client, topic string, options *ProducerOptions
 		log.WithError(err).Errorf("Failed to create producer")
 		return nil, err
 	} else {
-		log.Info("Created producer")
+		log.Info("Created producer on cnx: ")
+		go p.run()
 		return p, nil
 	}
 }
 
 func (p *partitionProducer) grabCnx() error {
-	//lr, err := p.client.lookupService.Lookup(p.topic)
-	//if err != nil {
-	//	p.log.WithError(err).Warn("Failed to lookup topic")
-	//	return err
-	//}
-
-	//id := p.client.rpcClient.NewRequestId()
-	//p.client.rpcClient.Request(lr.LogicalAddr.Host, lr.PhysicalAddr.Host, id, pb.BaseCommand_PRODUCER, *pb.CommandProducer{
-	//
-	//})
-	//
-	//var cnx impl.Connection
-	//cnx, err = p.client.cnxPool.GetConnection(lr.LogicalAddr.Host, lr.PhysicalAddr.Host)
-	//if err != nil {
-	//	p.log.WithError(err).Warn("Failed to get connection")
-	//	return err
-	//}
-
-	//cnx.
+	lr, err := p.client.lookupService.Lookup(p.topic)
+	if err != nil {
+		p.log.WithError(err).Warn("Failed to lookup topic")
+		return err
+	}
+
+	p.log.Info("Lookup result: ", lr)
+	id := p.client.rpcClient.NewRequestId()
+	res, err := p.client.rpcClient.Request(lr.LogicalAddr, lr.PhysicalAddr, id, pb.BaseCommand_PRODUCER, &pb.CommandProducer{
+		RequestId:    &id,
+		Topic:        &p.topic,
+		Encrypted:    nil,
+		Metadata:     nil,
+		ProducerId:   &p.producerId,
+		ProducerName: p.producerName,
+		Schema:       nil,
+	})
+
+	if err != nil {
+		p.log.WithError(err).Error("Failed to create producer")
+		return err
+	}
 
+	p.producerName = res.Response.ProducerSuccess.ProducerName
+	p.cnx = res.Cnx
+	p.log.WithField("cnx", res.Cnx).Info("Created producer")
 	return nil
 }
 
 func (p *partitionProducer) run() {
-
+	for {
+		i := <-p.eventsChan
+		switch v := i.(type) {
+		case *sendRequest:
+			p.log.Info("Received send request: ", v)
+			v.callback(nil, v.msg, nil)
+		}
+	}
 }
 
 func (p *partitionProducer) Topic() string {
-	return ""
+	return p.topic
 }
 
 func (p *partitionProducer) Name() string {
-	return ""
+	return *p.producerName
 }
 
-func (p *partitionProducer) Send(context.Context, ProducerMessage) error {
+func (p *partitionProducer) Send(ctx context.Context, msg *ProducerMessage) error {
+	wg := sync.WaitGroup{}
+	wg.Add(1)
+
+	var err error
+
+	p.SendAsync(ctx, msg, func(ID MessageID, message *ProducerMessage, e error) {
+		err = e
+		wg.Done()
+	})
+
+	// When sending synchronously we flush immediately to avoid
+	// the increased latency and reduced throughput of batching
+	if err = p.Flush(); err != nil {
+		return err
+	}
+
+	wg.Wait()
+	return err
 	return nil
 }
 
-func (p *partitionProducer) SendAsync(context.Context, ProducerMessage, func(ProducerMessage, error)) {
+type sendRequest struct {
+	ctx      context.Context
+	msg      *ProducerMessage
+	callback func(MessageID, *ProducerMessage, error)
+}
+
+func (p *partitionProducer) SendAsync(ctx context.Context, msg *ProducerMessage, callback func(MessageID, *ProducerMessage, error)) {
+	p.eventsChan <- &sendRequest{ctx, msg, callback}
 }
 
 func (p *partitionProducer) LastSequenceID() int64 {
+	// TODO: return real last sequence id
 	return -1
 }
 
@@ -84,5 +139,6 @@ func (p *partitionProducer) Flush() error {
 }
 
 func (p *partitionProducer) Close() error {
+	p.log.Info("Closing producer")
 	return nil
 }
diff --git a/pulsar/impl_producer.go b/pulsar/impl_producer.go
index 587b09d..abedfab 100644
--- a/pulsar/impl_producer.go
+++ b/pulsar/impl_producer.go
@@ -2,18 +2,16 @@ package pulsar
 
 import (
 	"context"
-	"fmt"
 	"pulsar-client-go-native/pulsar/impl"
-	"sync"
 )
 
 type producer struct {
 	topic         string
 	producers     []Producer
-	messageRouter func(ProducerMessage, TopicMetadata) int
+	messageRouter func(*ProducerMessage, TopicMetadata) int
 }
 
-func newProducer(client *client, options ProducerOptions) (*producer, error) {
+func newProducer(client *client, options *ProducerOptions) (*producer, error) {
 	if options.Topic == "" {
 		return nil, newError(ResultInvalidTopicName, "Topic name is required for producer")
 	}
@@ -24,7 +22,7 @@ func newProducer(client *client, options ProducerOptions) (*producer, error) {
 
 	if options.MessageRouter == nil {
 		internalRouter := impl.NewDefaultRouter(options.BatchingMaxPublishDelay)
-		p.messageRouter = func(message ProducerMessage, metadata TopicMetadata) int {
+		p.messageRouter = func(message *ProducerMessage, metadata TopicMetadata) int {
 			return internalRouter(metadata.NumPartitions())
 		}
 	}
@@ -45,12 +43,10 @@ func newProducer(client *client, options ProducerOptions) (*producer, error) {
 
 	c := make(chan ProducerError, numPartitions)
 
-	for i := 0; i < numPartitions; i++ {
-		partition := i
+	for partitionIdx, partition := range partitions {
 		go func() {
-			partitionName := fmt.Sprintf("%s-partition-%d", options.Topic, partition)
-			prod, err := newPartitionProducer(client, partitionName, &options)
-			c <- ProducerError{partition, prod, err}
+			prod, err := newPartitionProducer(client, partition, options)
+			c <- ProducerError{partitionIdx, prod, err}
 		}()
 	}
 
@@ -85,22 +81,12 @@ func (p *producer) NumPartitions() uint32 {
 	return uint32(len(p.producers))
 }
 
-func (p *producer) Send(ctx context.Context, msg ProducerMessage) error {
-	wg := sync.WaitGroup{}
-	wg.Add(1)
-
-	var err error
-
-	p.SendAsync(ctx, msg, func(message ProducerMessage, e error) {
-		err = e
-		wg.Done()
-	})
-
-	wg.Wait()
-	return err
+func (p *producer) Send(ctx context.Context, msg *ProducerMessage) error {
+	partition := p.messageRouter(msg, p)
+	return p.producers[partition].Send(ctx, msg)
 }
 
-func (p *producer) SendAsync(ctx context.Context, msg ProducerMessage, callback func(ProducerMessage, error)) {
+func (p *producer) SendAsync(ctx context.Context, msg *ProducerMessage, callback func(MessageID, *ProducerMessage, error)) {
 	partition := p.messageRouter(msg, p)
 	p.producers[partition].SendAsync(ctx, msg, callback)
 }
@@ -117,9 +103,21 @@ func (p *producer) LastSequenceID() int64 {
 }
 
 func (p *producer) Flush() error {
-	return nil
+	var err error = nil
+	for _, pp := range p.producers {
+		if e := pp.Flush(); e != nil && err == nil {
+			err = e
+		}
+	}
+	return err
 }
 
 func (p *producer) Close() error {
-	return nil
+	var err error = nil
+	for _, pp := range p.producers {
+		if e := pp.Close(); e != nil && err == nil {
+			err = e
+		}
+	}
+	return err
 }
diff --git a/pulsar/message.go b/pulsar/message.go
index ad61704..60dd989 100644
--- a/pulsar/message.go
+++ b/pulsar/message.go
@@ -83,8 +83,8 @@ func DeserializeMessageID(data []byte) MessageID {
 
 var (
 // MessageID that points to the earliest message avaialable in a topic
-//EarliestMessage MessageID = earliestMessageID()
+// TODO: EarliestMessage MessageID = earliestMessageID()
 
 // MessageID that points to the latest message
-//LatestMessage MessageID = latestMessageID()
+// TODO: LatestMessage MessageID = latestMessageID()
 )
diff --git a/pulsar/producer.go b/pulsar/producer.go
index 90769cb..0e59e66 100644
--- a/pulsar/producer.go
+++ b/pulsar/producer.go
@@ -161,12 +161,12 @@ type Producer interface {
 	// This call will be blocking until is successfully acknowledged by the Pulsar broker.
 	// Example:
 	// producer.Send(ctx, pulsar.ProducerMessage{ Payload: myPayload })
-	Send(context.Context, ProducerMessage) error
+	Send(context.Context, *ProducerMessage) error
 
 	// Send a message in asynchronous mode
 	// The callback will report back the message being published and
 	// the eventual error in publishing
-	SendAsync(context.Context, ProducerMessage, func(ProducerMessage, error))
+	SendAsync(context.Context, *ProducerMessage, func(MessageID, *ProducerMessage, error))
 
 	// Get the last sequence id that was published by this producer.
 	// This represent either the automatically assigned or custom sequence id (set on the ProducerMessage) that


[pulsar-client-go] 33/38: Renamed `impl` package to `internal`

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit d6dbcfa0037e899417d1cf5536849bf918de8d26
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Tue May 7 16:38:57 2019 -0700

    Renamed `impl` package to `internal`
---
 pulsar/impl_client.go                              | 22 +++++++++++-----------
 pulsar/impl_message.go                             |  2 +-
 pulsar/impl_partition_producer.go                  | 20 ++++++++++----------
 pulsar/impl_producer.go                            | 12 ++++++------
 pulsar/{impl => internal}/backoff.go               |  2 +-
 pulsar/{impl => internal}/batch_builder.go         |  6 +++---
 pulsar/{impl => internal}/buffer.go                |  2 +-
 pulsar/{impl => internal}/buffer_test.go           |  2 +-
 pulsar/{impl => internal}/checksum.go              |  2 +-
 pulsar/{impl => internal}/closable.go              |  2 +-
 pulsar/{impl => internal}/commands.go              |  4 ++--
 pulsar/{impl => internal}/commands_test.go         |  2 +-
 .../{impl => internal}/compression/compression.go  |  0
 .../compression/compression_test.go                |  0
 pulsar/{impl => internal}/compression/lz4.go       |  0
 pulsar/{impl => internal}/compression/noop.go      |  0
 pulsar/{impl => internal}/compression/zlib.go      |  0
 pulsar/{impl => internal}/compression/zstd.go      |  0
 pulsar/{impl => internal}/connection.go            |  4 ++--
 pulsar/{impl => internal}/connection_pool.go       |  2 +-
 pulsar/{impl => internal}/connection_reader.go     |  4 ++--
 pulsar/{impl => internal}/default_router.go        |  2 +-
 pulsar/{impl => internal}/default_router_test.go   |  2 +-
 pulsar/{impl => internal}/hash.go                  |  2 +-
 pulsar/{impl => internal}/hash_test.go             |  2 +-
 pulsar/{impl => internal}/lookup_service.go        |  4 ++--
 pulsar/{impl => internal}/lookup_service_test.go   |  4 ++--
 pulsar/{ => internal}/pulsar_proto/PulsarApi.pb.go |  0
 pulsar/{impl => internal}/rpc_client.go            |  4 ++--
 pulsar/{impl => internal}/topic_name.go            |  2 +-
 pulsar/{impl => internal}/topic_name_test.go       |  2 +-
 pulsar/{impl => internal}/util/blocking_queue.go   |  0
 .../{impl => internal}/util/blocking_queue_test.go |  0
 pulsar/{impl => internal}/util/semaphore.go        |  0
 pulsar/{impl => internal}/utils.go                 |  2 +-
 pulsar/producer_test.go                            |  2 +-
 36 files changed, 58 insertions(+), 58 deletions(-)

diff --git a/pulsar/impl_client.go b/pulsar/impl_client.go
index 89b955d..0db2ee8 100644
--- a/pulsar/impl_client.go
+++ b/pulsar/impl_client.go
@@ -23,18 +23,18 @@ import (
 	"fmt"
 	log "github.com/sirupsen/logrus"
 	"net/url"
-	"pulsar-client-go/pulsar/impl"
-	pb "pulsar-client-go/pulsar/pulsar_proto"
+	"pulsar-client-go/pulsar/internal"
+	pb "pulsar-client-go/pulsar/internal/pulsar_proto"
 )
 
 type client struct {
 	options ClientOptions
 
-	cnxPool       impl.ConnectionPool
-	rpcClient     impl.RpcClient
-	lookupService impl.LookupService
+	cnxPool       internal.ConnectionPool
+	rpcClient     internal.RpcClient
+	lookupService internal.LookupService
 
-	handlers            map[impl.Closable]bool
+	handlers            map[internal.Closable]bool
 	producerIdGenerator uint64
 	consumerIdGenerator uint64
 }
@@ -55,11 +55,11 @@ func newClient(options ClientOptions) (Client, error) {
 	}
 
 	c := &client{
-		cnxPool: impl.NewConnectionPool(),
+		cnxPool: internal.NewConnectionPool(),
 	}
-	c.rpcClient = impl.NewRpcClient(url, c.cnxPool)
-	c.lookupService = impl.NewLookupService(c.rpcClient, url)
-	c.handlers = make(map[impl.Closable]bool)
+	c.rpcClient = internal.NewRpcClient(url, c.cnxPool)
+	c.lookupService = internal.NewLookupService(c.rpcClient, url)
+	c.handlers = make(map[internal.Closable]bool)
 	return c, nil
 }
 
@@ -82,7 +82,7 @@ func (client *client) CreateReader(options ReaderOptions) (Reader, error) {
 }
 
 func (client *client) TopicPartitions(topic string) ([]string, error) {
-	topicName, err := impl.ParseTopicName(topic)
+	topicName, err := internal.ParseTopicName(topic)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pulsar/impl_message.go b/pulsar/impl_message.go
index 24f3f30..b73445f 100644
--- a/pulsar/impl_message.go
+++ b/pulsar/impl_message.go
@@ -21,7 +21,7 @@ package pulsar
 
 import (
 	"github.com/golang/protobuf/proto"
-	pb "pulsar-client-go/pulsar/pulsar_proto"
+	pb "pulsar-client-go/pulsar/internal/pulsar_proto"
 )
 
 type messageId struct {
diff --git a/pulsar/impl_partition_producer.go b/pulsar/impl_partition_producer.go
index 41e7702..0e38fa6 100644
--- a/pulsar/impl_partition_producer.go
+++ b/pulsar/impl_partition_producer.go
@@ -23,9 +23,9 @@ import (
 	"context"
 	"github.com/golang/protobuf/proto"
 	log "github.com/sirupsen/logrus"
-	"pulsar-client-go/pulsar/impl"
-	"pulsar-client-go/pulsar/impl/util"
-	pb "pulsar-client-go/pulsar/pulsar_proto"
+	"pulsar-client-go/pulsar/internal"
+	"pulsar-client-go/pulsar/internal/util"
+	pb "pulsar-client-go/pulsar/internal/pulsar_proto"
 	"sync"
 	"sync/atomic"
 	"time"
@@ -45,12 +45,12 @@ type partitionProducer struct {
 	client *client
 	topic  string
 	log    *log.Entry
-	cnx    impl.Connection
+	cnx    internal.Connection
 
 	options             *ProducerOptions
 	producerName        *string
 	producerId          uint64
-	batchBuilder        *impl.BatchBuilder
+	batchBuilder        *internal.BatchBuilder
 	sequenceIdGenerator *uint64
 	batchFlushTicker    *time.Ticker
 
@@ -140,7 +140,7 @@ func (p *partitionProducer) grabCnx() error {
 
 	p.producerName = res.Response.ProducerSuccess.ProducerName
 	if p.batchBuilder == nil {
-		p.batchBuilder = impl.NewBatchBuilder(p.options.BatchingMaxMessages, *p.producerName,
+		p.batchBuilder = internal.NewBatchBuilder(p.options.BatchingMaxMessages, *p.producerName,
 			p.producerId, pb.CompressionType(p.options.CompressionType))
 	}
 	if p.sequenceIdGenerator == nil {
@@ -170,7 +170,7 @@ func (p *partitionProducer) ConnectionClosed() {
 
 func (p *partitionProducer) reconnectToBroker() {
 	p.log.Info("Reconnecting to broker")
-	backoff := impl.Backoff{}
+	backoff := internal.Backoff{}
 	for {
 		if p.state != producerReady {
 			// Producer is already closing
@@ -231,7 +231,7 @@ func (p *partitionProducer) internalSend(request *sendRequest) {
 	}
 
 	if msg.EventTime != nil {
-		smm.EventTime = proto.Uint64(impl.TimestampMillis(*msg.EventTime))
+		smm.EventTime = proto.Uint64(internal.TimestampMillis(*msg.EventTime))
 	}
 
 	if msg.Key != "" {
@@ -239,10 +239,10 @@ func (p *partitionProducer) internalSend(request *sendRequest) {
 	}
 
 	if msg.Properties != nil {
-		smm.Properties = impl.ConvertFromStringMap(msg.Properties)
+		smm.Properties = internal.ConvertFromStringMap(msg.Properties)
 	}
 
-	sequenceId := impl.GetAndAdd(p.sequenceIdGenerator, 1)
+	sequenceId := internal.GetAndAdd(p.sequenceIdGenerator, 1)
 
 	if sendAsBatch {
 		for ; p.batchBuilder.Add(smm, sequenceId, msg.Payload, request, msg.ReplicationClusters) == false; {
diff --git a/pulsar/impl_producer.go b/pulsar/impl_producer.go
index c379531..afe8b3a 100644
--- a/pulsar/impl_producer.go
+++ b/pulsar/impl_producer.go
@@ -21,7 +21,7 @@ package pulsar
 
 import (
 	"context"
-	"pulsar-client-go/pulsar/impl"
+	"pulsar-client-go/pulsar/internal"
 )
 
 type producer struct {
@@ -33,11 +33,11 @@ type producer struct {
 func getHashingFunction(s HashingScheme) func(string) uint32 {
 	switch s {
 	case JavaStringHash:
-		return impl.JavaStringHash
+		return internal.JavaStringHash
 	case Murmur3_32Hash:
-		return impl.Murmur3_32Hash
+		return internal.Murmur3_32Hash
 	default:
-		return impl.JavaStringHash
+		return internal.JavaStringHash
 	}
 }
 
@@ -51,8 +51,8 @@ func newProducer(client *client, options *ProducerOptions) (*producer, error) {
 	}
 
 	if options.MessageRouter == nil {
-		internalRouter := impl.NewDefaultRouter(
-			impl.NewSystemClock(),
+		internalRouter := internal.NewDefaultRouter(
+			internal.NewSystemClock(),
 			getHashingFunction(options.HashingScheme),
 			options.BatchingMaxPublishDelay)
 		p.messageRouter = func(message *ProducerMessage, metadata TopicMetadata) int {
diff --git a/pulsar/impl/backoff.go b/pulsar/internal/backoff.go
similarity index 98%
rename from pulsar/impl/backoff.go
rename to pulsar/internal/backoff.go
index 3e7629e..94a185e 100644
--- a/pulsar/impl/backoff.go
+++ b/pulsar/internal/backoff.go
@@ -17,7 +17,7 @@
 // under the License.
 //
 
-package impl
+package internal
 
 import (
 	"time"
diff --git a/pulsar/impl/batch_builder.go b/pulsar/internal/batch_builder.go
similarity index 97%
rename from pulsar/impl/batch_builder.go
rename to pulsar/internal/batch_builder.go
index a3b41a2..5c18850 100644
--- a/pulsar/impl/batch_builder.go
+++ b/pulsar/internal/batch_builder.go
@@ -17,13 +17,13 @@
 // under the License.
 //
 
-package impl
+package internal
 
 import (
 	"github.com/golang/protobuf/proto"
 	log "github.com/sirupsen/logrus"
-	"pulsar-client-go/pulsar/impl/compression"
-	pb "pulsar-client-go/pulsar/pulsar_proto"
+	"pulsar-client-go/pulsar/internal/compression"
+	pb "pulsar-client-go/pulsar/internal/pulsar_proto"
 	"time"
 )
 
diff --git a/pulsar/impl/buffer.go b/pulsar/internal/buffer.go
similarity index 99%
rename from pulsar/impl/buffer.go
rename to pulsar/internal/buffer.go
index bf92b8f..cadf041 100644
--- a/pulsar/impl/buffer.go
+++ b/pulsar/internal/buffer.go
@@ -17,7 +17,7 @@
 // under the License.
 //
 
-package impl
+package internal
 
 import (
 	"encoding/binary"
diff --git a/pulsar/impl/buffer_test.go b/pulsar/internal/buffer_test.go
similarity index 98%
rename from pulsar/impl/buffer_test.go
rename to pulsar/internal/buffer_test.go
index e6ace40..72a9ee8 100644
--- a/pulsar/impl/buffer_test.go
+++ b/pulsar/internal/buffer_test.go
@@ -17,7 +17,7 @@
 // under the License.
 //
 
-package impl
+package internal
 
 import (
 	"testing"
diff --git a/pulsar/impl/checksum.go b/pulsar/internal/checksum.go
similarity index 98%
rename from pulsar/impl/checksum.go
rename to pulsar/internal/checksum.go
index 906c4dc..eba1460 100644
--- a/pulsar/impl/checksum.go
+++ b/pulsar/internal/checksum.go
@@ -17,7 +17,7 @@
 // under the License.
 //
 
-package impl
+package internal
 
 import "hash/crc32"
 
diff --git a/pulsar/impl/closable.go b/pulsar/internal/closable.go
similarity index 98%
rename from pulsar/impl/closable.go
rename to pulsar/internal/closable.go
index 3805556..77801a4 100644
--- a/pulsar/impl/closable.go
+++ b/pulsar/internal/closable.go
@@ -17,7 +17,7 @@
 // under the License.
 //
 
-package impl
+package internal
 
 type Closable interface {
 	Close() error
diff --git a/pulsar/impl/commands.go b/pulsar/internal/commands.go
similarity index 98%
rename from pulsar/impl/commands.go
rename to pulsar/internal/commands.go
index 949d56d..b375817 100644
--- a/pulsar/impl/commands.go
+++ b/pulsar/internal/commands.go
@@ -17,12 +17,12 @@
 // under the License.
 //
 
-package impl
+package internal
 
 import (
 	"github.com/golang/protobuf/proto"
 	log "github.com/sirupsen/logrus"
-	pb "pulsar-client-go/pulsar/pulsar_proto"
+	pb "pulsar-client-go/pulsar/internal/pulsar_proto"
 )
 
 const MaxFrameSize = 5 * 1024 * 1024
diff --git a/pulsar/impl/commands_test.go b/pulsar/internal/commands_test.go
similarity index 98%
rename from pulsar/impl/commands_test.go
rename to pulsar/internal/commands_test.go
index a1f08a8..7f510f1 100644
--- a/pulsar/impl/commands_test.go
+++ b/pulsar/internal/commands_test.go
@@ -17,7 +17,7 @@
 // under the License.
 //
 
-package impl
+package internal
 
 import (
 	"github.com/stretchr/testify/assert"
diff --git a/pulsar/impl/compression/compression.go b/pulsar/internal/compression/compression.go
similarity index 100%
rename from pulsar/impl/compression/compression.go
rename to pulsar/internal/compression/compression.go
diff --git a/pulsar/impl/compression/compression_test.go b/pulsar/internal/compression/compression_test.go
similarity index 100%
rename from pulsar/impl/compression/compression_test.go
rename to pulsar/internal/compression/compression_test.go
diff --git a/pulsar/impl/compression/lz4.go b/pulsar/internal/compression/lz4.go
similarity index 100%
rename from pulsar/impl/compression/lz4.go
rename to pulsar/internal/compression/lz4.go
diff --git a/pulsar/impl/compression/noop.go b/pulsar/internal/compression/noop.go
similarity index 100%
rename from pulsar/impl/compression/noop.go
rename to pulsar/internal/compression/noop.go
diff --git a/pulsar/impl/compression/zlib.go b/pulsar/internal/compression/zlib.go
similarity index 100%
rename from pulsar/impl/compression/zlib.go
rename to pulsar/internal/compression/zlib.go
diff --git a/pulsar/impl/compression/zstd.go b/pulsar/internal/compression/zstd.go
similarity index 100%
rename from pulsar/impl/compression/zstd.go
rename to pulsar/internal/compression/zstd.go
diff --git a/pulsar/impl/connection.go b/pulsar/internal/connection.go
similarity index 99%
rename from pulsar/impl/connection.go
rename to pulsar/internal/connection.go
index aa1c809..affa8cc 100644
--- a/pulsar/impl/connection.go
+++ b/pulsar/internal/connection.go
@@ -17,7 +17,7 @@
 // under the License.
 //
 
-package impl
+package internal
 
 import (
 	"errors"
@@ -25,7 +25,7 @@ import (
 	log "github.com/sirupsen/logrus"
 	"net"
 	"net/url"
-	pb "pulsar-client-go/pulsar/pulsar_proto"
+	pb "pulsar-client-go/pulsar/internal/pulsar_proto"
 	"sync"
 	"sync/atomic"
 	"time"
diff --git a/pulsar/impl/connection_pool.go b/pulsar/internal/connection_pool.go
similarity index 99%
rename from pulsar/impl/connection_pool.go
rename to pulsar/internal/connection_pool.go
index 474f2f8..4b56484 100644
--- a/pulsar/impl/connection_pool.go
+++ b/pulsar/internal/connection_pool.go
@@ -17,7 +17,7 @@
 // under the License.
 //
 
-package impl
+package internal
 
 import (
 	log "github.com/sirupsen/logrus"
diff --git a/pulsar/impl/connection_reader.go b/pulsar/internal/connection_reader.go
similarity index 98%
rename from pulsar/impl/connection_reader.go
rename to pulsar/internal/connection_reader.go
index fd753d1..836d7ce 100644
--- a/pulsar/impl/connection_reader.go
+++ b/pulsar/internal/connection_reader.go
@@ -17,14 +17,14 @@
 // under the License.
 //
 
-package impl
+package internal
 
 import (
 	"bufio"
 	"github.com/golang/protobuf/proto"
 	"github.com/pkg/errors"
 	"io"
-	pb "pulsar-client-go/pulsar/pulsar_proto"
+	pb "pulsar-client-go/pulsar/internal/pulsar_proto"
 )
 
 type connectionReader struct {
diff --git a/pulsar/impl/default_router.go b/pulsar/internal/default_router.go
similarity index 99%
rename from pulsar/impl/default_router.go
rename to pulsar/internal/default_router.go
index 20db625..cbdff53 100644
--- a/pulsar/impl/default_router.go
+++ b/pulsar/internal/default_router.go
@@ -17,7 +17,7 @@
 // under the License.
 //
 
-package impl
+package internal
 
 import (
 	"math/rand"
diff --git a/pulsar/impl/default_router_test.go b/pulsar/internal/default_router_test.go
similarity index 99%
rename from pulsar/impl/default_router_test.go
rename to pulsar/internal/default_router_test.go
index 8cad90d..0134c99 100644
--- a/pulsar/impl/default_router_test.go
+++ b/pulsar/internal/default_router_test.go
@@ -17,7 +17,7 @@
 // under the License.
 //
 
-package impl
+package internal
 
 import (
 	"github.com/stretchr/testify/assert"
diff --git a/pulsar/impl/hash.go b/pulsar/internal/hash.go
similarity index 98%
rename from pulsar/impl/hash.go
rename to pulsar/internal/hash.go
index 61f580b..5ec7df8 100644
--- a/pulsar/impl/hash.go
+++ b/pulsar/internal/hash.go
@@ -17,7 +17,7 @@
 // under the License.
 //
 
-package impl
+package internal
 
 import "github.com/spaolacci/murmur3"
 
diff --git a/pulsar/impl/hash_test.go b/pulsar/internal/hash_test.go
similarity index 98%
rename from pulsar/impl/hash_test.go
rename to pulsar/internal/hash_test.go
index b4f41d5..79c4c62 100644
--- a/pulsar/impl/hash_test.go
+++ b/pulsar/internal/hash_test.go
@@ -17,7 +17,7 @@
 // under the License.
 //
 
-package impl
+package internal
 
 import (
 	"github.com/stretchr/testify/assert"
diff --git a/pulsar/impl/lookup_service.go b/pulsar/internal/lookup_service.go
similarity index 98%
rename from pulsar/impl/lookup_service.go
rename to pulsar/internal/lookup_service.go
index 13c74a7..1ec8841 100644
--- a/pulsar/impl/lookup_service.go
+++ b/pulsar/internal/lookup_service.go
@@ -17,7 +17,7 @@
 // under the License.
 //
 
-package impl
+package internal
 
 import (
 	"errors"
@@ -25,7 +25,7 @@ import (
 	"github.com/golang/protobuf/proto"
 	log "github.com/sirupsen/logrus"
 	"net/url"
-	pb "pulsar-client-go/pulsar/pulsar_proto"
+	pb "pulsar-client-go/pulsar/internal/pulsar_proto"
 )
 
 type LookupResult struct {
diff --git a/pulsar/impl/lookup_service_test.go b/pulsar/internal/lookup_service_test.go
similarity index 99%
rename from pulsar/impl/lookup_service_test.go
rename to pulsar/internal/lookup_service_test.go
index a5e8cb0..7e0d148 100644
--- a/pulsar/impl/lookup_service_test.go
+++ b/pulsar/internal/lookup_service_test.go
@@ -17,13 +17,13 @@
 // under the License.
 //
 
-package impl
+package internal
 
 import (
 	"github.com/golang/protobuf/proto"
 	"github.com/stretchr/testify/assert"
 	"net/url"
-	pb "pulsar-client-go/pulsar/pulsar_proto"
+	pb "pulsar-client-go/pulsar/internal/pulsar_proto"
 	"testing"
 )
 
diff --git a/pulsar/pulsar_proto/PulsarApi.pb.go b/pulsar/internal/pulsar_proto/PulsarApi.pb.go
similarity index 100%
rename from pulsar/pulsar_proto/PulsarApi.pb.go
rename to pulsar/internal/pulsar_proto/PulsarApi.pb.go
diff --git a/pulsar/impl/rpc_client.go b/pulsar/internal/rpc_client.go
similarity index 98%
rename from pulsar/impl/rpc_client.go
rename to pulsar/internal/rpc_client.go
index 32be5b6..2281def 100644
--- a/pulsar/impl/rpc_client.go
+++ b/pulsar/internal/rpc_client.go
@@ -17,12 +17,12 @@
 // under the License.
 //
 
-package impl
+package internal
 
 import (
 	"github.com/golang/protobuf/proto"
 	"net/url"
-	pb "pulsar-client-go/pulsar/pulsar_proto"
+	pb "pulsar-client-go/pulsar/internal/pulsar_proto"
 	"sync"
 	"sync/atomic"
 )
diff --git a/pulsar/impl/topic_name.go b/pulsar/internal/topic_name.go
similarity index 99%
rename from pulsar/impl/topic_name.go
rename to pulsar/internal/topic_name.go
index 7b284ef..6cee520 100644
--- a/pulsar/impl/topic_name.go
+++ b/pulsar/internal/topic_name.go
@@ -17,7 +17,7 @@
 // under the License.
 //
 
-package impl
+package internal
 
 import (
 	"errors"
diff --git a/pulsar/impl/topic_name_test.go b/pulsar/internal/topic_name_test.go
similarity index 99%
rename from pulsar/impl/topic_name_test.go
rename to pulsar/internal/topic_name_test.go
index 7095edb..f1339a5 100644
--- a/pulsar/impl/topic_name_test.go
+++ b/pulsar/internal/topic_name_test.go
@@ -17,7 +17,7 @@
 // under the License.
 //
 
-package impl
+package internal
 
 import (
 	"testing"
diff --git a/pulsar/impl/util/blocking_queue.go b/pulsar/internal/util/blocking_queue.go
similarity index 100%
rename from pulsar/impl/util/blocking_queue.go
rename to pulsar/internal/util/blocking_queue.go
diff --git a/pulsar/impl/util/blocking_queue_test.go b/pulsar/internal/util/blocking_queue_test.go
similarity index 100%
rename from pulsar/impl/util/blocking_queue_test.go
rename to pulsar/internal/util/blocking_queue_test.go
diff --git a/pulsar/impl/util/semaphore.go b/pulsar/internal/util/semaphore.go
similarity index 100%
rename from pulsar/impl/util/semaphore.go
rename to pulsar/internal/util/semaphore.go
diff --git a/pulsar/impl/utils.go b/pulsar/internal/utils.go
similarity index 98%
rename from pulsar/impl/utils.go
rename to pulsar/internal/utils.go
index b9b9b08..30837d6 100644
--- a/pulsar/impl/utils.go
+++ b/pulsar/internal/utils.go
@@ -17,7 +17,7 @@
 // under the License.
 //
 
-package impl
+package internal
 
 import (
 	"sync/atomic"
diff --git a/pulsar/producer_test.go b/pulsar/producer_test.go
index 318d811..5d781a3 100644
--- a/pulsar/producer_test.go
+++ b/pulsar/producer_test.go
@@ -23,7 +23,7 @@ import (
 	"context"
 	log "github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/assert"
-	"pulsar-client-go/pulsar/impl/util"
+	"pulsar-client-go/pulsar/internal/util"
 	"sync"
 	"testing"
 	"time"


[pulsar-client-go] 30/38: Added license headers

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit 1ead55b2a8709614387f22bb2ea0b7b8e560ded4
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Mon May 6 14:44:31 2019 -0700

    Added license headers
---
 LICENSE                                     | 305 ++++++++++++++++++++++++++++
 NOTICE                                      |   6 +
 pulsar/impl/backoff.go                      |  19 ++
 pulsar/impl/batch_builder.go                |  19 ++
 pulsar/impl/buffer.go                       |  19 ++
 pulsar/impl/buffer_test.go                  |  19 ++
 pulsar/impl/checksum.go                     |  19 ++
 pulsar/impl/closable.go                     |  19 ++
 pulsar/impl/commands.go                     |  19 ++
 pulsar/impl/commands_test.go                |  19 ++
 pulsar/impl/compression/compression.go      |  19 ++
 pulsar/impl/compression/compression_test.go |  19 ++
 pulsar/impl/compression/lz4.go              |  19 ++
 pulsar/impl/compression/noop.go             |  19 ++
 pulsar/impl/compression/zlib.go             |  19 ++
 pulsar/impl/compression/zstd.go             |  19 ++
 pulsar/impl/connection.go                   |  19 ++
 pulsar/impl/connection_pool.go              |  19 ++
 pulsar/impl/connection_reader.go            |  19 ++
 pulsar/impl/default_router.go               |  19 ++
 pulsar/impl/default_router_test.go          |  19 ++
 pulsar/impl/hash.go                         |  19 ++
 pulsar/impl/hash_test.go                    |  19 ++
 pulsar/impl/lookup_service.go               |  19 ++
 pulsar/impl/lookup_service_test.go          |  19 ++
 pulsar/impl/rpc_client.go                   |  19 ++
 pulsar/impl/topic_name.go                   |  19 ++
 pulsar/impl/topic_name_test.go              |  19 ++
 pulsar/impl/util/blocking_queue.go          |  19 ++
 pulsar/impl/util/blocking_queue_test.go     |  19 ++
 pulsar/impl/util/semaphore.go               |  19 ++
 pulsar/impl/utils.go                        |  19 ++
 pulsar/impl_client.go                       |  19 ++
 pulsar/impl_client_test.go                  |  19 ++
 pulsar/impl_message.go                      |  19 ++
 pulsar/impl_message_test.go                 |  19 ++
 pulsar/impl_partition_producer.go           |  19 ++
 pulsar/impl_producer.go                     |  19 ++
 pulsar/producer_test.go                     |  19 ++
 pulsar/util_test.go                         |  19 ++
 40 files changed, 1033 insertions(+)

diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..356931c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,305 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+----------------------------------------------------------------------------------------------------
+
+pulsar-common/src/main/java/org/apache/pulsar/common/util/protobuf/ByteBufCoded{Input,Output}Stream.java
+
+Copyright 2014, Google Inc.  All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+    * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Code generated by the Protocol Buffer compiler is owned by the owner
+of the input file used when generating it.  This code is not
+standalone and requires a support library to be linked with it.  This
+support library is itself covered by the above license.
+
+----------------------------------------------------------------------------------------------------
+
+pulsar-client-cpp/lib/lz4/lz4.{h,c}
+
+LZ4 - Fast LZ compression algorithm
+Copyright (C) 2011-2015, Yann Collet.
+
+BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+You can contact the author at :
+- LZ4 source repository : https://github.com/Cyan4973/lz4
+- LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c
+
+----------------------------------------------------------------------------------------------------
+
+pulsar-client-cpp/lib/checksum/crc32c_sw.cc
+
+/* crc32c.c -- compute CRC-32C using the Intel crc32 instruction
+ * Copyright (C) 2013 Mark Adler
+ * Version 1.1  1 Aug 2013  Mark Adler
+ */
+
+/*
+ This software is provided 'as-is', without any express or implied
+ warranty.  In no event will the author be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+ Mark Adler
+ madler@alumni.caltech.edu
+ */
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..92236a6
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,6 @@
+
+Apache Pulsar
+Copyright 2017-2019 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
\ No newline at end of file
diff --git a/pulsar/impl/backoff.go b/pulsar/impl/backoff.go
index 451509f..afd6c4f 100644
--- a/pulsar/impl/backoff.go
+++ b/pulsar/impl/backoff.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 import (
diff --git a/pulsar/impl/batch_builder.go b/pulsar/impl/batch_builder.go
index 6675d8d..0cb19f0 100644
--- a/pulsar/impl/batch_builder.go
+++ b/pulsar/impl/batch_builder.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 import (
diff --git a/pulsar/impl/buffer.go b/pulsar/impl/buffer.go
index 3f8dcc0..bf92b8f 100644
--- a/pulsar/impl/buffer.go
+++ b/pulsar/impl/buffer.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 import (
diff --git a/pulsar/impl/buffer_test.go b/pulsar/impl/buffer_test.go
index 1d72196..e6ace40 100644
--- a/pulsar/impl/buffer_test.go
+++ b/pulsar/impl/buffer_test.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 import (
diff --git a/pulsar/impl/checksum.go b/pulsar/impl/checksum.go
index cbea686..906c4dc 100644
--- a/pulsar/impl/checksum.go
+++ b/pulsar/impl/checksum.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 import "hash/crc32"
diff --git a/pulsar/impl/closable.go b/pulsar/impl/closable.go
index c483901..3805556 100644
--- a/pulsar/impl/closable.go
+++ b/pulsar/impl/closable.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 type Closable interface {
diff --git a/pulsar/impl/commands.go b/pulsar/impl/commands.go
index 7a55a89..949d56d 100644
--- a/pulsar/impl/commands.go
+++ b/pulsar/impl/commands.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 import (
diff --git a/pulsar/impl/commands_test.go b/pulsar/impl/commands_test.go
index d9f94e6..a1f08a8 100644
--- a/pulsar/impl/commands_test.go
+++ b/pulsar/impl/commands_test.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 import (
diff --git a/pulsar/impl/compression/compression.go b/pulsar/impl/compression/compression.go
index 107485b..a4aa652 100644
--- a/pulsar/impl/compression/compression.go
+++ b/pulsar/impl/compression/compression.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package compression
 
 type Provider interface {
diff --git a/pulsar/impl/compression/compression_test.go b/pulsar/impl/compression/compression_test.go
index ef57402..b21f0fc 100644
--- a/pulsar/impl/compression/compression_test.go
+++ b/pulsar/impl/compression/compression_test.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package compression
 
 import (
diff --git a/pulsar/impl/compression/lz4.go b/pulsar/impl/compression/lz4.go
index cb725dd..da1868c 100644
--- a/pulsar/impl/compression/lz4.go
+++ b/pulsar/impl/compression/lz4.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package compression
 
 import (
diff --git a/pulsar/impl/compression/noop.go b/pulsar/impl/compression/noop.go
index 2267d66..533ab2c 100644
--- a/pulsar/impl/compression/noop.go
+++ b/pulsar/impl/compression/noop.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package compression
 
 type noopProvider struct {
diff --git a/pulsar/impl/compression/zlib.go b/pulsar/impl/compression/zlib.go
index ea184e6..1646bc5 100644
--- a/pulsar/impl/compression/zlib.go
+++ b/pulsar/impl/compression/zlib.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package compression
 
 import (
diff --git a/pulsar/impl/compression/zstd.go b/pulsar/impl/compression/zstd.go
index bf61be8..ffad2bc 100644
--- a/pulsar/impl/compression/zstd.go
+++ b/pulsar/impl/compression/zstd.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package compression
 
 import (
diff --git a/pulsar/impl/connection.go b/pulsar/impl/connection.go
index 75ad6e3..aa1c809 100644
--- a/pulsar/impl/connection.go
+++ b/pulsar/impl/connection.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 import (
diff --git a/pulsar/impl/connection_pool.go b/pulsar/impl/connection_pool.go
index ef5ca8b..474f2f8 100644
--- a/pulsar/impl/connection_pool.go
+++ b/pulsar/impl/connection_pool.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 import (
diff --git a/pulsar/impl/connection_reader.go b/pulsar/impl/connection_reader.go
index 8277507..fd753d1 100644
--- a/pulsar/impl/connection_reader.go
+++ b/pulsar/impl/connection_reader.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 import (
diff --git a/pulsar/impl/default_router.go b/pulsar/impl/default_router.go
index 1621ed4..20db625 100644
--- a/pulsar/impl/default_router.go
+++ b/pulsar/impl/default_router.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 import (
diff --git a/pulsar/impl/default_router_test.go b/pulsar/impl/default_router_test.go
index b3ef841..8cad90d 100644
--- a/pulsar/impl/default_router_test.go
+++ b/pulsar/impl/default_router_test.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 import (
diff --git a/pulsar/impl/hash.go b/pulsar/impl/hash.go
index bd65da1..61f580b 100644
--- a/pulsar/impl/hash.go
+++ b/pulsar/impl/hash.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 import "github.com/spaolacci/murmur3"
diff --git a/pulsar/impl/hash_test.go b/pulsar/impl/hash_test.go
index 76b418a..b4f41d5 100644
--- a/pulsar/impl/hash_test.go
+++ b/pulsar/impl/hash_test.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 import (
diff --git a/pulsar/impl/lookup_service.go b/pulsar/impl/lookup_service.go
index ce1360e..13c74a7 100644
--- a/pulsar/impl/lookup_service.go
+++ b/pulsar/impl/lookup_service.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 import (
diff --git a/pulsar/impl/lookup_service_test.go b/pulsar/impl/lookup_service_test.go
index 62f0ea7..a5e8cb0 100644
--- a/pulsar/impl/lookup_service_test.go
+++ b/pulsar/impl/lookup_service_test.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 import (
diff --git a/pulsar/impl/rpc_client.go b/pulsar/impl/rpc_client.go
index 137d6f0..32be5b6 100644
--- a/pulsar/impl/rpc_client.go
+++ b/pulsar/impl/rpc_client.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 import (
diff --git a/pulsar/impl/topic_name.go b/pulsar/impl/topic_name.go
index 2bafeb2..7b284ef 100644
--- a/pulsar/impl/topic_name.go
+++ b/pulsar/impl/topic_name.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 import (
diff --git a/pulsar/impl/topic_name_test.go b/pulsar/impl/topic_name_test.go
index b4d6184..7095edb 100644
--- a/pulsar/impl/topic_name_test.go
+++ b/pulsar/impl/topic_name_test.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 import (
diff --git a/pulsar/impl/util/blocking_queue.go b/pulsar/impl/util/blocking_queue.go
index bdf6099..b663302 100644
--- a/pulsar/impl/util/blocking_queue.go
+++ b/pulsar/impl/util/blocking_queue.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package util
 
 import (
diff --git a/pulsar/impl/util/blocking_queue_test.go b/pulsar/impl/util/blocking_queue_test.go
index 15cda0c..f8cb3fa 100644
--- a/pulsar/impl/util/blocking_queue_test.go
+++ b/pulsar/impl/util/blocking_queue_test.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package util
 
 import (
diff --git a/pulsar/impl/util/semaphore.go b/pulsar/impl/util/semaphore.go
index 9df8cd1..45fa7f4 100644
--- a/pulsar/impl/util/semaphore.go
+++ b/pulsar/impl/util/semaphore.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package util
 
 type Semaphore chan bool
diff --git a/pulsar/impl/utils.go b/pulsar/impl/utils.go
index 02d15b3..b9b9b08 100644
--- a/pulsar/impl/utils.go
+++ b/pulsar/impl/utils.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package impl
 
 import (
diff --git a/pulsar/impl_client.go b/pulsar/impl_client.go
index f688208..89b955d 100644
--- a/pulsar/impl_client.go
+++ b/pulsar/impl_client.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package pulsar
 
 import (
diff --git a/pulsar/impl_client_test.go b/pulsar/impl_client_test.go
index 99f0c78..4542740 100644
--- a/pulsar/impl_client_test.go
+++ b/pulsar/impl_client_test.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package pulsar
 
 import (
diff --git a/pulsar/impl_message.go b/pulsar/impl_message.go
index 651bab3..24f3f30 100644
--- a/pulsar/impl_message.go
+++ b/pulsar/impl_message.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package pulsar
 
 import (
diff --git a/pulsar/impl_message_test.go b/pulsar/impl_message_test.go
index ea64640..f720c33 100644
--- a/pulsar/impl_message_test.go
+++ b/pulsar/impl_message_test.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package pulsar
 
 import (
diff --git a/pulsar/impl_partition_producer.go b/pulsar/impl_partition_producer.go
index 9e8800c..41e7702 100644
--- a/pulsar/impl_partition_producer.go
+++ b/pulsar/impl_partition_producer.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package pulsar
 
 import (
diff --git a/pulsar/impl_producer.go b/pulsar/impl_producer.go
index f1b5509..c379531 100644
--- a/pulsar/impl_producer.go
+++ b/pulsar/impl_producer.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package pulsar
 
 import (
diff --git a/pulsar/producer_test.go b/pulsar/producer_test.go
index 1408170..318d811 100644
--- a/pulsar/producer_test.go
+++ b/pulsar/producer_test.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package pulsar
 
 import (
diff --git a/pulsar/util_test.go b/pulsar/util_test.go
index 4bc67d3..1795e41 100644
--- a/pulsar/util_test.go
+++ b/pulsar/util_test.go
@@ -1,3 +1,22 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
 package pulsar
 
 import (


[pulsar-client-go] 23/38: Completed default message router and tests

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit e7a4aef63b1b89d5b1f2bede6e043362c4788c9f
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Sat May 4 08:24:23 2019 -0700

    Completed default message router and tests
---
 pulsar/impl/default_router.go      | 42 ++++++++++++++++++------
 pulsar/impl/default_router_test.go | 66 ++++++++++++++++++++++++++++++++++++++
 pulsar/impl_producer.go            | 18 +++++++++--
 pulsar/producer.go                 | 22 -------------
 4 files changed, 115 insertions(+), 33 deletions(-)

diff --git a/pulsar/impl/default_router.go b/pulsar/impl/default_router.go
index bb5ca73..1621ed4 100644
--- a/pulsar/impl/default_router.go
+++ b/pulsar/impl/default_router.go
@@ -1,26 +1,50 @@
 package impl
 
 import (
+	"math/rand"
 	"time"
 )
 
 type defaultRouter struct {
-	partitionIdx     uint32
+	clock            Clock
+	shiftIdx         uint32
 	maxBatchingDelay time.Duration
+	hashFunc         func(string) uint32
 }
 
-func NewDefaultRouter(maxBatchingDelay time.Duration) func(uint32) int {
-	// TODO: XXX
-	//state := &defaultRouter{
-	//	partitionIdx:     rand.Uint32(),
-	//	maxBatchingDelay: maxBatchingDelay,
-	//}
+type Clock func() uint64
 
-	return func(numPartitions uint32) int {
+func NewSystemClock() Clock {
+	return func() uint64 {
+		return uint64(time.Now().UnixNano())
+	}
+}
+
+func NewDefaultRouter(clock Clock, hashFunc func(string) uint32, maxBatchingDelay time.Duration) func(string, uint32) int {
+	state := &defaultRouter{
+		clock:            clock,
+		shiftIdx:         rand.Uint32(),
+		maxBatchingDelay: maxBatchingDelay,
+		hashFunc:         hashFunc,
+	}
+
+	return func(key string, numPartitions uint32) int {
 		if numPartitions == 1 {
+			// When there are no partitions, don't even bother
 			return 0
 		}
 
-		return -1
+		if key != "" {
+			// When a key is specified, use the hash of that key
+			return int(state.hashFunc(key) % numPartitions)
+		}
+
+		// If there's no key, we do round-robin across partition, sticking with a given
+		// partition for a certain amount of time, to ensure we can have a decent amount
+		// of batching of the messages.
+		//
+		//currentMs / maxBatchingDelayMs + startPtnIdx
+		n := uint32(state.clock()/uint64(maxBatchingDelay.Nanoseconds())) + state.shiftIdx
+		return int(n % numPartitions)
 	}
 }
diff --git a/pulsar/impl/default_router_test.go b/pulsar/impl/default_router_test.go
new file mode 100644
index 0000000..b3ef841
--- /dev/null
+++ b/pulsar/impl/default_router_test.go
@@ -0,0 +1,66 @@
+package impl
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+	"time"
+)
+
+
+func TestDefaultRouter(t *testing.T) {
+
+	var currentClock uint64
+
+	router := NewDefaultRouter(func() uint64 {
+		return currentClock
+	}, JavaStringHash, 10*time.Nanosecond)
+
+	// partition index should not change with time
+	p1 := router("my-key", 100)
+	p2 := router("my-key", 100)
+
+	assert.Equal(t, p1, p2)
+
+	currentClock = 100
+	p3 := router("my-key", 100)
+
+	assert.Equal(t, p1, p3)
+
+	// With no key, we should give the same partition for a given time range
+	pr1 := router("", 100)
+	pr2 := router("", 100)
+	assert.Equal(t, pr1, pr2)
+
+	currentClock = 101
+	pr3 := router("", 100)
+	assert.Equal(t, pr1, pr3)
+
+	currentClock = 102
+	pr4 := router("", 100)
+	assert.Equal(t, pr1, pr4)
+
+	currentClock = 111
+	pr5 := router("", 100)
+	assert.NotEqual(t, pr1, pr5)
+
+	currentClock = 112
+	pr6 := router("", 100)
+	assert.Equal(t, pr5, pr6)
+}
+
+func TestDefaultRouterNoPartitions(t *testing.T) {
+
+	router := NewDefaultRouter(NewSystemClock(), JavaStringHash, 10*time.Nanosecond)
+
+	// partition index should not change with time
+	p1 := router("", 1)
+	p2 := router("my-key", 1)
+	p3 := router("my-key-2", 1)
+	p4 := router("my-key-3", 1)
+
+	assert.Equal(t, 0, p1)
+	assert.Equal(t, 0, p2)
+	assert.Equal(t, 0, p3)
+	assert.Equal(t, 0, p4)
+}
+
diff --git a/pulsar/impl_producer.go b/pulsar/impl_producer.go
index abedfab..12e5e7c 100644
--- a/pulsar/impl_producer.go
+++ b/pulsar/impl_producer.go
@@ -11,6 +11,17 @@ type producer struct {
 	messageRouter func(*ProducerMessage, TopicMetadata) int
 }
 
+func getHashingFunction(s HashingScheme) func(string) uint32 {
+	switch s {
+	case JavaStringHash:
+		return impl.JavaStringHash
+	case Murmur3_32Hash:
+		return impl.Murmur3_32Hash
+	default:
+		return impl.JavaStringHash
+	}
+}
+
 func newProducer(client *client, options *ProducerOptions) (*producer, error) {
 	if options.Topic == "" {
 		return nil, newError(ResultInvalidTopicName, "Topic name is required for producer")
@@ -21,9 +32,12 @@ func newProducer(client *client, options *ProducerOptions) (*producer, error) {
 	}
 
 	if options.MessageRouter == nil {
-		internalRouter := impl.NewDefaultRouter(options.BatchingMaxPublishDelay)
+		internalRouter := impl.NewDefaultRouter(
+			impl.NewSystemClock(),
+			getHashingFunction(options.HashingScheme),
+			options.BatchingMaxPublishDelay)
 		p.messageRouter = func(message *ProducerMessage, metadata TopicMetadata) int {
-			return internalRouter(metadata.NumPartitions())
+			return internalRouter(message.Key, metadata.NumPartitions())
 		}
 	}
 
diff --git a/pulsar/producer.go b/pulsar/producer.go
index 8eaf340..2a248cb 100644
--- a/pulsar/producer.go
+++ b/pulsar/producer.go
@@ -24,25 +24,11 @@ import (
 	"time"
 )
 
-type MessageRoutingMode int
-
-const (
-	// Publish messages across all partitions in round-robin.
-	RoundRobinDistribution MessageRoutingMode = iota
-
-	// The producer will chose one single partition and publish all the messages into that partition
-	UseSinglePartition
-
-	// Use custom message router implementation that will be called to determine the partition for a particular message.
-	CustomPartition
-)
-
 type HashingScheme int
 
 const (
 	JavaStringHash HashingScheme = iota // Java String.hashCode() equivalent
 	Murmur3_32Hash                      // Use Murmur3 hashing function
-	BoostHash                           // C++ based boost::hash
 )
 
 type CompressionType int
@@ -97,20 +83,12 @@ type ProducerOptions struct {
 	// `ProducerQueueIsFullError` when there is no space left in pending queue.
 	BlockIfQueueFull bool
 
-	// Set the message routing mode for the partitioned producer.
-	// Default routing mode is round-robin routing.
-	//
-	// This logic is applied when the application is not setting a key ProducerMessage#setKey(String) on a
-	// particular message.
-	MessageRoutingMode
-
 	// Change the `HashingScheme` used to chose the partition on where to publish a particular message.
 	// Standard hashing functions available are:
 	//
 	//  - `JavaStringHash` : Java String.hashCode() equivalent
 	//  - `Murmur3_32Hash` : Use Murmur3 hashing function.
 	// 		https://en.wikipedia.org/wiki/MurmurHash">https://en.wikipedia.org/wiki/MurmurHash
-	//  - `BoostHash`      : C++ based boost::hash
 	//
 	// Default is `JavaStringHash`.
 	HashingScheme


[pulsar-client-go] 38/38: Merge pull request #1 from merlimat/master

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit 9c5d5098536baeae117b005fafe5606d4037efa8
Merge: d74beea e058b84
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Tue May 14 09:33:33 2019 -0700

    Merge pull request #1 from merlimat/master
    
    Initial client library submission

 .gitignore                                      |    1 +
 Dockerfile                                      |   34 +
 LICENSE                                         |  305 ++
 NOTICE                                          |    6 +
 README.md                                       |   60 +
 integration-tests/certs/broker-cert.pem         |   73 +
 integration-tests/certs/broker-key.pem          |   28 +
 integration-tests/certs/cacert.pem              |   62 +
 integration-tests/certs/client-cert.pem         |   73 +
 integration-tests/certs/client-key.pem          |   28 +
 integration-tests/client.conf                   |   27 +
 integration-tests/standalone.conf               |  280 ++
 integration-tests/tokens/secret.key             |    1 +
 integration-tests/tokens/token.txt              |    1 +
 perf/perf-consumer.go                           |  113 +
 perf/perf-producer.go                           |  147 +
 perf/pulsar-perf-go.go                          |   49 +
 pulsar-test-service-start.sh                    |   79 +
 pulsar-test-service-stop.sh                     |   35 +
 pulsar/client.go                                |  113 +
 pulsar/consumer.go                              |  179 +
 pulsar/error.go                                 |  103 +
 pulsar/impl_client.go                           |  152 +
 pulsar/impl_client_test.go                      |  203 ++
 pulsar/impl_message.go                          |   78 +
 pulsar/impl_message_test.go                     |   48 +
 pulsar/impl_partition_producer.go               |  427 +++
 pulsar/impl_producer.go                         |  156 +
 pulsar/internal/auth/disabled.go                |   49 +
 pulsar/internal/auth/provider.go                |   63 +
 pulsar/internal/auth/tls.go                     |   64 +
 pulsar/internal/auth/token.go                   |   98 +
 pulsar/internal/backoff.go                      |   45 +
 pulsar/internal/batch_builder.go                |  166 +
 pulsar/internal/buffer.go                       |  193 ++
 pulsar/internal/buffer_test.go                  |   38 +
 pulsar/internal/checksum.go                     |   28 +
 pulsar/internal/closable.go                     |   24 +
 pulsar/internal/commands.go                     |  141 +
 pulsar/internal/commands_test.go                |   45 +
 pulsar/internal/compression/compression.go      |   33 +
 pulsar/internal/compression/compression_test.go |   71 +
 pulsar/internal/compression/lz4.go              |   47 +
 pulsar/internal/compression/noop.go             |   35 +
 pulsar/internal/compression/zlib.go             |   54 +
 pulsar/internal/compression/zstd.go             |   39 +
 pulsar/internal/connection.go                   |  484 +++
 pulsar/internal/connection_pool.go              |   85 +
 pulsar/internal/connection_reader.go            |  136 +
 pulsar/internal/default_router.go               |   69 +
 pulsar/internal/default_router_test.go          |   85 +
 pulsar/internal/hash.go                         |   38 +
 pulsar/internal/hash_test.go                    |   59 +
 pulsar/internal/lookup_service.go               |  132 +
 pulsar/internal/lookup_service_test.go          |  268 ++
 pulsar/internal/pulsar_proto/PulsarApi.pb.go    | 4043 +++++++++++++++++++++++
 pulsar/internal/rpc_client.go                   |  124 +
 pulsar/internal/topic_name.go                   |  107 +
 pulsar/internal/topic_name_test.go              |   87 +
 pulsar/internal/util/blocking_queue.go          |  203 ++
 pulsar/internal/util/blocking_queue_test.go     |  137 +
 pulsar/internal/util/semaphore.go               |   30 +
 pulsar/internal/utils.go                        |   39 +
 pulsar/message.go                               |   88 +
 pulsar/producer.go                              |  167 +
 pulsar/producer_test.go                         |  181 +
 pulsar/reader.go                                |   84 +
 pulsar/test_helper.go                           |   43 +
 68 files changed, 10783 insertions(+)


[pulsar-client-go] 21/38: Producer last sequence id

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit b2c9d8ca7c069e68cdb931e08ce211fba881ed0d
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Fri May 3 16:07:44 2019 -0700

    Producer last sequence id
---
 pulsar/impl_partition_producer.go |  7 +++++--
 pulsar/producer_test.go           | 31 +++++++++++++++++++++++++++++++
 2 files changed, 36 insertions(+), 2 deletions(-)

diff --git a/pulsar/impl_partition_producer.go b/pulsar/impl_partition_producer.go
index b87384d..7b32fdc 100644
--- a/pulsar/impl_partition_producer.go
+++ b/pulsar/impl_partition_producer.go
@@ -8,6 +8,7 @@ import (
 	"pulsar-client-go-native/pulsar/impl/util"
 	pb "pulsar-client-go-native/pulsar/pulsar_proto"
 	"sync"
+	"sync/atomic"
 	"time"
 )
 
@@ -39,6 +40,7 @@ type partitionProducer struct {
 
 	publishSemaphore util.Semaphore
 	pendingQueue     util.BlockingQueue
+	lastSequenceID   int64
 }
 
 const defaultBatchingMaxPublishDelay = 10 * time.Millisecond
@@ -70,6 +72,7 @@ func newPartitionProducer(client *client, topic string, options *ProducerOptions
 		batchFlushTicker: time.NewTicker(batchingMaxPublishDelay),
 		publishSemaphore: make(util.Semaphore, maxPendingMessages),
 		pendingQueue:     util.NewBlockingQueue(maxPendingMessages),
+		lastSequenceID:   -1,
 	}
 
 	if options.Name != "" {
@@ -313,6 +316,7 @@ func (p *partitionProducer) ReceivedSendReceipt(response *pb.CommandSendReceipt)
 	p.publishSemaphore.Release()
 	for _, i := range pi.sendRequests {
 		sr := i.(*sendRequest)
+		atomic.StoreInt64(&p.lastSequenceID, int64(pi.sequenceId))
 		if sr.callback != nil {
 			sr.callback(nil, sr.msg, nil)
 		}
@@ -347,8 +351,7 @@ func (p *partitionProducer) internalClose(req *closeProducer) {
 }
 
 func (p *partitionProducer) LastSequenceID() int64 {
-	// TODO: return real last sequence id
-	return -1
+	return atomic.LoadInt64(&p.lastSequenceID)
 }
 
 func (p *partitionProducer) Flush() error {
diff --git a/pulsar/producer_test.go b/pulsar/producer_test.go
index ab3d739..fa197b4 100644
--- a/pulsar/producer_test.go
+++ b/pulsar/producer_test.go
@@ -78,3 +78,34 @@ func TestProducerCompression(t *testing.T) {
 		})
 	}
 }
+
+func TestProducerLastSequenceID(t *testing.T) {
+	client, err := NewClient(ClientOptions{
+		URL: serviceUrl,
+	})
+	assert.NoError(t, err)
+
+	producer, err := client.CreateProducer(ProducerOptions{
+		Topic: newTopicName(),
+	})
+
+	assert.NoError(t, err)
+	assert.NotNil(t, producer)
+
+	assert.Equal(t, int64(-1), producer.LastSequenceID())
+
+	for i := 0; i < 10; i++ {
+		err := producer.Send(context.Background(), &ProducerMessage{
+			Payload: []byte("hello"),
+		})
+
+		assert.NoError(t, err)
+		assert.Equal(t, int64(i), producer.LastSequenceID())
+	}
+
+	err = producer.Close()
+	assert.NoError(t, err)
+
+	err = client.Close()
+	assert.NoError(t, err)
+}
\ No newline at end of file


[pulsar-client-go] 29/38: Renamed to pulsar-client-go

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit dfd7550791033554fd5f2d3f55ccc2fb76749756
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Sun May 5 16:36:29 2019 -0700

    Renamed to pulsar-client-go
---
 perf/perf-consumer.go              | 2 +-
 perf/perf-producer.go              | 2 +-
 pulsar/impl/batch_builder.go       | 4 ++--
 pulsar/impl/commands.go            | 2 +-
 pulsar/impl/connection.go          | 2 +-
 pulsar/impl/connection_reader.go   | 2 +-
 pulsar/impl/lookup_service.go      | 2 +-
 pulsar/impl/lookup_service_test.go | 2 +-
 pulsar/impl/rpc_client.go          | 2 +-
 pulsar/impl_client.go              | 4 ++--
 pulsar/impl_message.go             | 2 +-
 pulsar/impl_partition_producer.go  | 6 +++---
 pulsar/impl_producer.go            | 2 +-
 pulsar/producer_test.go            | 2 +-
 14 files changed, 18 insertions(+), 18 deletions(-)

diff --git a/perf/perf-consumer.go b/perf/perf-consumer.go
index e6fdf77..ee315ba 100644
--- a/perf/perf-consumer.go
+++ b/perf/perf-consumer.go
@@ -22,7 +22,7 @@ package main
 import (
 	"context"
 	"encoding/json"
-	"pulsar-client-go-native/pulsar"
+	"pulsar-client-go/pulsar"
 	"github.com/spf13/cobra"
 	log "github.com/sirupsen/logrus"
 	"sync/atomic"
diff --git a/perf/perf-producer.go b/perf/perf-producer.go
index c40af30..e8fd76f 100644
--- a/perf/perf-producer.go
+++ b/perf/perf-producer.go
@@ -26,7 +26,7 @@ import (
 	"github.com/bmizerany/perks/quantile"
 	"github.com/spf13/cobra"
 	log "github.com/sirupsen/logrus"
-	"pulsar-client-go-native/pulsar"
+	"pulsar-client-go/pulsar"
 	"time"
 )
 
diff --git a/pulsar/impl/batch_builder.go b/pulsar/impl/batch_builder.go
index 0619154..6675d8d 100644
--- a/pulsar/impl/batch_builder.go
+++ b/pulsar/impl/batch_builder.go
@@ -3,8 +3,8 @@ package impl
 import (
 	"github.com/golang/protobuf/proto"
 	log "github.com/sirupsen/logrus"
-	"pulsar-client-go-native/pulsar/impl/compression"
-	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+	"pulsar-client-go/pulsar/impl/compression"
+	pb "pulsar-client-go/pulsar/pulsar_proto"
 	"time"
 )
 
diff --git a/pulsar/impl/commands.go b/pulsar/impl/commands.go
index c2db791..7a55a89 100644
--- a/pulsar/impl/commands.go
+++ b/pulsar/impl/commands.go
@@ -3,7 +3,7 @@ package impl
 import (
 	"github.com/golang/protobuf/proto"
 	log "github.com/sirupsen/logrus"
-	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+	pb "pulsar-client-go/pulsar/pulsar_proto"
 )
 
 const MaxFrameSize = 5 * 1024 * 1024
diff --git a/pulsar/impl/connection.go b/pulsar/impl/connection.go
index 580a821..75ad6e3 100644
--- a/pulsar/impl/connection.go
+++ b/pulsar/impl/connection.go
@@ -6,7 +6,7 @@ import (
 	log "github.com/sirupsen/logrus"
 	"net"
 	"net/url"
-	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+	pb "pulsar-client-go/pulsar/pulsar_proto"
 	"sync"
 	"sync/atomic"
 	"time"
diff --git a/pulsar/impl/connection_reader.go b/pulsar/impl/connection_reader.go
index 3b45dc4..8277507 100644
--- a/pulsar/impl/connection_reader.go
+++ b/pulsar/impl/connection_reader.go
@@ -5,7 +5,7 @@ import (
 	"github.com/golang/protobuf/proto"
 	"github.com/pkg/errors"
 	"io"
-	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+	pb "pulsar-client-go/pulsar/pulsar_proto"
 )
 
 type connectionReader struct {
diff --git a/pulsar/impl/lookup_service.go b/pulsar/impl/lookup_service.go
index 3dfe758..ce1360e 100644
--- a/pulsar/impl/lookup_service.go
+++ b/pulsar/impl/lookup_service.go
@@ -6,7 +6,7 @@ import (
 	"github.com/golang/protobuf/proto"
 	log "github.com/sirupsen/logrus"
 	"net/url"
-	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+	pb "pulsar-client-go/pulsar/pulsar_proto"
 )
 
 type LookupResult struct {
diff --git a/pulsar/impl/lookup_service_test.go b/pulsar/impl/lookup_service_test.go
index 7e18198..62f0ea7 100644
--- a/pulsar/impl/lookup_service_test.go
+++ b/pulsar/impl/lookup_service_test.go
@@ -4,7 +4,7 @@ import (
 	"github.com/golang/protobuf/proto"
 	"github.com/stretchr/testify/assert"
 	"net/url"
-	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+	pb "pulsar-client-go/pulsar/pulsar_proto"
 	"testing"
 )
 
diff --git a/pulsar/impl/rpc_client.go b/pulsar/impl/rpc_client.go
index 706b9a1..137d6f0 100644
--- a/pulsar/impl/rpc_client.go
+++ b/pulsar/impl/rpc_client.go
@@ -3,7 +3,7 @@ package impl
 import (
 	"github.com/golang/protobuf/proto"
 	"net/url"
-	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+	pb "pulsar-client-go/pulsar/pulsar_proto"
 	"sync"
 	"sync/atomic"
 )
diff --git a/pulsar/impl_client.go b/pulsar/impl_client.go
index a1f6e10..f688208 100644
--- a/pulsar/impl_client.go
+++ b/pulsar/impl_client.go
@@ -4,8 +4,8 @@ import (
 	"fmt"
 	log "github.com/sirupsen/logrus"
 	"net/url"
-	"pulsar-client-go-native/pulsar/impl"
-	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+	"pulsar-client-go/pulsar/impl"
+	pb "pulsar-client-go/pulsar/pulsar_proto"
 )
 
 type client struct {
diff --git a/pulsar/impl_message.go b/pulsar/impl_message.go
index 5d54678..651bab3 100644
--- a/pulsar/impl_message.go
+++ b/pulsar/impl_message.go
@@ -2,7 +2,7 @@ package pulsar
 
 import (
 	"github.com/golang/protobuf/proto"
-	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+	pb "pulsar-client-go/pulsar/pulsar_proto"
 )
 
 type messageId struct {
diff --git a/pulsar/impl_partition_producer.go b/pulsar/impl_partition_producer.go
index 78f14af..9e8800c 100644
--- a/pulsar/impl_partition_producer.go
+++ b/pulsar/impl_partition_producer.go
@@ -4,9 +4,9 @@ import (
 	"context"
 	"github.com/golang/protobuf/proto"
 	log "github.com/sirupsen/logrus"
-	"pulsar-client-go-native/pulsar/impl"
-	"pulsar-client-go-native/pulsar/impl/util"
-	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+	"pulsar-client-go/pulsar/impl"
+	"pulsar-client-go/pulsar/impl/util"
+	pb "pulsar-client-go/pulsar/pulsar_proto"
 	"sync"
 	"sync/atomic"
 	"time"
diff --git a/pulsar/impl_producer.go b/pulsar/impl_producer.go
index aec4eb5..f1b5509 100644
--- a/pulsar/impl_producer.go
+++ b/pulsar/impl_producer.go
@@ -2,7 +2,7 @@ package pulsar
 
 import (
 	"context"
-	"pulsar-client-go-native/pulsar/impl"
+	"pulsar-client-go/pulsar/impl"
 )
 
 type producer struct {
diff --git a/pulsar/producer_test.go b/pulsar/producer_test.go
index f7455ce..1408170 100644
--- a/pulsar/producer_test.go
+++ b/pulsar/producer_test.go
@@ -4,7 +4,7 @@ import (
 	"context"
 	log "github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/assert"
-	"pulsar-client-go-native/pulsar/impl/util"
+	"pulsar-client-go/pulsar/impl/util"
 	"sync"
 	"testing"
 	"time"


[pulsar-client-go] 18/38: Added compression codecs and tests

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit f9fa72736270b99fb95476645d81cc3fd0704993
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Thu Apr 11 17:50:11 2019 -0700

    Added compression codecs and tests
---
 pulsar/impl/compression/compression.go      | 14 ++++++++
 pulsar/impl/compression/compression_test.go | 52 +++++++++++++++++++++++++++++
 pulsar/impl/compression/lz4.go              | 28 ++++++++++++++++
 pulsar/impl/compression/noop.go             | 16 +++++++++
 pulsar/impl/compression/zlib.go             | 35 +++++++++++++++++++
 pulsar/impl/compression/zstd.go             | 20 +++++++++++
 6 files changed, 165 insertions(+)

diff --git a/pulsar/impl/compression/compression.go b/pulsar/impl/compression/compression.go
new file mode 100644
index 0000000..107485b
--- /dev/null
+++ b/pulsar/impl/compression/compression.go
@@ -0,0 +1,14 @@
+package compression
+
+type Provider interface {
+	Compress(data []byte) []byte
+
+	Decompress(compressedData []byte, originalSize int) ([]byte, error)
+}
+
+var (
+	NoopProvider = NewNoopProvider()
+	ZLibProvider = NewZLibProvider()
+	Lz4Provider  = NewLz4Provider()
+	ZStdProvider = NewZStdProvider()
+)
diff --git a/pulsar/impl/compression/compression_test.go b/pulsar/impl/compression/compression_test.go
new file mode 100644
index 0000000..ef57402
--- /dev/null
+++ b/pulsar/impl/compression/compression_test.go
@@ -0,0 +1,52 @@
+package compression
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+type testProvider struct {
+	name     string
+	provider Provider
+
+	// Compressed data for "hello"
+	compressedHello []byte
+}
+
+var providers = []testProvider{
+	{"zlib", ZLibProvider, []byte{0x78, 0x9c, 0xca, 0x48, 0xcd, 0xc9, 0xc9, 0x07, 0x00, 0x00, 0x00, 0xff, 0xff}},
+	{"lz4", Lz4Provider, []byte{0x50, 0x68, 0x65, 0x6c, 0x6c, 0x6f}},
+	{"zstd", ZStdProvider, []byte{0x28, 0xb5, 0x2f, 0xfd, 0x20, 0x05, 0x29, 0x00, 0x00, 0x68, 0x65, 0x6c, 0x6c, 0x6f}},
+}
+
+func TestCompression(t *testing.T) {
+	for _, p := range providers {
+		t.Run(p.name, func(t *testing.T) {
+			hello := []byte("test compression data")
+			compressed := p.provider.Compress(hello)
+			uncompressed, err := p.provider.Decompress(compressed, len(hello))
+			assert.Nil(t, err)
+			assert.ElementsMatch(t, hello, uncompressed)
+		})
+	}
+}
+
+func TestJavaCompatibility(t *testing.T) {
+	for _, p := range providers {
+		t.Run(p.name, func(t *testing.T) {
+			hello := []byte("hello")
+			uncompressed, err := p.provider.Decompress(p.compressedHello, len(hello))
+			assert.Nil(t, err)
+			assert.ElementsMatch(t, hello, uncompressed)
+		})
+	}
+}
+
+func TestDecompressionError(t *testing.T) {
+	for _, p := range providers {
+		t.Run(p.name, func(t *testing.T) {
+			_, err := p.provider.Decompress([]byte{0x05}, 0)
+			assert.NotNil(t, err)
+		})
+	}
+}
diff --git a/pulsar/impl/compression/lz4.go b/pulsar/impl/compression/lz4.go
new file mode 100644
index 0000000..cb725dd
--- /dev/null
+++ b/pulsar/impl/compression/lz4.go
@@ -0,0 +1,28 @@
+package compression
+
+import (
+	"github.com/cloudflare/golz4"
+)
+
+type lz4Provider struct {
+}
+
+func NewLz4Provider() Provider {
+	return &lz4Provider{}
+}
+
+func (lz4Provider) Compress(data []byte) []byte {
+	maxSize := lz4.CompressBound(data)
+	compressed := make([]byte, maxSize)
+	size, err := lz4.Compress(data, compressed)
+	if err != nil {
+		panic("Failed to compress")
+	}
+	return compressed[:size]
+}
+
+func (lz4Provider) Decompress(compressedData []byte, originalSize int) ([]byte, error) {
+	uncompressed := make([]byte, originalSize)
+	err := lz4.Uncompress(compressedData, uncompressed)
+	return uncompressed, err
+}
diff --git a/pulsar/impl/compression/noop.go b/pulsar/impl/compression/noop.go
new file mode 100644
index 0000000..2267d66
--- /dev/null
+++ b/pulsar/impl/compression/noop.go
@@ -0,0 +1,16 @@
+package compression
+
+type noopProvider struct {
+}
+
+func NewNoopProvider() Provider {
+	return &noopProvider{}
+}
+
+func (noopProvider) Compress(data []byte) []byte {
+	return data
+}
+
+func (noopProvider) Decompress(compressedData []byte, originalSize int) ([]byte, error) {
+	return compressedData, nil
+}
diff --git a/pulsar/impl/compression/zlib.go b/pulsar/impl/compression/zlib.go
new file mode 100644
index 0000000..ea184e6
--- /dev/null
+++ b/pulsar/impl/compression/zlib.go
@@ -0,0 +1,35 @@
+package compression
+
+import (
+	"bytes"
+	"compress/zlib"
+)
+
+type zlibProvider struct {
+}
+
+func NewZLibProvider() Provider {
+	return &zlibProvider{}
+}
+
+func (zlibProvider) Compress(data []byte) []byte {
+	var b bytes.Buffer
+	w := zlib.NewWriter(&b)
+	w.Write(data)
+	w.Close()
+
+	return b.Bytes()
+}
+
+func (zlibProvider) Decompress(compressedData []byte, originalSize int) ([]byte, error) {
+	r, err := zlib.NewReader(bytes.NewBuffer(compressedData))
+	if err != nil {
+		return nil, err
+	}
+
+	uncompressed := make([]byte, originalSize)
+	r.Read(uncompressed)
+	r.Close()
+
+	return uncompressed, nil
+}
diff --git a/pulsar/impl/compression/zstd.go b/pulsar/impl/compression/zstd.go
new file mode 100644
index 0000000..bf61be8
--- /dev/null
+++ b/pulsar/impl/compression/zstd.go
@@ -0,0 +1,20 @@
+package compression
+
+import (
+	zstd "github.com/valyala/gozstd"
+)
+
+type zstdProvider struct {
+}
+
+func NewZStdProvider() Provider {
+	return &zstdProvider{}
+}
+
+func (zstdProvider) Compress(data []byte) []byte {
+	return zstd.Compress(nil, data)
+}
+
+func (zstdProvider) Decompress(compressedData []byte, originalSize int) ([]byte, error) {
+	return zstd.Decompress(nil, compressedData)
+}


[pulsar-client-go] 24/38: Added perf producer/consumer

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit 585500e59e10e71ad74bb982e8fab5d0eb216fb8
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Sat May 4 08:32:38 2019 -0700

    Added perf producer/consumer
---
 perf/perf-consumer.go  | 114 +++++++++++++++++++++++++++++++++++++
 perf/perf-producer.go  | 148 +++++++++++++++++++++++++++++++++++++++++++++++++
 perf/pulsar-perf-go.go |  49 ++++++++++++++++
 3 files changed, 311 insertions(+)

diff --git a/perf/perf-consumer.go b/perf/perf-consumer.go
new file mode 100644
index 0000000..4ffa36f
--- /dev/null
+++ b/perf/perf-consumer.go
@@ -0,0 +1,114 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package main
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"pulsar-client-go-native/pulsar"
+	"github.com/spf13/cobra"
+	"log"
+	"sync/atomic"
+	"time"
+)
+
+type ConsumeArgs struct {
+	Topic             string
+	SubscriptionName  string
+	ReceiverQueueSize int
+}
+
+var consumeArgs ConsumeArgs
+
+var cmdConsume = &cobra.Command{
+	Use:   "consume <topic>",
+	Short: "Consume from topic",
+	Args:  cobra.ExactArgs(1),
+	Run: func(cmd *cobra.Command, args []string) {
+		consumeArgs.Topic = args[0]
+		consume()
+	},
+}
+
+func initConsumer() {
+	cmdConsume.Flags().StringVarP(&consumeArgs.SubscriptionName, "subscription", "s", "sub", "Subscription name")
+	cmdConsume.Flags().IntVarP(&consumeArgs.ReceiverQueueSize, "receiver-queue-size", "r", 1000, "Receiver queue size")
+}
+
+func consume() {
+	b, _ := json.MarshalIndent(clientArgs, "", "  ")
+	fmt.Println("Client config: ", string(b))
+	b, _ = json.MarshalIndent(consumeArgs, "", "  ")
+	fmt.Println("Consumer config: ", string(b))
+
+	client, err := pulsar.NewClient(pulsar.ClientOptions{
+		URL:                    clientArgs.ServiceUrl,
+	})
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	defer client.Close()
+
+	consumer, err := client.Subscribe(pulsar.ConsumerOptions{
+		Topic:            consumeArgs.Topic,
+		SubscriptionName: consumeArgs.SubscriptionName,
+	})
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	defer consumer.Close()
+
+	ctx := context.Background()
+
+	var msgReceived int64 = 0
+	var bytesReceived int64 = 0
+
+	go func() {
+		for {
+			msg, _ := consumer.Receive(ctx)
+
+			atomic.AddInt64(&msgReceived, 1)
+			atomic.AddInt64(&bytesReceived, int64(len(msg.Payload())))
+
+			consumer.Ack(msg)
+		}
+	}()
+
+	// Print stats of the consume rate
+	tick := time.NewTicker(10 * time.Second)
+
+	for {
+		select {
+		case <-tick.C:
+			currentMsgReceived := atomic.SwapInt64(&msgReceived, 0)
+			currentBytesReceived := atomic.SwapInt64(&bytesReceived, 0)
+			msgRate := float64(currentMsgReceived) / float64(10)
+			bytesRate := float64(currentBytesReceived) / float64(10)
+
+			log.Printf(`Stats - Consume rate: %6.1f msg/s - %6.1f Mbps`,
+				msgRate, bytesRate*8/1024/1024)
+		}
+	}
+}
\ No newline at end of file
diff --git a/perf/perf-producer.go b/perf/perf-producer.go
new file mode 100644
index 0000000..8b5c0fd
--- /dev/null
+++ b/perf/perf-producer.go
@@ -0,0 +1,148 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package main
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"github.com/beefsack/go-rate"
+	"github.com/bmizerany/perks/quantile"
+	"github.com/spf13/cobra"
+	"log"
+	"pulsar-client-go-native/pulsar"
+	"time"
+)
+
+type ProduceArgs struct {
+	Topic             string
+	Rate              int
+	Batching          bool
+	MessageSize       int
+	ProducerQueueSize int
+}
+
+var produceArgs ProduceArgs
+
+var cmdProduce = &cobra.Command{
+	Use:   "produce ",
+	Short: "Produce on a topic and measure performance",
+	Args:  cobra.ExactArgs(1),
+	Run: func(cmd *cobra.Command, args []string) {
+		produceArgs.Topic = args[0]
+		produce()
+	},
+}
+
+func initProducer() {
+	cmdProduce.Flags().IntVarP(&produceArgs.Rate, "rate", "r", 100, "Publish rate. Set to 0 to go unthrottled")
+	cmdProduce.Flags().BoolVarP(&produceArgs.Batching, "batching", "b", true, "Enable batching")
+	cmdProduce.Flags().IntVarP(&produceArgs.MessageSize, "size", "s", 1024, "Message size")
+	cmdProduce.Flags().IntVarP(&produceArgs.ProducerQueueSize, "queue-size", "q", 1000, "Produce queue size")
+}
+
+func produce() {
+	b, _ := json.MarshalIndent(clientArgs, "", "  ")
+	fmt.Println("Client config: ", string(b))
+	b, _ = json.MarshalIndent(produceArgs, "", "  ")
+	fmt.Println("Producer config: ", string(b))
+
+	client, err := pulsar.NewClient(pulsar.ClientOptions{
+		URL: clientArgs.ServiceUrl,
+	})
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	defer client.Close()
+
+	producer, err := client.CreateProducer(pulsar.ProducerOptions{
+		Topic:                   produceArgs.Topic,
+		MaxPendingMessages:      produceArgs.ProducerQueueSize,
+		BatchingMaxPublishDelay: 1 * time.Millisecond,
+		SendTimeout:             0,
+		BlockIfQueueFull:        true,
+	})
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	defer producer.Close()
+
+	ctx := context.Background()
+
+	payload := make([]byte, produceArgs.MessageSize)
+
+	ch := make(chan float64)
+
+	go func() {
+		var rateLimiter *rate.RateLimiter = nil
+		if produceArgs.Rate > 0 {
+			rateLimiter = rate.New(produceArgs.Rate, time.Second)
+		}
+
+		for {
+			if rateLimiter != nil {
+				rateLimiter.Wait()
+			}
+
+			start := time.Now()
+
+			producer.SendAsync(ctx, &pulsar.ProducerMessage{
+				Payload: payload,
+			}, func(msgID pulsar.MessageID, message *pulsar.ProducerMessage, e error) {
+				if e != nil {
+					log.Fatal("Failed to publish", e)
+				}
+
+				latency := time.Since(start).Seconds()
+				ch <- latency
+			})
+		}
+	}()
+
+	// Print stats of the publish rate and latencies
+	tick := time.NewTicker(10 * time.Second)
+	q := quantile.NewTargeted(0.90, 0.95, 0.99, 0.999, 1.0)
+	messagesPublished := 0
+
+	for {
+		select {
+		case <-tick.C:
+			messageRate := float64(messagesPublished) / float64(10)
+			log.Printf(`Stats - Publish rate: %6.1f msg/s - %6.1f Mbps - Latency ms: 50%% %5.1f - 95%% %5.1f - 99%% %5.1f - 99.9%% %5.1f - max %6.1f`,
+				messageRate,
+				messageRate*float64(produceArgs.MessageSize)/1024/1024*8,
+				q.Query(0.5)*1000,
+				q.Query(0.95)*1000,
+				q.Query(0.99)*1000,
+				q.Query(0.999)*1000,
+				q.Query(1.0)*1000,
+			)
+
+			q.Reset()
+			messagesPublished = 0
+		case latency := <-ch:
+			messagesPublished += 1
+			q.Insert(latency)
+		}
+	}
+}
diff --git a/perf/pulsar-perf-go.go b/perf/pulsar-perf-go.go
new file mode 100644
index 0000000..7a6d101
--- /dev/null
+++ b/perf/pulsar-perf-go.go
@@ -0,0 +1,49 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package main
+
+import (
+	"github.com/spf13/cobra"
+	log "github.com/sirupsen/logrus"
+)
+
+type ClientArgs struct {
+	ServiceUrl      string
+}
+
+var clientArgs ClientArgs
+
+func main() {
+	log.SetFormatter(&log.TextFormatter{
+		FullTimestamp:   true,
+		TimestampFormat: "15:04:05.000",
+	})
+	log.SetLevel(log.InfoLevel)
+
+	initProducer()
+	initConsumer()
+
+	var rootCmd = &cobra.Command{Use: "pulsar-perf-go"}
+	rootCmd.Flags().StringVarP(&clientArgs.ServiceUrl, "service-url", "u",
+		"pulsar://localhost:6650", "The Pulsar service URL")
+	rootCmd.AddCommand(cmdProduce, cmdConsume)
+
+	rootCmd.Execute()
+}


[pulsar-client-go] 03/38: Use string for result errors

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit 0c2da14b1242290507109a1be7f5b1149aea26a5
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Thu Mar 28 09:21:42 2019 -0700

    Use string for result errors
---
 pulsar/client.go                  |  2 +-
 pulsar/error.go                   | 94 ++++++++++++++++++++++++---------------
 pulsar/impl_client.go             | 21 +++++----
 pulsar/impl_client_test.go        | 14 ++++++
 pulsar/impl_partition_producer.go | 42 ++++++++---------
 pulsar/impl_producer.go           |  8 ++--
 pulsar/message.go                 |  8 ++--
 7 files changed, 117 insertions(+), 72 deletions(-)

diff --git a/pulsar/client.go b/pulsar/client.go
index 21db637..e68f09c 100644
--- a/pulsar/client.go
+++ b/pulsar/client.go
@@ -28,7 +28,7 @@ func NewClient(options ClientOptions) (Client, error) {
 }
 
 // Opaque interface that represents the authentication credentials
-type Authentication interface {}
+type Authentication interface{}
 
 // Create new Authentication provider with specified auth token
 func NewAuthenticationToken(token string) Authentication {
diff --git a/pulsar/error.go b/pulsar/error.go
index 929b250..24e1536 100644
--- a/pulsar/error.go
+++ b/pulsar/error.go
@@ -18,45 +18,48 @@
 //
 
 package pulsar
+
 import "C"
 import "fmt"
 
 type Result int
 
 const (
-	UnknownError                          Result = 1  // Unknown error happened on broker
-	InvalidConfiguration                  Result = 2  // Invalid configuration
-	TimeoutError                          Result = 3  // Operation timed out
-	LookupError                           Result = 4  // Broker lookup failed
-	ConnectError                          Result = 5  // Failed to connect to broker
-	ReadError                             Result = 6  // Failed to read from socket
-	AuthenticationError                   Result = 7  // Authentication failed on broker
-	AuthorizationError                    Result = 8  // Client is not authorized to create producer/consumer
-	ErrorGettingAuthenticationData        Result = 9  // Client cannot find authorization data
-	BrokerMetadataError                   Result = 10 // Broker failed in updating metadata
-	BrokerPersistenceError                Result = 11 // Broker failed to persist entry
-	ChecksumError                         Result = 12 // Corrupt message checksum failure
-	ConsumerBusy                          Result = 13 // Exclusive consumer is already connected
-	NotConnectedError                     Result = 14 // Producer/Consumer is not currently connected to broker
-	AlreadyClosedError                    Result = 15 // Producer/Consumer is already closed and not accepting any operation
-	InvalidMessage                        Result = 16 // Error in publishing an already used message
-	ConsumerNotInitialized                Result = 17 // Consumer is not initialized
-	ProducerNotInitialized                Result = 18 // Producer is not initialized
-	TooManyLookupRequestException         Result = 19 // Too Many concurrent LookupRequest
-	InvalidTopicName                      Result = 20 // Invalid topic name
-	InvalidUrl                            Result = 21 // Client Initialized with Invalid Broker Url (VIP Url passed to Client Constructor)
-	ServiceUnitNotReady                   Result = 22 // Service Unit unloaded between client did lookup and producer/consumer got created
-	OperationNotSupported                 Result = 23
-	ProducerBlockedQuotaExceededError     Result = 24 // Producer is blocked
-	ProducerBlockedQuotaExceededException Result = 25 // Producer is getting exception
-	ProducerQueueIsFull                   Result = 26 // Producer queue is full
-	MessageTooBig                         Result = 27 // Trying to send a messages exceeding the max size
-	TopicNotFound                         Result = 28 // Topic not found
-	SubscriptionNotFound                  Result = 29 // Subscription not found
-	ConsumerNotFound                      Result = 30 // Consumer not found
-	UnsupportedVersionError               Result = 31 // Error when an older client/version doesn't support a required feature
-	TopicTerminated                       Result = 32 // Topic was already terminated
-	CryptoError                           Result = 33 // Error when crypto operation fails
+	ResultOk                   = iota // No errors
+	ResultUnknownError                // Unknown error happened on broker
+	ResultInvalidConfiguration        // Invalid configuration
+	ResultTimeoutError                // Operation timed out
+	ResultLookupError                 // Broker lookup failed
+	ResultInvalidTopicName            // Invalid topic name
+	ResultConnectError                // Failed to connect to broker
+
+	//ReadError                      Result = 6  // Failed to read from socket
+	//AuthenticationError            Result = 7  // Authentication failed on broker
+	//AuthorizationError             Result = 8  // Client is not authorized to create producer/consumer
+	//ErrorGettingAuthenticationData Result = 9  // Client cannot find authorization data
+	//BrokerMetadataError            Result = 10 // Broker failed in updating metadata
+	//BrokerPersistenceError         Result = 11 // Broker failed to persist entry
+	//ChecksumError                  Result = 12 // Corrupt message checksum failure
+	//ConsumerBusy                   Result = 13 // Exclusive consumer is already connected
+	//NotConnectedError              Result = 14 // Producer/Consumer is not currently connected to broker
+	//AlreadyClosedError             Result = 15 // Producer/Consumer is already closed and not accepting any operation
+	//InvalidMessage                 Result = 16 // Error in publishing an already used message
+	//ConsumerNotInitialized         Result = 17 // Consumer is not initialized
+	//ProducerNotInitialized         Result = 18 // Producer is not initialized
+	//TooManyLookupRequestException  Result = 19 // Too Many concurrent LookupRequest
+	//InvalidUrl                            Result = 21 // Client Initialized with Invalid Broker Url (VIP Url passed to Client Constructor)
+	//ServiceUnitNotReady                   Result = 22 // Service Unit unloaded between client did lookup and producer/consumer got created
+	//OperationNotSupported                 Result = 23
+	//ProducerBlockedQuotaExceededError     Result = 24 // Producer is blocked
+	//ProducerBlockedQuotaExceededException Result = 25 // Producer is getting exception
+	//ProducerQueueIsFull                   Result = 26 // Producer queue is full
+	//MessageTooBig                         Result = 27 // Trying to send a messages exceeding the max size
+	//TopicNotFound                         Result = 28 // Topic not found
+	//SubscriptionNotFound                  Result = 29 // Subscription not found
+	//ConsumerNotFound                      Result = 30 // Consumer not found
+	//UnsupportedVersionError               Result = 31 // Error when an older client/version doesn't support a required feature
+	//TopicTerminated                       Result = 32 // Topic was already terminated
+	//CryptoError                           Result = 33 // Error when crypto operation fails
 )
 
 type Error struct {
@@ -74,7 +77,28 @@ func (e *Error) Error() string {
 
 func newError(result Result, msg string) error {
 	return &Error{
-		msg:    fmt.Sprintf("%s: %d", msg, result),
+		msg:    fmt.Sprintf("%s: %s", msg, getResultStr(result)),
 		result: result,
 	}
-}
\ No newline at end of file
+}
+
+func getResultStr(r Result) string {
+	switch r {
+	case ResultOk:
+		return "OK"
+	case ResultUnknownError:
+		return "Unknown error"
+	case ResultInvalidConfiguration:
+		return "InvalidConfiguration"
+	case ResultTimeoutError:
+		return "TimeoutError"
+	case ResultLookupError:
+		return "LookupError"
+	case ResultInvalidTopicName:
+		return "InvalidTopicName"
+	case ResultConnectError:
+		return "ConnectError"
+	default:
+		return fmt.Sprintf("Result(%d)", r)
+	}
+}
diff --git a/pulsar/impl_client.go b/pulsar/impl_client.go
index e143601..4a7047f 100644
--- a/pulsar/impl_client.go
+++ b/pulsar/impl_client.go
@@ -20,17 +20,17 @@ type client struct {
 
 func newClient(options ClientOptions) (Client, error) {
 	if options.URL == "" {
-		return nil, newError(InvalidConfiguration, "URL is required for client")
+		return nil, newError(ResultInvalidConfiguration, "URL is required for client")
 	}
 
 	url, err := url.Parse(options.URL)
 	if err != nil {
 		log.WithError(err).Error("Failed to parse service URL")
-		return nil, newError(InvalidConfiguration, "Invalid service URL")
+		return nil, newError(ResultInvalidConfiguration, "Invalid service URL")
 	}
 
 	if url.Scheme != "pulsar" {
-		return nil, newError(InvalidConfiguration, fmt.Sprintf("Invalid URL scheme '%s'", url.Scheme))
+		return nil, newError(ResultInvalidConfiguration, fmt.Sprintf("Invalid URL scheme '%s'", url.Scheme))
 	}
 
 	c := &client{
@@ -77,14 +77,19 @@ func (client *client) TopicPartitions(topic string) ([]string, error) {
 
 	r := res.Response.PartitionMetadataResponse
 	if r.Error != nil {
-		return nil, newError(LookupError, r.GetError().String())
+		return nil, newError(ResultLookupError, r.GetError().String())
 	}
 
-	partitions := make([]string, r.GetPartitions())
-	for i := 0; i < int(r.GetPartitions()); i++ {
-		partitions[i] = fmt.Sprintf("%s-partition-%d", topic, i)
+	if r.GetPartitions() > 0 {
+		partitions := make([]string, r.GetPartitions())
+		for i := 0; i < int(r.GetPartitions()); i++ {
+			partitions[i] = fmt.Sprintf("%s-partition-%d", topic, i)
+		}
+		return partitions, nil
+	} else {
+		// Non-partitioned topic
+		return []string{topicName.Name}, nil
 	}
-	return partitions, nil
 }
 
 func (client *client) Close() error {
diff --git a/pulsar/impl_client_test.go b/pulsar/impl_client_test.go
new file mode 100644
index 0000000..99f0c78
--- /dev/null
+++ b/pulsar/impl_client_test.go
@@ -0,0 +1,14 @@
+package pulsar
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestClient(t *testing.T) {
+	client, err := NewClient(ClientOptions{})
+	assert.Nil(t, client)
+	assert.NotNil(t, err)
+	assert.Equal(t, Result(ResultInvalidConfiguration), err.(*Error).Result())
+}
diff --git a/pulsar/impl_partition_producer.go b/pulsar/impl_partition_producer.go
index bb96a27..d035ad1 100644
--- a/pulsar/impl_partition_producer.go
+++ b/pulsar/impl_partition_producer.go
@@ -3,8 +3,8 @@ package pulsar
 import (
 	"context"
 	log "github.com/sirupsen/logrus"
-	"pulsar-client-go-native/pulsar/impl"
-	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+	//"pulsar-client-go-native/pulsar/impl"
+	//pb "pulsar-client-go-native/pulsar/pulsar_proto"
 	"sync"
 )
 
@@ -33,25 +33,25 @@ func newPartitionProducer(client *client, topic string, options *ProducerOptions
 }
 
 func (p *partitionProducer) grabCnx() error {
-	lr, err := p.client.lookupService.Lookup(p.topic)
-	if err != nil {
-		p.log.WithError(err).Warn("Failed to lookup topic")
-		return err
-	}
-
-	id := p.client.rpcClient.NewRequestId()
-	p.client.rpcClient.Request(lr.LogicalAddr.Host, lr.PhysicalAddr.Host, id, pb.BaseCommand_PRODUCER, *pb.CommandProducer{
-
-	})
-
-	var cnx impl.Connection
-	cnx, err = p.client.cnxPool.GetConnection(lr.LogicalAddr.Host, lr.PhysicalAddr.Host)
-	if err != nil {
-		p.log.WithError(err).Warn("Failed to get connection")
-		return err
-	}
-
-	cnx.
+	//lr, err := p.client.lookupService.Lookup(p.topic)
+	//if err != nil {
+	//	p.log.WithError(err).Warn("Failed to lookup topic")
+	//	return err
+	//}
+
+	//id := p.client.rpcClient.NewRequestId()
+	//p.client.rpcClient.Request(lr.LogicalAddr.Host, lr.PhysicalAddr.Host, id, pb.BaseCommand_PRODUCER, *pb.CommandProducer{
+	//
+	//})
+	//
+	//var cnx impl.Connection
+	//cnx, err = p.client.cnxPool.GetConnection(lr.LogicalAddr.Host, lr.PhysicalAddr.Host)
+	//if err != nil {
+	//	p.log.WithError(err).Warn("Failed to get connection")
+	//	return err
+	//}
+
+	//cnx.
 
 	return nil
 }
diff --git a/pulsar/impl_producer.go b/pulsar/impl_producer.go
index f2332ea..587b09d 100644
--- a/pulsar/impl_producer.go
+++ b/pulsar/impl_producer.go
@@ -2,6 +2,7 @@ package pulsar
 
 import (
 	"context"
+	"fmt"
 	"pulsar-client-go-native/pulsar/impl"
 	"sync"
 )
@@ -14,7 +15,7 @@ type producer struct {
 
 func newProducer(client *client, options ProducerOptions) (*producer, error) {
 	if options.Topic == "" {
-		return nil, newError(InvalidTopicName, "Topic name is required for producer")
+		return nil, newError(ResultInvalidTopicName, "Topic name is required for producer")
 	}
 
 	p := &producer{
@@ -47,7 +48,8 @@ func newProducer(client *client, options ProducerOptions) (*producer, error) {
 	for i := 0; i < numPartitions; i++ {
 		partition := i
 		go func() {
-			prod, err := newPartitionProducer(client, &options)
+			partitionName := fmt.Sprintf("%s-partition-%d", options.Topic, partition)
+			prod, err := newPartitionProducer(client, partitionName, &options)
 			c <- ProducerError{partition, prod, err}
 		}()
 	}
@@ -62,7 +64,7 @@ func newProducer(client *client, options ProducerOptions) (*producer, error) {
 		// Since there were some failures, cleanup all the partitions that succeeded in creating the producers
 		for _, producer := range p.producers {
 			if producer != nil {
-				_ := producer.Close()
+				_ = producer.Close()
 			}
 		}
 		return nil, err
diff --git a/pulsar/message.go b/pulsar/message.go
index b829b96..ad61704 100644
--- a/pulsar/message.go
+++ b/pulsar/message.go
@@ -82,9 +82,9 @@ func DeserializeMessageID(data []byte) MessageID {
 }
 
 var (
-	// MessageID that points to the earliest message avaialable in a topic
-	//EarliestMessage MessageID = earliestMessageID()
+// MessageID that points to the earliest message avaialable in a topic
+//EarliestMessage MessageID = earliestMessageID()
 
-	// MessageID that points to the latest message
-	//LatestMessage MessageID = latestMessageID()
+// MessageID that points to the latest message
+//LatestMessage MessageID = latestMessageID()
 )


[pulsar-client-go] 37/38: Added token auth provider

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit e058b846ceb683d06a0793fc8af0873705b35d68
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Wed May 8 18:13:09 2019 -0700

    Added token auth provider
---
 Dockerfile                          |  9 +---
 integration-tests/tokens/secret.key |  1 +
 integration-tests/tokens/token.txt  |  1 +
 pulsar/client.go                    | 10 ++--
 pulsar/impl_client.go               | 28 -----------
 pulsar/impl_client_test.go          | 42 +++++++++++++++-
 pulsar/internal/auth/provider.go    |  3 ++
 pulsar/internal/auth/token.go       | 98 +++++++++++++++++++++++++++++++++++++
 pulsar/test_helper.go               |  3 +-
 9 files changed, 151 insertions(+), 44 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index 3451983..1186f96 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -27,15 +27,8 @@ ENV PATH /root/go/bin:/usr/local/go/bin:$PATH
 ### Add test scripts
 
 COPY integration-tests/certs /pulsar/certs
+COPY integration-tests/tokens /pulsar/tokens
 COPY integration-tests/standalone.conf /pulsar/conf
 COPY integration-tests/client.conf /pulsar/conf
 COPY pulsar-test-service-start.sh /pulsar/bin
 COPY pulsar-test-service-stop.sh /pulsar/bin
-
-# Initialize test configuration and credentials
-RUN mkdir /pulsar/tokens
-RUN /pulsar/bin/pulsar tokens create-secret-key --output /pulsar/tokens/secret.key
-RUN /pulsar/bin/pulsar tokens create \
-                --subject token-principal \
-                --secret-key file:///pulsar/tokens/secret.key \
-                > /pulsar/tokens/token.txt
diff --git a/integration-tests/tokens/secret.key b/integration-tests/tokens/secret.key
new file mode 100644
index 0000000..92e05b9
--- /dev/null
+++ b/integration-tests/tokens/secret.key
@@ -0,0 +1 @@
+=Z��k؁HKX��w<��H�߾V�`}
h�
\ No newline at end of file
diff --git a/integration-tests/tokens/token.txt b/integration-tests/tokens/token.txt
new file mode 100644
index 0000000..b1b8309
--- /dev/null
+++ b/integration-tests/tokens/token.txt
@@ -0,0 +1 @@
+eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0b2tlbi1wcmluY2lwYWwifQ.tSfgR8l7dKC6LoWCxQgNkuSB8our7xV_nAM7wpgCbG4
diff --git a/pulsar/client.go b/pulsar/client.go
index 1a838ac..0225a83 100644
--- a/pulsar/client.go
+++ b/pulsar/client.go
@@ -37,14 +37,12 @@ func NewAuthentication(name string, params string) (Authentication, error) {
 
 // Create new Authentication provider with specified auth token
 func NewAuthenticationToken(token string) Authentication {
-	// TODO: return newAuthenticationToken(token)
-	return nil
+	return auth.NewAuthenticationToken(token)
 }
 
-// Create new Authentication provider with specified auth token supplier
-func NewAuthenticationTokenSupplier(tokenSupplier func() string) Authentication {
-	// TODO:  return newAuthenticationTokenSupplier(tokenSupplier)
-	return nil
+// Create new Authentication provider with specified auth token from a file
+func NewAuthenticationTokenFromFile(tokenFilePath string) Authentication {
+	return auth.NewAuthenticationTokenFromFile(tokenFilePath)
 }
 
 // Create new Authentication provider with specified TLS certificate and private key
diff --git a/pulsar/impl_client.go b/pulsar/impl_client.go
index b84ac82..a952efc 100644
--- a/pulsar/impl_client.go
+++ b/pulsar/impl_client.go
@@ -20,12 +20,9 @@
 package pulsar
 
 import (
-	"crypto/tls"
-	"crypto/x509"
 	"fmt"
 	"github.com/pkg/errors"
 	log "github.com/sirupsen/logrus"
-	"io/ioutil"
 	"net/url"
 	"pulsar-client-go/pulsar/internal"
 	"pulsar-client-go/pulsar/internal/auth"
@@ -153,28 +150,3 @@ func (client *client) Close() error {
 
 	return nil
 }
-
-func getTlsConfig(options ClientOptions) (*tls.Config, error) {
-	tlsConfig := &tls.Config{
-		InsecureSkipVerify: options.TLSAllowInsecureConnection,
-	}
-
-	if options.TLSTrustCertsFilePath != "" {
-		caCerts, err := ioutil.ReadFile(options.TLSTrustCertsFilePath)
-		if err != nil {
-			return nil, err
-		}
-
-		tlsConfig.RootCAs = x509.NewCertPool()
-		ok := tlsConfig.RootCAs.AppendCertsFromPEM([]byte(caCerts))
-		if !ok {
-			return nil, errors.New("failed to parse root CAs certificates")
-		}
-	}
-
-	if options.TLSValidateHostname {
-		tlsConfig.ServerName = options.URL
-	}
-
-	return tlsConfig, nil
-}
diff --git a/pulsar/impl_client_test.go b/pulsar/impl_client_test.go
index 218587f..4d11ee6 100644
--- a/pulsar/impl_client_test.go
+++ b/pulsar/impl_client_test.go
@@ -20,6 +20,7 @@
 package pulsar
 
 import (
+	"io/ioutil"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -147,7 +148,46 @@ func TestTLSAuth(t *testing.T) {
 	client, err := NewClient(ClientOptions{
 		URL:                   serviceUrlTls,
 		TLSTrustCertsFilePath: caCertsPath,
-		Authentication: NewAuthenticationTLS(tlsClientCertPath, tlsClientKeyPath),
+		Authentication:        NewAuthenticationTLS(tlsClientCertPath, tlsClientKeyPath),
+	})
+	assert.NoError(t, err)
+
+	producer, err := client.CreateProducer(ProducerOptions{
+		Topic: newAuthTopicName(),
+	})
+
+	assert.NoError(t, err)
+	assert.NotNil(t, producer)
+
+	err = client.Close()
+	assert.NoError(t, err)
+}
+
+func TestTokenAuth(t *testing.T) {
+	token, err := ioutil.ReadFile(tokenFilePath)
+	assert.NoError(t, err)
+
+	client, err := NewClient(ClientOptions{
+		URL:            serviceUrl,
+		Authentication: NewAuthenticationToken(string(token)),
+	})
+	assert.NoError(t, err)
+
+	producer, err := client.CreateProducer(ProducerOptions{
+		Topic: newAuthTopicName(),
+	})
+
+	assert.NoError(t, err)
+	assert.NotNil(t, producer)
+
+	err = client.Close()
+	assert.NoError(t, err)
+}
+
+func TestTokenAuthFromFile(t *testing.T) {
+	client, err := NewClient(ClientOptions{
+		URL:            serviceUrl,
+		Authentication: NewAuthenticationTokenFromFile(tokenFilePath),
 	})
 	assert.NoError(t, err)
 
diff --git a/pulsar/internal/auth/provider.go b/pulsar/internal/auth/provider.go
index c23a662..48c20dc 100644
--- a/pulsar/internal/auth/provider.go
+++ b/pulsar/internal/auth/provider.go
@@ -50,6 +50,9 @@ func NewProvider(name string, params string) (Provider, error) {
 	case "tls", "org.apache.pulsar.client.impl.auth.AuthenticationTls":
 		return NewAuthenticationTLSWithParams(m), nil
 
+	case "token", "org.apache.pulsar.client.impl.auth.AuthenticationToken":
+		return NewAuthenticationTokenWithParams(m)
+
 	default:
 		return nil, errors.New(fmt.Sprintf("invalid auth provider '%s'", name))
 	}
diff --git a/pulsar/internal/auth/token.go b/pulsar/internal/auth/token.go
new file mode 100644
index 0000000..d14279b
--- /dev/null
+++ b/pulsar/internal/auth/token.go
@@ -0,0 +1,98 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package auth
+
+import (
+	"crypto/tls"
+	"github.com/pkg/errors"
+	"io/ioutil"
+	"strings"
+)
+
+type tokenAuthProvider struct {
+	tokenSupplier func() (string, error)
+}
+
+func NewAuthenticationTokenWithParams(params map[string]string) (Provider, error) {
+	if params["token"] != "" {
+		return NewAuthenticationToken(params["token"]), nil
+	} else if params["file"] != "" {
+		return NewAuthenticationTokenFromFile(params["file"]), nil
+	} else {
+		return nil, errors.New("missing configuration for token auth")
+	}
+}
+
+func NewAuthenticationToken(token string) Provider {
+	return &tokenAuthProvider{
+		tokenSupplier: func() (string, error) {
+			if token == "" {
+				return "", errors.New("empty token credentials")
+			} else {
+				return token, nil
+			}
+		},
+	}
+}
+
+func NewAuthenticationTokenFromFile(tokenFilePath string) Provider {
+	return &tokenAuthProvider{
+		tokenSupplier: func() (string, error) {
+			data, err := ioutil.ReadFile(tokenFilePath)
+			if err != nil {
+				return "", err
+			}
+
+			token := strings.Trim(string(data), " \n")
+			if token == "" {
+				return "", errors.New("empty token credentials")
+			} else {
+				return token, nil
+			}
+		},
+	}
+}
+
+func (p *tokenAuthProvider) Init() error {
+	// Try to read certificates immediately to provide better error at startup
+	_, err := p.GetData()
+	return err
+}
+
+func (p *tokenAuthProvider) Name() string {
+	return "token"
+}
+
+func (p *tokenAuthProvider) GetTlsCertificate() (*tls.Certificate, error) {
+	return nil, nil
+}
+
+func (p *tokenAuthProvider) GetData() ([]byte, error) {
+	t, err := p.tokenSupplier()
+	if err != nil {
+		return nil, err
+	} else {
+		return []byte(t), nil
+	}
+}
+
+func (tokenAuthProvider) Close() error {
+	return nil
+}
diff --git a/pulsar/test_helper.go b/pulsar/test_helper.go
index 1e84a92..f4c9d7d 100644
--- a/pulsar/test_helper.go
+++ b/pulsar/test_helper.go
@@ -30,7 +30,8 @@ const (
 
 	caCertsPath       = "../integration-tests/certs/cacert.pem"
 	tlsClientCertPath = "../integration-tests/certs/client-cert.pem"
-	tlsClientKeyPath = "../integration-tests/certs/client-key.pem"
+	tlsClientKeyPath  = "../integration-tests/certs/client-key.pem"
+	tokenFilePath     = "../integration-tests/tokens/token.txt"
 )
 
 func newTopicName() string {


[pulsar-client-go] 13/38: Added blocking queue iterator

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit ecab9cf16eed5095bf39cbb376ca80248417db1c
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Wed Apr 10 14:46:44 2019 -0700

    Added blocking queue iterator
---
 pulsar/impl/util/blocking_queue.go      | 44 +++++++++++++++++++++++++++++++++
 pulsar/impl/util/blocking_queue_test.go | 15 +++++++++++
 2 files changed, 59 insertions(+)

diff --git a/pulsar/impl/util/blocking_queue.go b/pulsar/impl/util/blocking_queue.go
index 281d6ba..1b6a609 100644
--- a/pulsar/impl/util/blocking_queue.go
+++ b/pulsar/impl/util/blocking_queue.go
@@ -1,6 +1,7 @@
 package util
 
 import (
+	log "github.com/sirupsen/logrus"
 	"sync"
 )
 
@@ -19,6 +20,14 @@ type BlockingQueue interface {
 
 	// Return the current size of the queue
 	Size() int
+
+	// Return an iterator for the queue
+	Iterator() BlockingQueueIterator
+}
+
+type BlockingQueueIterator interface {
+	HasNext() bool
+	Next() interface{}
 }
 
 type blockingQueue struct {
@@ -33,6 +42,12 @@ type blockingQueue struct {
 	isNotFull  *sync.Cond
 }
 
+type blockingQueueIterator struct {
+	bq      *blockingQueue
+	readIdx int
+	toRead  int
+}
+
 func NewBlockingQueue(maxSize int) BlockingQueue {
 	bq := &blockingQueue{
 		items:   make([]interface{}, maxSize),
@@ -123,3 +138,32 @@ func (bq *blockingQueue) Size() int {
 
 	return bq.size
 }
+
+func (bq *blockingQueue) Iterator() BlockingQueueIterator {
+	bq.mutex.Lock()
+	defer bq.mutex.Unlock()
+
+	return &blockingQueueIterator{
+		bq:      bq,
+		readIdx: bq.headIdx,
+		toRead:  bq.size,
+	}
+}
+
+func (bqi *blockingQueueIterator) HasNext() bool {
+	return bqi.toRead > 0
+}
+
+func (bqi *blockingQueueIterator) Next() interface{} {
+	if bqi.toRead == 0 {
+		log.Panic("Trying to read past the end of the iterator")
+	}
+
+	item := bqi.bq.items[bqi.readIdx]
+	bqi.toRead--
+	bqi.readIdx++
+	if bqi.readIdx == bqi.bq.maxSize {
+		bqi.readIdx = 0
+	}
+	return item
+}
diff --git a/pulsar/impl/util/blocking_queue_test.go b/pulsar/impl/util/blocking_queue_test.go
index 0ecc8d1..dd17db4 100644
--- a/pulsar/impl/util/blocking_queue_test.go
+++ b/pulsar/impl/util/blocking_queue_test.go
@@ -95,3 +95,18 @@ func TestBlockingQueueWaitWhenFull(t *testing.T) {
 
 	close(ch)
 }
+
+func TestBlockingQueueIterator(t *testing.T) {
+	q := NewBlockingQueue(10)
+
+	q.Put(1)
+	q.Put(2)
+	q.Put(3)
+	assert.Equal(t, 3, q.Size())
+
+	i := 1
+	for it := q.Iterator(); it.HasNext(); {
+		assert.Equal(t, i, it.Next())
+		i++
+	}
+}


[pulsar-client-go] 05/38: Implemented keep-alive logic

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit 1bdf19ff647f585ef792fdb35eaf084b9de44d04
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Sat Mar 30 11:17:12 2019 -0700

    Implemented keep-alive logic
---
 pulsar/impl/commands.go   | 11 +++----
 pulsar/impl/connection.go | 74 +++++++++++++++++++++++++++++++----------------
 2 files changed, 53 insertions(+), 32 deletions(-)

diff --git a/pulsar/impl/commands.go b/pulsar/impl/commands.go
index 16e5b60..c803602 100644
--- a/pulsar/impl/commands.go
+++ b/pulsar/impl/commands.go
@@ -15,19 +15,16 @@ func baseCommand(cmdType pb.BaseCommand_Type, msg proto.Message) *pb.BaseCommand
 	switch cmdType {
 	case pb.BaseCommand_CONNECT:
 		cmd.Connect = msg.(*pb.CommandConnect)
-		break
 	case pb.BaseCommand_LOOKUP:
 		cmd.LookupTopic = msg.(*pb.CommandLookupTopic)
-		break
-
 	case pb.BaseCommand_PARTITIONED_METADATA:
 		cmd.PartitionMetadata = msg.(*pb.CommandPartitionedTopicMetadata)
-		break
-
 	case pb.BaseCommand_PRODUCER:
 		cmd.Producer = msg.(*pb.CommandProducer)
-		break
-
+	case pb.BaseCommand_PING:
+		cmd.Ping = msg.(*pb.CommandPing)
+	case pb.BaseCommand_PONG:
+		cmd.Pong = msg.(*pb.CommandPong)
 	default:
 		log.Panic("Missing command type: ", cmdType)
 	}
diff --git a/pulsar/impl/connection.go b/pulsar/impl/connection.go
index 28871cb..2bd2984 100644
--- a/pulsar/impl/connection.go
+++ b/pulsar/impl/connection.go
@@ -9,6 +9,7 @@ import (
 	pb "pulsar-client-go-native/pulsar/pulsar_proto"
 	"sync"
 	"sync/atomic"
+	"time"
 )
 
 type Connection interface {
@@ -26,6 +27,8 @@ const (
 	connectionClosed
 )
 
+const keepAliveInterval = 30 * time.Second
+
 type request struct {
 	id       uint64
 	cmd      *pb.BaseCommand
@@ -41,8 +44,10 @@ type connection struct {
 	physicalAddr string
 	cnx          net.Conn
 
-	writeBuffer Buffer
-	reader      *connectionReader
+	writeBuffer          Buffer
+	reader               *connectionReader
+	lastDataReceivedTime time.Time
+	pingTicker           *time.Ticker
 
 	log *log.Entry
 
@@ -54,12 +59,14 @@ type connection struct {
 
 func newConnection(logicalAddr *url.URL, physicalAddr *url.URL) *connection {
 	cnx := &connection{
-		state:        connectionInit,
-		logicalAddr:  logicalAddr.Host,
-		physicalAddr: physicalAddr.Host,
-		writeBuffer:  NewBuffer(4096),
-		log:          log.WithField("raddr", physicalAddr),
-		pendingReqs:  make(map[uint64]*request),
+		state:                connectionInit,
+		logicalAddr:          logicalAddr.Host,
+		physicalAddr:         physicalAddr.Host,
+		writeBuffer:          NewBuffer(4096),
+		log:                  log.WithField("raddr", physicalAddr),
+		pendingReqs:          make(map[uint64]*request),
+		lastDataReceivedTime: time.Now(),
+		pingTicker:           time.NewTicker(keepAliveInterval),
 	}
 	cnx.reader = newConnectionReader(cnx)
 	cnx.cond = sync.NewCond(cnx)
@@ -156,6 +163,9 @@ func (c *connection) run() {
 		case req := <-c.incomingRequests:
 			c.pendingReqs[req.id] = req
 			c.writeCommand(req.cmd)
+
+		case _ = <-c.pingTicker.C:
+			c.sendPing()
 		}
 	}
 }
@@ -189,59 +199,46 @@ func (c *connection) writeCommand(cmd proto.Message) {
 
 func (c *connection) receivedCommand(cmd *pb.BaseCommand, headersAndPayload []byte) {
 	c.log.Infof("Received command: %s -- payload: %v", cmd, headersAndPayload)
+	c.lastDataReceivedTime = time.Now()
 
 	switch *cmd.Type {
 	case pb.BaseCommand_SUCCESS:
 		c.handleResponse(*cmd.Success.RequestId, cmd)
-		break
 
 	case pb.BaseCommand_PRODUCER_SUCCESS:
 		c.handleResponse(*cmd.ProducerSuccess.RequestId, cmd)
-		break
 
 	case pb.BaseCommand_PARTITIONED_METADATA_RESPONSE:
 		c.handleResponse(*cmd.PartitionMetadataResponse.RequestId, cmd)
-		break
 
 	case pb.BaseCommand_LOOKUP_RESPONSE:
 		c.handleResponse(*cmd.LookupTopicResponse.RequestId, cmd)
-		break
 
 	case pb.BaseCommand_CONSUMER_STATS_RESPONSE:
 		c.handleResponse(*cmd.ConsumerStatsResponse.RequestId, cmd)
-		break
 
 	case pb.BaseCommand_GET_LAST_MESSAGE_ID_RESPONSE:
 		c.handleResponse(*cmd.GetLastMessageIdResponse.RequestId, cmd)
-		break
 
 	case pb.BaseCommand_GET_TOPICS_OF_NAMESPACE_RESPONSE:
 		c.handleResponse(*cmd.GetTopicsOfNamespaceResponse.RequestId, cmd)
-		break
 
 	case pb.BaseCommand_GET_SCHEMA_RESPONSE:
 		c.handleResponse(*cmd.GetSchemaResponse.RequestId, cmd)
-		break
 
 	case pb.BaseCommand_ERROR:
-		break
-
 	case pb.BaseCommand_CLOSE_PRODUCER:
 	case pb.BaseCommand_CLOSE_CONSUMER:
-
 	case pb.BaseCommand_SEND_RECEIPT:
-		break
 	case pb.BaseCommand_SEND_ERROR:
-		break
 
 	case pb.BaseCommand_MESSAGE:
-		break
-
+	case pb.BaseCommand_PING:
+		c.handlePing()
 	case pb.BaseCommand_PONG:
+		c.handlePong()
 
 	case pb.BaseCommand_ACTIVE_CONSUMER_CHANGE:
-		// TODO
-		break
 
 	default:
 		c.log.Errorf("Received invalid command type: %s", cmd.Type)
@@ -269,6 +266,27 @@ func (c *connection) handleResponse(requestId uint64, response *pb.BaseCommand)
 	request.callback(response)
 }
 
+func (c *connection) sendPing() {
+	if c.lastDataReceivedTime.Add(2 * keepAliveInterval).Before(time.Now()) {
+		// We have not received a response to the previous Ping request, the
+		// connection to broker is stale
+		c.log.Info("Detected stale connection to broker")
+		c.internalClose()
+		return
+	}
+
+	c.log.Debug("Sending PING")
+	c.writeCommand(baseCommand(pb.BaseCommand_PING, &pb.CommandPing{}))
+}
+
+func (c *connection) handlePong() {
+	c.writeCommand(baseCommand(pb.BaseCommand_PONG, &pb.CommandPong{}))
+}
+
+func (c *connection) handlePing() {
+	c.writeCommand(baseCommand(pb.BaseCommand_PONG, &pb.CommandPong{}))
+}
+
 func (c *connection) Close() {
 	// TODO
 }
@@ -285,10 +303,16 @@ func (c *connection) newRequestId() uint64 {
 }
 
 func (c *connection) internalClose() {
+	c.Lock()
+	defer c.Unlock()
+
 	c.state = connectionClosed
 	c.cond.Broadcast()
 
 	if c.cnx != nil {
+		c.log.Info("Connection closed")
 		c.cnx.Close()
+		c.pingTicker.Stop()
+		c.cnx = nil
 	}
 }


[pulsar-client-go] 17/38: Implemented producer flush

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit ea44ac94fdb99a0d8317a94d000b878c656d027b
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Thu Apr 11 12:16:36 2019 -0700

    Implemented producer flush
---
 pulsar/impl/util/blocking_queue.go      | 17 +++++++++-
 pulsar/impl/util/blocking_queue_test.go |  6 ++++
 pulsar/impl_partition_producer.go       | 56 +++++++++++++++++++++++++--------
 3 files changed, 65 insertions(+), 14 deletions(-)

diff --git a/pulsar/impl/util/blocking_queue.go b/pulsar/impl/util/blocking_queue.go
index 1b6a609..bdf6099 100644
--- a/pulsar/impl/util/blocking_queue.go
+++ b/pulsar/impl/util/blocking_queue.go
@@ -15,9 +15,12 @@ type BlockingQueue interface {
 	// Dequeue one item, return nil if queue is empty
 	Poll() interface{}
 
-	// Return one item without dequeing, return nil if queue is empty
+	// Return the first item without dequeing, return nil if queue is empty
 	Peek() interface{}
 
+	// Return last item in queue without dequeing, return nil if queue is empty
+	PeekLast() interface{}
+
 	// Return the current size of the queue
 	Size() int
 
@@ -118,6 +121,18 @@ func (bq *blockingQueue) Peek() interface{} {
 	}
 }
 
+func (bq *blockingQueue) PeekLast() interface{} {
+	bq.mutex.Lock()
+	defer bq.mutex.Unlock()
+
+	if bq.size == 0 {
+		return nil
+	} else {
+		idx := (bq.headIdx + bq.size - 1) % bq.maxSize
+		return bq.items[idx]
+	}
+}
+
 func (bq *blockingQueue) dequeue() interface{} {
 	item := bq.items[bq.headIdx]
 	bq.items[bq.headIdx] = nil
diff --git a/pulsar/impl/util/blocking_queue_test.go b/pulsar/impl/util/blocking_queue_test.go
index dd17db4..15cda0c 100644
--- a/pulsar/impl/util/blocking_queue_test.go
+++ b/pulsar/impl/util/blocking_queue_test.go
@@ -14,14 +14,18 @@ func TestBlockingQueue(t *testing.T) {
 	assert.Equal(t, 0, q.Size())
 	assert.Equal(t, nil, q.Poll())
 	assert.Equal(t, nil, q.Peek())
+	assert.Equal(t, nil, q.PeekLast())
 
 	q.Put("test")
 	assert.Equal(t, 1, q.Size())
 
 	assert.Equal(t, "test", q.Peek())
+	assert.Equal(t, "test", q.PeekLast())
 	assert.Equal(t, 1, q.Size())
 
 	assert.Equal(t, "test", q.Take())
+	assert.Equal(t, nil, q.Peek())
+	assert.Equal(t, nil, q.PeekLast())
 	assert.Equal(t, 0, q.Size())
 
 	ch := make(chan string)
@@ -65,6 +69,8 @@ func TestBlockingQueueWaitWhenFull(t *testing.T) {
 	q.Put("test-2")
 	q.Put("test-3")
 	assert.Equal(t, 3, q.Size())
+	assert.Equal(t, "test-1", q.Peek())
+	assert.Equal(t, "test-3", q.PeekLast())
 
 	ch := make(chan bool)
 
diff --git a/pulsar/impl_partition_producer.go b/pulsar/impl_partition_producer.go
index c99ea67..2557d1d 100644
--- a/pulsar/impl_partition_producer.go
+++ b/pulsar/impl_partition_producer.go
@@ -168,21 +168,20 @@ func (p *partitionProducer) runEventsLoop() {
 	for {
 		select {
 		case i := <-p.eventsChan:
-			if i == nil {
-				return
-			}
-
 			switch v := i.(type) {
 			case *sendRequest:
 				p.internalSend(v)
 			case *connectionClosed:
 				p.reconnectToBroker()
+			case *flushRequest:
+				p.internalFlush(v)
 			case *closeProducer:
 				p.internalClose(v)
+				return
 			}
 
 		case _ = <-p.batchFlushTicker.C:
-			p.internalFlush()
+			p.internalFlushCurrentBatch()
 		}
 	}
 }
@@ -222,26 +221,26 @@ func (p *partitionProducer) internalSend(request *sendRequest) {
 	if sendAsBatch {
 		for ; p.batchBuilder.Add(smm, sequenceId, msg.Payload, request, msg.ReplicationClusters) == false; {
 			// The current batch is full.. flush it and retry
-			p.internalFlush()
+			p.internalFlushCurrentBatch()
 		}
 	} else {
 		// Send individually
 		p.batchBuilder.Add(smm, sequenceId, msg.Payload, request, msg.ReplicationClusters)
-		p.internalFlush()
+		p.internalFlushCurrentBatch()
 	}
 
 	if request.flushImmediately {
-		p.internalFlush()
+		p.internalFlushCurrentBatch()
 	}
 }
 
 type pendingItem struct {
 	batchData   []byte
 	sequenceId  uint64
-	sendRequest []interface{}
+	sendRequests []interface{}
 }
 
-func (p *partitionProducer) internalFlush() {
+func (p *partitionProducer) internalFlushCurrentBatch() {
 	batchData, sequenceId, callbacks := p.batchBuilder.Flush()
 	if batchData == nil {
 		return
@@ -251,6 +250,18 @@ func (p *partitionProducer) internalFlush() {
 	p.cnx.WriteData(batchData)
 }
 
+func (p *partitionProducer) internalFlush(fr *flushRequest) {
+	p.internalFlushCurrentBatch()
+
+	pi := p.pendingQueue.PeekLast().(*pendingItem)
+	pi.sendRequests = append(pi.sendRequests, &sendRequest{
+		callback: func(id MessageID, message *ProducerMessage, e error) {
+			fr.err = e
+			fr.waitGroup.Done()
+		},
+	})
+}
+
 func (p *partitionProducer) Send(ctx context.Context, msg *ProducerMessage) error {
 	wg := sync.WaitGroup{}
 	wg.Add(1)
@@ -299,9 +310,11 @@ func (p *partitionProducer) ReceivedSendReceipt(response *pb.CommandSendReceipt)
 	// The ack was indeed for the expected item in the queue, we can remove it and trigger the callback
 	p.pendingQueue.Poll()
 	p.publishSemaphore.Release()
-	for _, i := range pi.sendRequest {
+	for _, i := range pi.sendRequests {
 		sr := i.(*sendRequest)
-		sr.callback(nil, sr.msg, nil)
+		if sr.callback != nil {
+			sr.callback(nil, sr.msg, nil)
+		}
 	}
 }
 
@@ -338,10 +351,21 @@ func (p *partitionProducer) LastSequenceID() int64 {
 }
 
 func (p *partitionProducer) Flush() error {
-	return nil
+	wg := sync.WaitGroup{}
+	wg.Add(1)
+
+	cp := &flushRequest{&wg, nil}
+	p.eventsChan <- cp
+
+	wg.Wait()
+	return cp.err
 }
 
 func (p *partitionProducer) Close() error {
+	if p.state != producerReady {
+		// Producer is closing
+		return nil
+	}
 
 	wg := sync.WaitGroup{}
 	wg.Add(1)
@@ -364,3 +388,9 @@ type closeProducer struct {
 	waitGroup *sync.WaitGroup
 	err       error
 }
+
+type flushRequest struct {
+	waitGroup *sync.WaitGroup
+	err       error
+}
+


[pulsar-client-go] 32/38: Addressed comments

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit 617f0d5647347d16e15df8e9db7a6d852df54d38
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Tue May 7 13:16:16 2019 -0700

    Addressed comments
---
 pulsar/error.go                         |  1 -
 pulsar/impl/backoff.go                  |  6 ++++--
 pulsar/impl/batch_builder.go            | 10 +++++-----
 pulsar/{util_test.go => test_helper.go} |  0
 4 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/pulsar/error.go b/pulsar/error.go
index 24e1536..38b9d64 100644
--- a/pulsar/error.go
+++ b/pulsar/error.go
@@ -19,7 +19,6 @@
 
 package pulsar
 
-import "C"
 import "fmt"
 
 type Result int
diff --git a/pulsar/impl/backoff.go b/pulsar/impl/backoff.go
index afd6c4f..3e7629e 100644
--- a/pulsar/impl/backoff.go
+++ b/pulsar/impl/backoff.go
@@ -27,8 +27,10 @@ type Backoff struct {
 	backoff time.Duration
 }
 
-const minBackoff = 100 * time.Millisecond
-const maxBackoff = 60 * time.Second
+const (
+	minBackoff = 100 * time.Millisecond
+	maxBackoff = 60 * time.Second
+)
 
 func (b *Backoff) Next() time.Duration {
 	// Double the delay each time
diff --git a/pulsar/impl/batch_builder.go b/pulsar/impl/batch_builder.go
index 0cb19f0..a3b41a2 100644
--- a/pulsar/impl/batch_builder.go
+++ b/pulsar/impl/batch_builder.go
@@ -27,11 +27,11 @@ import (
 	"time"
 )
 
-const MaxMessageSize = 5 * 1024 * 1024
-
-const MaxBatchSize = 128 * 1024
-
-const DefaultMaxMessagesPerBatch = 1000
+const (
+	MaxMessageSize = 5 * 1024 * 1024
+	MaxBatchSize = 128 * 1024
+	DefaultMaxMessagesPerBatch = 1000
+)
 
 type BatchBuilder struct {
 	buffer Buffer
diff --git a/pulsar/util_test.go b/pulsar/test_helper.go
similarity index 100%
rename from pulsar/util_test.go
rename to pulsar/test_helper.go


[pulsar-client-go] 10/38: Reconnection logic

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit 2e74112c2e6162bfdf702ed03af2550d0d3ddd49
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Sat Apr 6 08:35:50 2019 -0700

    Reconnection logic
---
 pulsar/impl/backoff.go            | 24 ++++++++++++++++++++
 pulsar/impl/batch_builder.go      |  2 +-
 pulsar/impl/connection.go         | 48 ++++++++++++++++++++++++++++++++-------
 pulsar/impl/connection_pool.go    |  3 ++-
 pulsar/impl_partition_producer.go | 28 +++++++++++++++++++++++
 5 files changed, 95 insertions(+), 10 deletions(-)

diff --git a/pulsar/impl/backoff.go b/pulsar/impl/backoff.go
new file mode 100644
index 0000000..451509f
--- /dev/null
+++ b/pulsar/impl/backoff.go
@@ -0,0 +1,24 @@
+package impl
+
+import (
+	"time"
+)
+
+type Backoff struct {
+	backoff time.Duration
+}
+
+const minBackoff = 100 * time.Millisecond
+const maxBackoff = 60 * time.Second
+
+func (b *Backoff) Next() time.Duration {
+	// Double the delay each time
+	b.backoff += b.backoff
+	if b.backoff.Nanoseconds() < minBackoff.Nanoseconds() {
+		b.backoff = minBackoff
+	} else if b.backoff.Nanoseconds() > maxBackoff.Nanoseconds() {
+		b.backoff = maxBackoff
+	}
+
+	return b.backoff
+}
diff --git a/pulsar/impl/batch_builder.go b/pulsar/impl/batch_builder.go
index 6b0df8e..53bf081 100644
--- a/pulsar/impl/batch_builder.go
+++ b/pulsar/impl/batch_builder.go
@@ -84,7 +84,7 @@ func (bb *BatchBuilder) reset() {
 }
 
 func (bb *BatchBuilder) Flush() []byte {
-	log.Info("BatchBuilder flush: messages: ", bb.numMessages)
+	log.Debug("BatchBuilder flush: messages: ", bb.numMessages)
 	if bb.numMessages == 0 {
 		// No-Op for empty batch
 		return nil
diff --git a/pulsar/impl/connection.go b/pulsar/impl/connection.go
index 04fa10f..18a3b63 100644
--- a/pulsar/impl/connection.go
+++ b/pulsar/impl/connection.go
@@ -12,9 +12,17 @@ import (
 	"time"
 )
 
+// ConnectionListener is a user of a connection (eg. a producer or
+// a consumer) that can register itself to get notified
+// when the connection is closed.
+type ConnectionListener interface {
+	ConnectionClosed()
+}
+
 type Connection interface {
 	SendRequest(requestId uint64, req *pb.BaseCommand, callback func(command *pb.BaseCommand))
 	WriteData(data []byte)
+	RegisterListener(listener ConnectionListener)
 	Close()
 }
 
@@ -57,6 +65,7 @@ type connection struct {
 	incomingRequests chan *request
 	writeRequests    chan []byte
 	pendingReqs      map[uint64]*request
+	listeners        []ConnectionListener
 }
 
 func newConnection(logicalAddr *url.URL, physicalAddr *url.URL) *connection {
@@ -72,6 +81,7 @@ func newConnection(logicalAddr *url.URL, physicalAddr *url.URL) *connection {
 
 		incomingRequests: make(chan *request),
 		writeRequests:    make(chan []byte),
+		listeners:        make([]ConnectionListener, 0),
 	}
 	cnx.reader = newConnectionReader(cnx)
 	cnx.cond = sync.NewCond(cnx)
@@ -142,13 +152,15 @@ func (c *connection) waitUntilReady() error {
 	defer c.Unlock()
 
 	for {
+		c.log.Debug("Wait until connection is ready. State: ", c.state)
 		switch c.state {
 		case connectionInit:
+			fallthrough
 		case connectionConnecting:
+			fallthrough
 		case connectionTcpConnected:
 			// Wait for the state to change
 			c.cond.Wait()
-			break
 
 		case connectionReady:
 			return nil
@@ -166,10 +178,16 @@ func (c *connection) run() {
 	for {
 		select {
 		case req := <-c.incomingRequests:
+			if req == nil {
+				return
+			}
 			c.pendingReqs[req.id] = req
 			c.writeCommand(req.cmd)
 
 		case data := <-c.writeRequests:
+			if data == nil {
+				return
+			}
 			c.internalWriteData(data)
 
 		case _ = <-c.pingTicker.C:
@@ -183,7 +201,7 @@ func (c *connection) WriteData(data []byte) {
 }
 
 func (c *connection) internalWriteData(data []byte) {
-	c.log.Info("Write data: ", len(data))
+	c.log.Debug("Write data: ", len(data))
 	if _, err := c.cnx.Write(data); err != nil {
 		c.log.WithError(err).Warn("Failed to write on connection")
 		c.Close()
@@ -313,20 +331,34 @@ func (c *connection) handlePing() {
 	c.writeCommand(baseCommand(pb.BaseCommand_PONG, &pb.CommandPong{}))
 }
 
+func (c *connection) RegisterListener(listener ConnectionListener) {
+	c.Lock()
+	defer c.Unlock()
+
+	c.listeners = append(c.listeners, listener)
+}
+
 func (c *connection) Close() {
 	c.Lock()
 	defer c.Unlock()
 
-	c.state = connectionClosed
 	c.cond.Broadcast()
 
+	if c.state == connectionClosed {
+		return
+	}
+
+	c.log.Info("Connection closed")
+	c.state = connectionClosed
 	if c.cnx != nil {
-		c.log.Info("Connection closed")
 		c.cnx.Close()
-		c.pingTicker.Stop()
-		close(c.incomingRequests)
-		close(c.writeRequests)
-		c.cnx = nil
+	}
+	c.pingTicker.Stop()
+	close(c.incomingRequests)
+	close(c.writeRequests)
+	//c.cnx = nil
+	for _, listener := range c.listeners {
+		listener.ConnectionClosed()
 	}
 }
 
diff --git a/pulsar/impl/connection_pool.go b/pulsar/impl/connection_pool.go
index 1fa647f..ef5ca8b 100644
--- a/pulsar/impl/connection_pool.go
+++ b/pulsar/impl/connection_pool.go
@@ -32,7 +32,8 @@ func (p *connectionPool) GetConnection(logicalAddr *url.URL, physicalAddr *url.U
 			return cnx, nil
 		} else {
 			// The cached connection is failed
-			p.pool.Delete(logicalAddr)
+			p.pool.Delete(logicalAddr.Host)
+			log.Debug("Removed failed connection from pool:", cnx.logicalAddr, cnx.physicalAddr)
 		}
 	}
 
diff --git a/pulsar/impl_partition_producer.go b/pulsar/impl_partition_producer.go
index 2677770..60ae76c 100644
--- a/pulsar/impl_partition_producer.go
+++ b/pulsar/impl_partition_producer.go
@@ -99,10 +99,36 @@ func (p *partitionProducer) grabCnx() error {
 		p.sequenceIdGenerator = &nextSequenceId
 	}
 	p.cnx = res.Cnx
+	p.cnx.RegisterListener(p)
 	p.log.WithField("cnx", res.Cnx).Debug("Connected producer")
 	return nil
 }
 
+type connectionClosed struct {
+}
+
+func (p *partitionProducer) ConnectionClosed() {
+	// Trigger reconnection in the produce goroutine
+	p.eventsChan <- &connectionClosed{}
+}
+
+func (p *partitionProducer) reconnectToBroker() {
+	backoff := impl.Backoff{}
+	for {
+		err := p.grabCnx()
+		if err == nil {
+			// Successfully reconnected
+			return
+		}
+
+		d := backoff.Next()
+		p.log.Info("Retrying reconnection after ", d)
+
+		time.Sleep(d)
+	}
+}
+
+
 func (p *partitionProducer) run() {
 	for {
 		select {
@@ -110,6 +136,8 @@ func (p *partitionProducer) run() {
 			switch v := i.(type) {
 			case *sendRequest:
 				p.internalSend(v)
+			case *connectionClosed:
+				p.reconnectToBroker()
 			}
 
 		case _ = <-p.batchFlushTicker.C:


[pulsar-client-go] 22/38: Added hash functions and tests

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit 0ac78681c6337874e250154a6d9ad4431edf0f24
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Fri May 3 17:25:02 2019 -0700

    Added hash functions and tests
---
 pulsar/impl/hash.go      | 19 +++++++++++++++++++
 pulsar/impl/hash_test.go | 40 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 59 insertions(+)

diff --git a/pulsar/impl/hash.go b/pulsar/impl/hash.go
new file mode 100644
index 0000000..bd65da1
--- /dev/null
+++ b/pulsar/impl/hash.go
@@ -0,0 +1,19 @@
+package impl
+
+import "github.com/spaolacci/murmur3"
+
+func JavaStringHash(s string) uint32 {
+	var h uint32
+	for i, size := 0, len(s); i < size; i++ {
+		h = 31*h + uint32(s[i])
+	}
+
+	return h
+}
+
+func Murmur3_32Hash(s string) uint32 {
+	h := murmur3.New32()
+	h.Write([]byte(s))
+	// Maintain compatibility with values used in Java client
+	return h.Sum32() & 0x7fffffff
+}
diff --git a/pulsar/impl/hash_test.go b/pulsar/impl/hash_test.go
new file mode 100644
index 0000000..76b418a
--- /dev/null
+++ b/pulsar/impl/hash_test.go
@@ -0,0 +1,40 @@
+package impl
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+type testProvider struct {
+	str string
+
+	hash uint32
+}
+
+var javaHashValues = []testProvider{
+	{"", 0x0,},
+	{"hello", 0x5e918d2},
+	{"test", 0x364492},
+}
+
+var murmurHashValues = []testProvider{
+	{"", 0x0},
+	{"hello", 0x248bfa47},
+	{"test", 0x3a6bd213},
+}
+
+func TestJavaHash(t *testing.T) {
+	for _, p := range javaHashValues {
+		t.Run(p.str, func(t *testing.T) {
+			assert.Equal(t, p.hash, JavaStringHash(p.str))
+		})
+	}
+}
+
+func TestMurmurHash(t *testing.T) {
+	for _, p := range murmurHashValues {
+		t.Run(p.str, func(t *testing.T) {
+			assert.Equal(t, p.hash, Murmur3_32Hash(p.str))
+		})
+	}
+}


[pulsar-client-go] 14/38: Resend pending messages after reconnection

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit 427fe05cdec379e4a773a6a911a90fb6b3820506
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Wed Apr 10 15:20:27 2019 -0700

    Resend pending messages after reconnection
---
 pulsar/impl/connection.go         | 2 +-
 pulsar/impl_partition_producer.go | 9 +++++++++
 2 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/pulsar/impl/connection.go b/pulsar/impl/connection.go
index fa32889..0cba51d 100644
--- a/pulsar/impl/connection.go
+++ b/pulsar/impl/connection.go
@@ -369,7 +369,7 @@ func (c *connection) Close() {
 	c.pingTicker.Stop()
 	close(c.incomingRequests)
 	close(c.writeRequests)
-	//c.cnx = nil
+
 	for _, listener := range c.listeners {
 		listener.ConnectionClosed()
 	}
diff --git a/pulsar/impl_partition_producer.go b/pulsar/impl_partition_producer.go
index cdd34d5..5f50296 100644
--- a/pulsar/impl_partition_producer.go
+++ b/pulsar/impl_partition_producer.go
@@ -114,6 +114,13 @@ func (p *partitionProducer) grabCnx() error {
 	p.cnx = res.Cnx
 	p.cnx.RegisterListener(p.producerId, p)
 	p.log.WithField("cnx", res.Cnx).Debug("Connected producer")
+
+	if p.pendingQueue.Size() > 0 {
+		p.log.Infof("Resending %v pending batches", p.pendingQueue.Size())
+		for it := p.pendingQueue.Iterator(); it.HasNext(); {
+			p.cnx.WriteData(it.Next().(*pendingItem).batchData)
+		}
+	}
 	return nil
 }
 
@@ -126,6 +133,7 @@ func (p *partitionProducer) ConnectionClosed() {
 }
 
 func (p *partitionProducer) reconnectToBroker() {
+	p.log.Info("Reconnecting to broker")
 	backoff := impl.Backoff{}
 	for {
 		err := p.grabCnx()
@@ -255,6 +263,7 @@ func (p *partitionProducer) ReceivedSendReceipt(response *pb.CommandSendReceipt)
 
 	// The ack was indeed for the expected item in the queue, we can remove it and trigger the callback
 	p.pendingQueue.Poll()
+	p.publishSemaphore.Release()
 	for _ ,i := range pi.sendRequest {
 		sr := i.(*sendRequest)
 		sr.callback(nil, sr.msg, nil)


[pulsar-client-go] 31/38: Added README

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit b212407d84ca781cea30a584ba05aa34c1f4ca3d
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Mon May 6 14:58:39 2019 -0700

    Added README
---
 README.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 60 insertions(+)

diff --git a/README.md b/README.md
index e69de29..3e6a279 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1,60 @@
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+
+# Apache Pulsar Go Client Library
+
+> Note: this library is still a work in progress. For production usage, please
+refer to the CGo based client library, documented at
+http://pulsar.apache.org/docs/en/client-libraries-go/
+
+## Goal
+
+This projects is developing a pure-Go client library for Pulsar that does not
+depend on the C++ Pulsar library.
+
+Once feature parity and stability are reached, this will supersede the current
+CGo based library.
+
+## Status
+
+Check the Projects page at https://github.com/apache/pulsar-client-go/projects for
+tracking the status and the progress.
+
+## Contact
+
+##### Mailing lists
+
+| Name                                                                          | Scope                           |                                                                 |                                                                     |                                                                              |
+|:------------------------------------------------------------------------------|:--------------------------------|:----------------------------------------------------------------|:--------------------------------------------------------------------|:-----------------------------------------------------------------------------|
+| [users@pulsar.apache.org](mailto:users@pulsar.apache.org) | User-related discussions        | [Subscribe](mailto:users-subscribe@pulsar.apache.org) | [Unsubscribe](mailto:users-unsubscribe@pulsar.apache.org) | [Archives](http://mail-archives.apache.org/mod_mbox/pulsar-users/) |
+| [dev@pulsar.apache.org](mailto:dev@pulsar.apache.org)     | Development-related discussions | [Subscribe](mailto:dev-subscribe@pulsar.apache.org)   | [Unsubscribe](mailto:dev-unsubscribe@pulsar.apache.org)   | [Archives](http://mail-archives.apache.org/mod_mbox/pulsar-dev/)   |
+
+##### Slack
+
+Pulsar slack channel `#dev-go` at https://apache-pulsar.slack.com/
+
+You can self-register at https://apache-pulsar.herokuapp.com/
+
+## License
+
+Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+
+


[pulsar-client-go] 25/38: Added auto-resize when writing to buffer

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit 9c6d52f2ed619999d72dea3a40e8022babd799f2
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Sat May 4 12:43:19 2019 -0700

    Added auto-resize when writing to buffer
---
 pulsar/impl/buffer.go     | 18 +++++++++++++++++-
 pulsar/impl/connection.go |  5 -----
 2 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/pulsar/impl/buffer.go b/pulsar/impl/buffer.go
index 3d03002..3f8dcc0 100644
--- a/pulsar/impl/buffer.go
+++ b/pulsar/impl/buffer.go
@@ -1,6 +1,8 @@
 package impl
 
-import "encoding/binary"
+import (
+	"encoding/binary"
+)
 
 type Buffer interface {
 	ReadableBytes() uint32
@@ -121,6 +123,17 @@ func (b *buffer) Resize(newSize uint32) {
 	b.writerIdx = size
 }
 
+func (b *buffer) resizeIfNeeded(spaceNeeded int) {
+	if b.WritableBytes() < uint32(spaceNeeded) {
+		capacityNeeded := uint32(cap(b.data) + spaceNeeded)
+		minCapacityIncrease := uint32(cap(b.data) * 3 / 2)
+		if capacityNeeded < minCapacityIncrease {
+			capacityNeeded = minCapacityIncrease
+		}
+		b.Resize(capacityNeeded)
+	}
+}
+
 func (b *buffer) ReadUint32() uint32 {
 	return binary.BigEndian.Uint32(b.Read(4))
 }
@@ -130,6 +143,7 @@ func (b *buffer) ReadUint16() uint16 {
 }
 
 func (b *buffer) WriteUint32(n uint32) {
+	b.resizeIfNeeded(4)
 	binary.BigEndian.PutUint32(b.WritableSlice(), n)
 	b.writerIdx += 4
 }
@@ -139,11 +153,13 @@ func (b *buffer) PutUint32(n uint32, idx uint32) {
 }
 
 func (b *buffer) WriteUint16(n uint16) {
+	b.resizeIfNeeded(2)
 	binary.BigEndian.PutUint16(b.WritableSlice(), n)
 	b.writerIdx += 2
 }
 
 func (b *buffer) Write(s []byte) {
+	b.resizeIfNeeded(len(s))
 	copy(b.WritableSlice(), s)
 	b.writerIdx += uint32(len(s))
 }
diff --git a/pulsar/impl/connection.go b/pulsar/impl/connection.go
index 8cdcf8b..580a821 100644
--- a/pulsar/impl/connection.go
+++ b/pulsar/impl/connection.go
@@ -216,13 +216,8 @@ func (c *connection) writeCommand(cmd proto.Message) {
 	// [FRAME_SIZE] [CMD_SIZE][CMD]
 	cmdSize := uint32(proto.Size(cmd))
 	frameSize := cmdSize + 4
-	bufferSize := frameSize + 4
 
 	c.writeBuffer.Clear()
-	if c.writeBuffer.WritableBytes() < bufferSize {
-		c.writeBuffer.Resize(c.writeBuffer.Capacity() * 2)
-	}
-
 	c.writeBuffer.WriteUint32(frameSize)
 	c.writeBuffer.WriteUint32(cmdSize)
 	serialized, err := proto.Marshal(cmd)


[pulsar-client-go] 35/38: Added TLS connection support

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit ddc789e1e2ea8706556bd7ace71fd2c3f662b217
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Wed May 8 12:14:44 2019 -0700

    Added TLS connection support
---
 pulsar/client.go                   |  4 +-
 pulsar/impl_client.go              | 46 +++++++++++++++++--
 pulsar/impl_client_test.go         | 94 ++++++++++++++++++++++++++++++++++++++
 pulsar/internal/connection.go      | 70 +++++++++++++++++++++++++---
 pulsar/internal/connection_pool.go | 11 +++--
 pulsar/test_helper.go              |  7 ++-
 6 files changed, 215 insertions(+), 17 deletions(-)

diff --git a/pulsar/client.go b/pulsar/client.go
index e68f09c..d870dcd 100644
--- a/pulsar/client.go
+++ b/pulsar/client.go
@@ -20,6 +20,7 @@
 package pulsar
 
 import (
+	"pulsar-client-go/pulsar/internal/auth"
 	"time"
 )
 
@@ -44,8 +45,7 @@ func NewAuthenticationTokenSupplier(tokenSupplier func() string) Authentication
 
 // Create new Authentication provider with specified TLS certificate and private key
 func NewAuthenticationTLS(certificatePath string, privateKeyPath string) Authentication {
-	// TODO: return newAuthenticationTLS(certificatePath, privateKeyPath)
-	return nil
+	return auth.NewAuthenticationTLS(certificatePath, privateKeyPath)
 }
 
 // Create new Athenz Authentication provider with configuration in JSON form
diff --git a/pulsar/impl_client.go b/pulsar/impl_client.go
index 0db2ee8..9a3ee7d 100644
--- a/pulsar/impl_client.go
+++ b/pulsar/impl_client.go
@@ -20,8 +20,12 @@
 package pulsar
 
 import (
+	"crypto/tls"
+	"crypto/x509"
 	"fmt"
+	"github.com/pkg/errors"
 	log "github.com/sirupsen/logrus"
+	"io/ioutil"
 	"net/url"
 	"pulsar-client-go/pulsar/internal"
 	pb "pulsar-client-go/pulsar/internal/pulsar_proto"
@@ -50,12 +54,24 @@ func newClient(options ClientOptions) (Client, error) {
 		return nil, newError(ResultInvalidConfiguration, "Invalid service URL")
 	}
 
-	if url.Scheme != "pulsar" {
+	var tlsConfig *internal.TLSOptions
+	if url.Scheme == "pulsar" {
+		tlsConfig = nil
+	} else if url.Scheme == "pulsar+ssl" {
+		tlsConfig = &internal.TLSOptions{
+			AllowInsecureConnection: options.TLSAllowInsecureConnection,
+			TrustCertsFilePath:      options.TLSTrustCertsFilePath,
+			ValidateHostname:        options.TLSValidateHostname,
+		}
+		if err != nil {
+			return nil, err
+		}
+	} else {
 		return nil, newError(ResultInvalidConfiguration, fmt.Sprintf("Invalid URL scheme '%s'", url.Scheme))
 	}
 
 	c := &client{
-		cnxPool: internal.NewConnectionPool(),
+		cnxPool: internal.NewConnectionPool(tlsConfig),
 	}
 	c.rpcClient = internal.NewRpcClient(url, c.cnxPool)
 	c.lookupService = internal.NewLookupService(c.rpcClient, url)
@@ -114,7 +130,6 @@ func (client *client) TopicPartitions(topic string) ([]string, error) {
 	}
 }
 
-
 func (client *client) Close() error {
 	for handler := range client.handlers {
 		if err := handler.Close(); err != nil {
@@ -124,3 +139,28 @@ func (client *client) Close() error {
 
 	return nil
 }
+
+func getTlsConfig(options ClientOptions) (*tls.Config, error) {
+	tlsConfig := &tls.Config{
+		InsecureSkipVerify: options.TLSAllowInsecureConnection,
+	}
+
+	if options.TLSTrustCertsFilePath != "" {
+		caCerts, err := ioutil.ReadFile(options.TLSTrustCertsFilePath)
+		if err != nil {
+			return nil, err
+		}
+
+		tlsConfig.RootCAs = x509.NewCertPool()
+		ok := tlsConfig.RootCAs.AppendCertsFromPEM([]byte(caCerts))
+		if !ok {
+			return nil, errors.New("failed to parse root CAs certificates")
+		}
+	}
+
+	if options.TLSValidateHostname {
+		tlsConfig.ServerName = options.URL
+	}
+
+	return tlsConfig, nil
+}
diff --git a/pulsar/impl_client_test.go b/pulsar/impl_client_test.go
index 4542740..9fd4231 100644
--- a/pulsar/impl_client_test.go
+++ b/pulsar/impl_client_test.go
@@ -31,3 +31,97 @@ func TestClient(t *testing.T) {
 	assert.NotNil(t, err)
 	assert.Equal(t, Result(ResultInvalidConfiguration), err.(*Error).Result())
 }
+
+func TestTLSConnectionCAError(t *testing.T) {
+	client, err := NewClient(ClientOptions{
+		URL: serviceUrlTls,
+	})
+	assert.NoError(t, err)
+
+	producer, err := client.CreateProducer(ProducerOptions{
+		Topic: newTopicName(),
+	})
+
+	// The client should fail because it wouldn't trust the
+	// broker certificate
+	assert.Error(t, err)
+	assert.Nil(t, producer)
+
+	err = client.Close()
+	assert.NoError(t, err)
+}
+
+func TestTLSInsecureConnection(t *testing.T) {
+	client, err := NewClient(ClientOptions{
+		URL:                        serviceUrlTls,
+		TLSAllowInsecureConnection: true,
+	})
+	assert.NoError(t, err)
+
+	producer, err := client.CreateProducer(ProducerOptions{
+		Topic: newTopicName(),
+	})
+
+	assert.NoError(t, err)
+	assert.NotNil(t, producer)
+
+	err = client.Close()
+	assert.NoError(t, err)
+}
+
+func TestTLSConnection(t *testing.T) {
+	client, err := NewClient(ClientOptions{
+		URL:                   serviceUrlTls,
+		TLSTrustCertsFilePath: caCertsPath,
+	})
+	assert.NoError(t, err)
+
+	producer, err := client.CreateProducer(ProducerOptions{
+		Topic: newTopicName(),
+	})
+
+	assert.NoError(t, err)
+	assert.NotNil(t, producer)
+
+	err = client.Close()
+	assert.NoError(t, err)
+}
+
+func TestTLSConnectionHostNameVerification(t *testing.T) {
+	client, err := NewClient(ClientOptions{
+		URL:                   serviceUrlTls,
+		TLSTrustCertsFilePath: caCertsPath,
+		TLSValidateHostname:   true,
+	})
+	assert.NoError(t, err)
+
+	producer, err := client.CreateProducer(ProducerOptions{
+		Topic: newTopicName(),
+	})
+
+	assert.NoError(t, err)
+	assert.NotNil(t, producer)
+
+	err = client.Close()
+	assert.NoError(t, err)
+}
+
+func TestTLSConnectionHostNameVerificationError(t *testing.T) {
+	client, err := NewClient(ClientOptions{
+		URL:                   "pulsar+ssl://127.0.0.1:6651",
+		TLSTrustCertsFilePath: caCertsPath,
+		TLSValidateHostname:   true,
+	})
+	assert.NoError(t, err)
+
+	producer, err := client.CreateProducer(ProducerOptions{
+		Topic: newTopicName(),
+	})
+
+	assert.Error(t, err)
+	assert.Nil(t, producer)
+
+	err = client.Close()
+	assert.NoError(t, err)
+}
+
diff --git a/pulsar/internal/connection.go b/pulsar/internal/connection.go
index affa8cc..fdcfd60 100644
--- a/pulsar/internal/connection.go
+++ b/pulsar/internal/connection.go
@@ -20,9 +20,12 @@
 package internal
 
 import (
+	"crypto/tls"
+	"crypto/x509"
 	"errors"
 	"github.com/golang/protobuf/proto"
 	log "github.com/sirupsen/logrus"
+	"io/ioutil"
 	"net"
 	"net/url"
 	pb "pulsar-client-go/pulsar/internal/pulsar_proto"
@@ -31,6 +34,12 @@ import (
 	"time"
 )
 
+type TLSOptions struct {
+	TrustCertsFilePath      string
+	AllowInsecureConnection bool
+	ValidateHostname        bool
+}
+
 // ConnectionListener is a user of a connection (eg. a producer or
 // a consumer) that can register itself to get notified
 // when the connection is closed.
@@ -71,8 +80,8 @@ type connection struct {
 	cond  *sync.Cond
 	state connectionState
 
-	logicalAddr  string
-	physicalAddr string
+	logicalAddr  *url.URL
+	physicalAddr *url.URL
 	cnx          net.Conn
 
 	writeBuffer          Buffer
@@ -88,18 +97,21 @@ type connection struct {
 	writeRequests    chan []byte
 	pendingReqs      map[uint64]*request
 	listeners        map[uint64]ConnectionListener
+
+	tlsOptions *TLSOptions
 }
 
-func newConnection(logicalAddr *url.URL, physicalAddr *url.URL) *connection {
+func newConnection(logicalAddr *url.URL, physicalAddr *url.URL, tlsOptions *TLSOptions) *connection {
 	cnx := &connection{
 		state:                connectionInit,
-		logicalAddr:          logicalAddr.Host,
-		physicalAddr:         physicalAddr.Host,
+		logicalAddr:          logicalAddr,
+		physicalAddr:         physicalAddr,
 		writeBuffer:          NewBuffer(4096),
 		log:                  log.WithField("raddr", physicalAddr),
 		pendingReqs:          make(map[uint64]*request),
 		lastDataReceivedTime: time.Now(),
 		pingTicker:           time.NewTicker(keepAliveInterval),
+		tlsOptions:           tlsOptions,
 
 		incomingRequests: make(chan *request),
 		writeRequests:    make(chan []byte),
@@ -128,13 +140,32 @@ func (c *connection) start() {
 func (c *connection) connect() (ok bool) {
 	c.log.Info("Connecting to broker")
 
-	var err error
-	c.cnx, err = net.Dial("tcp", c.physicalAddr)
+	var (
+		err       error
+		cnx       net.Conn
+		tlsConfig *tls.Config
+	)
+
+	if c.tlsOptions == nil {
+		// Clear text connection
+		cnx, err = net.Dial("tcp", c.physicalAddr.Host)
+	} else {
+		// TLS connection
+		tlsConfig, err  = c.getTlsConfig()
+		if err != nil {
+			c.log.WithError(err).Warn("Failed to configure TLS ")
+			return false
+		}
+
+		cnx, err = tls.Dial("tcp", c.physicalAddr.Host, tlsConfig)
+	}
+
 	if err != nil {
 		c.log.WithError(err).Warn("Failed to connect to broker.")
 		c.Close()
 		return false
 	} else {
+		c.cnx = cnx
 		c.log = c.log.WithField("laddr", c.cnx.LocalAddr())
 		c.log.Debug("TCP connection established")
 		c.state = connectionTcpConnected
@@ -407,3 +438,28 @@ func (c *connection) changeState(state connectionState) {
 func (c *connection) newRequestId() uint64 {
 	return atomic.AddUint64(&c.requestIdGenerator, 1)
 }
+
+func (c *connection) getTlsConfig() (*tls.Config, error) {
+	tlsConfig := &tls.Config{
+		InsecureSkipVerify: c.tlsOptions.AllowInsecureConnection,
+	}
+
+	if c.tlsOptions.TrustCertsFilePath != "" {
+		caCerts, err := ioutil.ReadFile(c.tlsOptions.TrustCertsFilePath)
+		if err != nil {
+			return nil, err
+		}
+
+		tlsConfig.RootCAs = x509.NewCertPool()
+		ok := tlsConfig.RootCAs.AppendCertsFromPEM([]byte(caCerts))
+		if !ok {
+			return nil, errors.New("failed to parse root CAs certificates")
+		}
+	}
+
+	if c.tlsOptions.ValidateHostname {
+		tlsConfig.ServerName = c.physicalAddr.Hostname()
+	}
+
+	return tlsConfig, nil
+}
diff --git a/pulsar/internal/connection_pool.go b/pulsar/internal/connection_pool.go
index 4b56484..6540137 100644
--- a/pulsar/internal/connection_pool.go
+++ b/pulsar/internal/connection_pool.go
@@ -33,11 +33,14 @@ type ConnectionPool interface {
 }
 
 type connectionPool struct {
-	pool sync.Map
+	pool       sync.Map
+	tlsOptions *TLSOptions
 }
 
-func NewConnectionPool() ConnectionPool {
-	return &connectionPool{}
+func NewConnectionPool(tlsOptions *TLSOptions) ConnectionPool {
+	return &connectionPool{
+		tlsOptions: tlsOptions,
+	}
 }
 
 func (p *connectionPool) GetConnection(logicalAddr *url.URL, physicalAddr *url.URL) (Connection, error) {
@@ -57,7 +60,7 @@ func (p *connectionPool) GetConnection(logicalAddr *url.URL, physicalAddr *url.U
 	}
 
 	// Try to create a new connection
-	newCnx, wasCached := p.pool.LoadOrStore(logicalAddr.Host, newConnection(logicalAddr, physicalAddr))
+	newCnx, wasCached := p.pool.LoadOrStore(logicalAddr.Host, newConnection(logicalAddr, physicalAddr, p.tlsOptions))
 	cnx := newCnx.(*connection)
 	if !wasCached {
 		cnx.start()
diff --git a/pulsar/test_helper.go b/pulsar/test_helper.go
index 1795e41..c6c78e1 100644
--- a/pulsar/test_helper.go
+++ b/pulsar/test_helper.go
@@ -24,7 +24,12 @@ import (
 	"time"
 )
 
-const serviceUrl = "pulsar://localhost:6650"
+const (
+	serviceUrl    = "pulsar://localhost:6650"
+	serviceUrlTls = "pulsar+ssl://localhost:6651"
+
+	caCertsPath   = "../integration-tests/certs/cacert.pem"
+)
 
 func newTopicName() string {
 	return fmt.Sprintf("my-topic-%v", time.Now().Nanosecond())


[pulsar-client-go] 06/38: Set right level for logs

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit d31030ecc8373ba80ff33f03a13d8a54fdf7f880
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Sat Mar 30 11:37:23 2019 -0700

    Set right level for logs
---
 pulsar/impl/connection.go         | 4 ++--
 pulsar/impl/lookup_service.go     | 6 +++---
 pulsar/impl_partition_producer.go | 9 +++++----
 3 files changed, 10 insertions(+), 9 deletions(-)

diff --git a/pulsar/impl/connection.go b/pulsar/impl/connection.go
index 2bd2984..2f09690 100644
--- a/pulsar/impl/connection.go
+++ b/pulsar/impl/connection.go
@@ -99,7 +99,7 @@ func (c *connection) connect() (ok bool) {
 		return false
 	} else {
 		c.log = c.log.WithField("laddr", c.cnx.LocalAddr())
-		c.log.Info("TCP connection established")
+		c.log.Debug("TCP connection established")
 		c.state = connectionTcpConnected
 		return true
 	}
@@ -198,7 +198,7 @@ func (c *connection) writeCommand(cmd proto.Message) {
 }
 
 func (c *connection) receivedCommand(cmd *pb.BaseCommand, headersAndPayload []byte) {
-	c.log.Infof("Received command: %s -- payload: %v", cmd, headersAndPayload)
+	c.log.Debugf("Received command: %s -- payload: %v", cmd, headersAndPayload)
 	c.lastDataReceivedTime = time.Now()
 
 	switch *cmd.Type {
diff --git a/pulsar/impl/lookup_service.go b/pulsar/impl/lookup_service.go
index afd0dda..e3753d9 100644
--- a/pulsar/impl/lookup_service.go
+++ b/pulsar/impl/lookup_service.go
@@ -44,17 +44,17 @@ func (ls *lookupService) Lookup(topic string) (*LookupResult, error) {
 			return nil, err
 		}
 
-		log.Infof("Got lookup response: %s", res)
+		log.WithField("topic", topic).Debugf("Got topic lookup response: %s", res)
 		lr := res.Response.LookupTopicResponse
 		switch *lr.Response {
 		case pb.CommandLookupTopicResponse_Redirect:
 			// TODO: Handle redirects
-			log.WithField("topic", topic).Infof("Follow redirect to broker. %v / %v - Use proxy: %v",
+			log.WithField("topic", topic).Debugf("Follow redirect to broker. %v / %v - Use proxy: %v",
 				lr.BrokerServiceUrl, lr.BrokerServiceUrlTls, lr.ProxyThroughServiceUrl)
 			break
 
 		case pb.CommandLookupTopicResponse_Connect:
-			log.WithField("topic", topic).Infof("Successfully looked up topic on broker. %s / %s - Use proxy: %t",
+			log.WithField("topic", topic).Debugf("Successfully looked up topic on broker. %s / %s - Use proxy: %t",
 				lr.GetBrokerServiceUrl(), lr.GetBrokerServiceUrlTls(), lr.GetProxyThroughServiceUrl())
 
 			logicalAddress, err := url.Parse(lr.GetBrokerServiceUrl())
diff --git a/pulsar/impl_partition_producer.go b/pulsar/impl_partition_producer.go
index 4ba5506..7bb2165 100644
--- a/pulsar/impl_partition_producer.go
+++ b/pulsar/impl_partition_producer.go
@@ -42,7 +42,8 @@ func newPartitionProducer(client *client, topic string, options *ProducerOptions
 		log.WithError(err).Errorf("Failed to create producer")
 		return nil, err
 	} else {
-		log.Info("Created producer on cnx: ")
+		p.log = p.log.WithField("name", *p.producerName)
+		p.log.Info("Created producer")
 		go p.run()
 		return p, nil
 	}
@@ -55,7 +56,7 @@ func (p *partitionProducer) grabCnx() error {
 		return err
 	}
 
-	p.log.Info("Lookup result: ", lr)
+	p.log.Debug("Lookup result: ", lr)
 	id := p.client.rpcClient.NewRequestId()
 	res, err := p.client.rpcClient.Request(lr.LogicalAddr, lr.PhysicalAddr, id, pb.BaseCommand_PRODUCER, &pb.CommandProducer{
 		RequestId:    &id,
@@ -74,7 +75,7 @@ func (p *partitionProducer) grabCnx() error {
 
 	p.producerName = res.Response.ProducerSuccess.ProducerName
 	p.cnx = res.Cnx
-	p.log.WithField("cnx", res.Cnx).Info("Created producer")
+	p.log.WithField("cnx", res.Cnx).Debug("Connected producer")
 	return nil
 }
 
@@ -83,7 +84,7 @@ func (p *partitionProducer) run() {
 		i := <-p.eventsChan
 		switch v := i.(type) {
 		case *sendRequest:
-			p.log.Info("Received send request: ", v)
+			p.log.Debug("Received send request: ", v)
 			v.callback(nil, v.msg, nil)
 		}
 	}


[pulsar-client-go] 20/38: Completed lookup service with tests

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit b924b7823c4b971222ddeee9a6e31a236c3bba2f
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Fri May 3 15:52:50 2019 -0700

    Completed lookup service with tests
---
 pulsar/impl/lookup_service.go      |  73 +++++++----
 pulsar/impl/lookup_service_test.go | 249 +++++++++++++++++++++++++++++++++++++
 2 files changed, 300 insertions(+), 22 deletions(-)

diff --git a/pulsar/impl/lookup_service.go b/pulsar/impl/lookup_service.go
index e3753d9..3dfe758 100644
--- a/pulsar/impl/lookup_service.go
+++ b/pulsar/impl/lookup_service.go
@@ -3,6 +3,7 @@ package impl
 import (
 	"errors"
 	"fmt"
+	"github.com/golang/protobuf/proto"
 	log "github.com/sirupsen/logrus"
 	"net/url"
 	pb "pulsar-client-go-native/pulsar/pulsar_proto"
@@ -29,17 +30,34 @@ func NewLookupService(rpcClient RpcClient, serviceUrl *url.URL) LookupService {
 	}
 }
 
+func (ls *lookupService) getBrokerAddress(lr *pb.CommandLookupTopicResponse) (logicalAddress *url.URL, physicalAddress *url.URL, err error) {
+	logicalAddress, err = url.ParseRequestURI(lr.GetBrokerServiceUrl())
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var physicalAddr *url.URL
+	if lr.GetProxyThroughServiceUrl() {
+		physicalAddr = ls.serviceUrl
+	} else {
+		physicalAddr = logicalAddress
+	}
+
+	return logicalAddress, physicalAddr, nil
+}
+
+// Follow brokers redirect up to certain number of times
+const lookupResultMaxRedirect = 20
+
 func (ls *lookupService) Lookup(topic string) (*LookupResult, error) {
-	// Follow brokers redirect up to certain number of times
-	const lookupResultMaxRedirect = 20
+	id := ls.rpcClient.NewRequestId()
+	res, err := ls.rpcClient.RequestToAnyBroker(id, pb.BaseCommand_LOOKUP, &pb.CommandLookupTopic{
+		RequestId:     &id,
+		Topic:         &topic,
+		Authoritative: proto.Bool(false),
+	})
 
 	for i := 0; i < lookupResultMaxRedirect; i++ {
-		id := ls.rpcClient.NewRequestId()
-		res, err := ls.rpcClient.RequestToAnyBroker(id, pb.BaseCommand_LOOKUP, &pb.CommandLookupTopic{
-			RequestId: &id,
-			Topic:     &topic,
-		})
-
 		if err != nil {
 			return nil, err
 		}
@@ -47,38 +65,49 @@ func (ls *lookupService) Lookup(topic string) (*LookupResult, error) {
 		log.WithField("topic", topic).Debugf("Got topic lookup response: %s", res)
 		lr := res.Response.LookupTopicResponse
 		switch *lr.Response {
+
 		case pb.CommandLookupTopicResponse_Redirect:
-			// TODO: Handle redirects
+			logicalAddress, physicalAddr, err := ls.getBrokerAddress(lr)
+			if err != nil {
+				return nil, err
+			}
+
 			log.WithField("topic", topic).Debugf("Follow redirect to broker. %v / %v - Use proxy: %v",
 				lr.BrokerServiceUrl, lr.BrokerServiceUrlTls, lr.ProxyThroughServiceUrl)
-			break
+
+			id := ls.rpcClient.NewRequestId()
+			res, err = ls.rpcClient.Request(logicalAddress, physicalAddr, id, pb.BaseCommand_LOOKUP, &pb.CommandLookupTopic{
+				RequestId:     &id,
+				Topic:         &topic,
+				Authoritative: lr.Authoritative,
+			})
+
+			// Process the response at the top of the loop
+			continue
 
 		case pb.CommandLookupTopicResponse_Connect:
 			log.WithField("topic", topic).Debugf("Successfully looked up topic on broker. %s / %s - Use proxy: %t",
 				lr.GetBrokerServiceUrl(), lr.GetBrokerServiceUrlTls(), lr.GetProxyThroughServiceUrl())
 
-			logicalAddress, err := url.Parse(lr.GetBrokerServiceUrl())
+			logicalAddress, physicalAddress, err := ls.getBrokerAddress(lr)
 			if err != nil {
 				return nil, err
 			}
 
-			var physicalAddr *url.URL
-			if lr.GetProxyThroughServiceUrl() {
-				physicalAddr = ls.serviceUrl
-			} else {
-				physicalAddr = logicalAddress
-			}
 			return &LookupResult{
 				LogicalAddr:  logicalAddress,
-				PhysicalAddr: physicalAddr,
+				PhysicalAddr: physicalAddress,
 			}, nil
 
 		case pb.CommandLookupTopicResponse_Failed:
-			log.WithField("topic", topic).Warn("Failed to lookup topic",
-				lr.Error.String())
-			return nil, errors.New(fmt.Sprintf("failed to lookup topic: %s", lr.Error.String()))
+			errorMsg := ""
+			if lr.Error != nil {
+				errorMsg = lr.Error.String()
+			}
+			log.WithField("topic", topic).Warn("Failed to lookup topic", errorMsg)
+			return nil, errors.New(fmt.Sprintf("failed to lookup topic: %s", errorMsg))
 		}
 	}
 
-	return nil, nil
+	return nil, errors.New("exceeded max number of redirection during topic lookup")
 }
diff --git a/pulsar/impl/lookup_service_test.go b/pulsar/impl/lookup_service_test.go
new file mode 100644
index 0000000..7e18198
--- /dev/null
+++ b/pulsar/impl/lookup_service_test.go
@@ -0,0 +1,249 @@
+package impl
+
+import (
+	"github.com/golang/protobuf/proto"
+	"github.com/stretchr/testify/assert"
+	"net/url"
+	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+	"testing"
+)
+
+type mockedRpcClient struct {
+	requestIdGenerator uint64
+	t                  *testing.T
+
+	expectedUrl      string
+	expectedRequests []pb.CommandLookupTopic
+	mockedResponses  []pb.CommandLookupTopicResponse
+}
+
+// Create a new unique request id
+func (c *mockedRpcClient) NewRequestId() uint64 {
+	c.requestIdGenerator += 1
+	return c.requestIdGenerator
+}
+
+func (c *mockedRpcClient) NewProducerId() uint64 {
+	return 1
+}
+
+func (c *mockedRpcClient) NewConsumerId() uint64 {
+	return 1
+}
+
+func (c *mockedRpcClient) RequestToAnyBroker(requestId uint64, cmdType pb.BaseCommand_Type, message proto.Message) (*RpcResult, error) {
+	assert.Equal(c.t, cmdType, pb.BaseCommand_LOOKUP)
+
+	expectedRequest := &c.expectedRequests[0]
+	c.expectedRequests = c.expectedRequests[1:]
+
+	assert.Equal(c.t, expectedRequest, message)
+
+	mockedResponse := &c.mockedResponses[0]
+	c.mockedResponses = c.mockedResponses[1:]
+
+	return &RpcResult{
+		&pb.BaseCommand{
+			LookupTopicResponse: mockedResponse,
+		},
+		nil,
+	}, nil
+}
+
+func (c *mockedRpcClient) Request(logicalAddr *url.URL, physicalAddr *url.URL, requestId uint64,
+	cmdType pb.BaseCommand_Type, message proto.Message) (*RpcResult, error) {
+	assert.Equal(c.t, cmdType, pb.BaseCommand_LOOKUP)
+	expectedRequest := &c.expectedRequests[0]
+	c.expectedRequests = c.expectedRequests[1:]
+
+	assert.Equal(c.t, expectedRequest, message)
+
+	mockedResponse := &c.mockedResponses[0]
+	c.mockedResponses = c.mockedResponses[1:]
+
+	assert.Equal(c.t, c.expectedUrl, logicalAddr.String())
+	assert.Equal(c.t, c.expectedUrl, physicalAddr.String())
+
+	return &RpcResult{
+		&pb.BaseCommand{
+			LookupTopicResponse: mockedResponse,
+		},
+		nil,
+	}, nil
+}
+
+func (c *mockedRpcClient) RequestOnCnx(cnx Connection, requestId uint64, cmdType pb.BaseCommand_Type, message proto.Message) (*RpcResult, error) {
+	assert.Fail(c.t, "Shouldn't be called")
+	return nil, nil
+}
+
+func responseType(r pb.CommandLookupTopicResponse_LookupType) *pb.CommandLookupTopicResponse_LookupType {
+	return &r
+}
+
+func TestLookupSuccess(t *testing.T) {
+	url, err := url.Parse("pulsar://example:6650")
+	assert.NoError(t, err)
+
+	ls := NewLookupService(&mockedRpcClient{
+		t: t,
+
+		expectedRequests: []pb.CommandLookupTopic{
+			{
+				RequestId:     proto.Uint64(1),
+				Topic:         proto.String("my-topic"),
+				Authoritative: proto.Bool(false),
+			},
+		},
+		mockedResponses: []pb.CommandLookupTopicResponse{
+			{
+				RequestId:        proto.Uint64(1),
+				Response:         responseType(pb.CommandLookupTopicResponse_Connect),
+				Authoritative:    proto.Bool(true),
+				BrokerServiceUrl: proto.String("pulsar://broker-1:6650"),
+			},
+		},
+	}, url)
+
+	lr, err := ls.Lookup("my-topic")
+	assert.NoError(t, err)
+	assert.NotNil(t, lr)
+
+	assert.Equal(t, "pulsar://broker-1:6650", lr.LogicalAddr.String())
+	assert.Equal(t, "pulsar://broker-1:6650", lr.PhysicalAddr.String())
+}
+
+func TestLookupWithProxy(t *testing.T) {
+	url, err := url.Parse("pulsar://example:6650")
+	assert.NoError(t, err)
+
+	ls := NewLookupService(&mockedRpcClient{
+		t: t,
+
+		expectedRequests: []pb.CommandLookupTopic{
+			{
+				RequestId:     proto.Uint64(1),
+				Topic:         proto.String("my-topic"),
+				Authoritative: proto.Bool(false),
+			},
+		},
+		mockedResponses: []pb.CommandLookupTopicResponse{
+			{
+				RequestId:              proto.Uint64(1),
+				Response:               responseType(pb.CommandLookupTopicResponse_Connect),
+				Authoritative:          proto.Bool(true),
+				BrokerServiceUrl:       proto.String("pulsar://broker-1:6650"),
+				ProxyThroughServiceUrl: proto.Bool(true),
+			},
+		},
+	}, url)
+
+	lr, err := ls.Lookup("my-topic")
+	assert.NoError(t, err)
+	assert.NotNil(t, lr)
+
+	assert.Equal(t, "pulsar://broker-1:6650", lr.LogicalAddr.String())
+	assert.Equal(t, "pulsar://example:6650", lr.PhysicalAddr.String())
+}
+
+func TestLookupWithRedirect(t *testing.T) {
+	url, err := url.Parse("pulsar://example:6650")
+	assert.NoError(t, err)
+
+	ls := NewLookupService(&mockedRpcClient{
+		t:           t,
+		expectedUrl: "pulsar://broker-2:6650",
+
+		expectedRequests: []pb.CommandLookupTopic{
+			{
+				RequestId:     proto.Uint64(1),
+				Topic:         proto.String("my-topic"),
+				Authoritative: proto.Bool(false),
+			},
+			{
+				RequestId:     proto.Uint64(2),
+				Topic:         proto.String("my-topic"),
+				Authoritative: proto.Bool(true),
+			},
+		},
+		mockedResponses: []pb.CommandLookupTopicResponse{
+			{
+				RequestId:        proto.Uint64(1),
+				Response:         responseType(pb.CommandLookupTopicResponse_Redirect),
+				Authoritative:    proto.Bool(true),
+				BrokerServiceUrl: proto.String("pulsar://broker-2:6650"),
+			},
+			{
+				RequestId:        proto.Uint64(2),
+				Response:         responseType(pb.CommandLookupTopicResponse_Connect),
+				Authoritative:    proto.Bool(true),
+				BrokerServiceUrl: proto.String("pulsar://broker-1:6650"),
+			},
+		},
+	}, url)
+
+	lr, err := ls.Lookup("my-topic")
+	assert.NoError(t, err)
+	assert.NotNil(t, lr)
+
+	assert.Equal(t, "pulsar://broker-1:6650", lr.LogicalAddr.String())
+	assert.Equal(t, "pulsar://broker-1:6650", lr.PhysicalAddr.String())
+}
+
+func TestLookupWithInvalidUrlResponse(t *testing.T) {
+	url, err := url.Parse("pulsar://example:6650")
+	assert.NoError(t, err)
+
+	ls := NewLookupService(&mockedRpcClient{
+		t: t,
+
+		expectedRequests: []pb.CommandLookupTopic{
+			{
+				RequestId:     proto.Uint64(1),
+				Topic:         proto.String("my-topic"),
+				Authoritative: proto.Bool(false),
+			},
+		},
+		mockedResponses: []pb.CommandLookupTopicResponse{
+			{
+				RequestId:              proto.Uint64(1),
+				Response:               responseType(pb.CommandLookupTopicResponse_Connect),
+				Authoritative:          proto.Bool(true),
+				BrokerServiceUrl:       proto.String("foo.html") /* invalid url */,
+				ProxyThroughServiceUrl: proto.Bool(false),
+			},
+		},
+	}, url)
+
+	lr, err := ls.Lookup("my-topic")
+	assert.Error(t, err)
+	assert.Nil(t, lr)
+}
+
+func TestLookupWithLookupFailure(t *testing.T) {
+	url, err := url.Parse("pulsar://example:6650")
+	assert.NoError(t, err)
+
+	ls := NewLookupService(&mockedRpcClient{
+		t: t,
+
+		expectedRequests: []pb.CommandLookupTopic{
+			{
+				RequestId:     proto.Uint64(1),
+				Topic:         proto.String("my-topic"),
+				Authoritative: proto.Bool(false),
+			},
+		},
+		mockedResponses: []pb.CommandLookupTopicResponse{
+			{
+				RequestId:              proto.Uint64(1),
+				Response:               responseType(pb.CommandLookupTopicResponse_Failed),
+				Authoritative:          proto.Bool(true),
+			},
+		},
+	}, url)
+
+	lr, err := ls.Lookup("my-topic")
+	assert.Error(t, err)
+	assert.Nil(t, lr)
+}


[pulsar-client-go] 36/38: TLS Auth provider

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit 1a6dfaae40f93e6e1f59f1bfa66610c66620f931
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Wed May 8 17:28:43 2019 -0700

    TLS Auth provider
---
 pulsar/client.go                                   |  4 ++
 pulsar/impl_client.go                              | 16 +++++-
 pulsar/impl_client_test.go                         | 36 ++++++++++++
 .../{test_helper.go => internal/auth/disabled.go}  | 37 +++++++++----
 .../{test_helper.go => internal/auth/provider.go}  | 42 +++++++++++---
 pulsar/internal/auth/tls.go                        | 64 ++++++++++++++++++++++
 pulsar/internal/connection.go                      | 27 +++++++--
 pulsar/internal/connection_pool.go                 |  8 ++-
 pulsar/test_helper.go                              |  8 ++-
 9 files changed, 213 insertions(+), 29 deletions(-)

diff --git a/pulsar/client.go b/pulsar/client.go
index d870dcd..1a838ac 100644
--- a/pulsar/client.go
+++ b/pulsar/client.go
@@ -31,6 +31,10 @@ func NewClient(options ClientOptions) (Client, error) {
 // Opaque interface that represents the authentication credentials
 type Authentication interface{}
 
+func NewAuthentication(name string, params string) (Authentication, error) {
+	return auth.NewProvider(name, params)
+}
+
 // Create new Authentication provider with specified auth token
 func NewAuthenticationToken(token string) Authentication {
 	// TODO: return newAuthenticationToken(token)
diff --git a/pulsar/impl_client.go b/pulsar/impl_client.go
index 9a3ee7d..b84ac82 100644
--- a/pulsar/impl_client.go
+++ b/pulsar/impl_client.go
@@ -28,6 +28,7 @@ import (
 	"io/ioutil"
 	"net/url"
 	"pulsar-client-go/pulsar/internal"
+	"pulsar-client-go/pulsar/internal/auth"
 	pb "pulsar-client-go/pulsar/internal/pulsar_proto"
 )
 
@@ -37,6 +38,7 @@ type client struct {
 	cnxPool       internal.ConnectionPool
 	rpcClient     internal.RpcClient
 	lookupService internal.LookupService
+	auth          auth.Provider
 
 	handlers            map[internal.Closable]bool
 	producerIdGenerator uint64
@@ -70,8 +72,20 @@ func newClient(options ClientOptions) (Client, error) {
 		return nil, newError(ResultInvalidConfiguration, fmt.Sprintf("Invalid URL scheme '%s'", url.Scheme))
 	}
 
+	var authProvider auth.Provider
+	var ok bool
+
+	if options.Authentication == nil {
+		authProvider = auth.NewAuthDisabled()
+	} else {
+		authProvider, ok = options.Authentication.(auth.Provider)
+		if !ok {
+			return nil, errors.New("invalid auth provider interface")
+		}
+	}
+
 	c := &client{
-		cnxPool: internal.NewConnectionPool(tlsConfig),
+		cnxPool: internal.NewConnectionPool(tlsConfig, authProvider),
 	}
 	c.rpcClient = internal.NewRpcClient(url, c.cnxPool)
 	c.lookupService = internal.NewLookupService(c.rpcClient, url)
diff --git a/pulsar/impl_client_test.go b/pulsar/impl_client_test.go
index 9fd4231..218587f 100644
--- a/pulsar/impl_client_test.go
+++ b/pulsar/impl_client_test.go
@@ -125,3 +125,39 @@ func TestTLSConnectionHostNameVerificationError(t *testing.T) {
 	assert.NoError(t, err)
 }
 
+func TestTLSAuthError(t *testing.T) {
+	client, err := NewClient(ClientOptions{
+		URL:                   serviceUrlTls,
+		TLSTrustCertsFilePath: caCertsPath,
+	})
+	assert.NoError(t, err)
+
+	producer, err := client.CreateProducer(ProducerOptions{
+		Topic: newAuthTopicName(),
+	})
+
+	assert.Error(t, err)
+	assert.Nil(t, producer)
+
+	err = client.Close()
+	assert.NoError(t, err)
+}
+
+func TestTLSAuth(t *testing.T) {
+	client, err := NewClient(ClientOptions{
+		URL:                   serviceUrlTls,
+		TLSTrustCertsFilePath: caCertsPath,
+		Authentication: NewAuthenticationTLS(tlsClientCertPath, tlsClientKeyPath),
+	})
+	assert.NoError(t, err)
+
+	producer, err := client.CreateProducer(ProducerOptions{
+		Topic: newAuthTopicName(),
+	})
+
+	assert.NoError(t, err)
+	assert.NotNil(t, producer)
+
+	err = client.Close()
+	assert.NoError(t, err)
+}
diff --git a/pulsar/test_helper.go b/pulsar/internal/auth/disabled.go
similarity index 67%
copy from pulsar/test_helper.go
copy to pulsar/internal/auth/disabled.go
index c6c78e1..7d8bbf7 100644
--- a/pulsar/test_helper.go
+++ b/pulsar/internal/auth/disabled.go
@@ -17,20 +17,33 @@
 // under the License.
 //
 
-package pulsar
+package auth
 
-import (
-	"fmt"
-	"time"
-)
+import "crypto/tls"
 
-const (
-	serviceUrl    = "pulsar://localhost:6650"
-	serviceUrlTls = "pulsar+ssl://localhost:6651"
+type disabled struct {
+}
+
+func NewAuthDisabled() Provider {
+	return &disabled{}
+}
+
+func (disabled) Init() error {
+	return nil
+}
 
-	caCertsPath   = "../integration-tests/certs/cacert.pem"
-)
+func (disabled) GetData() ([]byte, error) {
+	return nil, nil
+}
+
+func (disabled) Name() string {
+	return ""
+}
+
+func (disabled) GetTlsCertificate() (*tls.Certificate, error) {
+	return nil, nil
+}
 
-func newTopicName() string {
-	return fmt.Sprintf("my-topic-%v", time.Now().Nanosecond())
+func (disabled) Close() error {
+	return nil
 }
diff --git a/pulsar/test_helper.go b/pulsar/internal/auth/provider.go
similarity index 54%
copy from pulsar/test_helper.go
copy to pulsar/internal/auth/provider.go
index c6c78e1..c23a662 100644
--- a/pulsar/test_helper.go
+++ b/pulsar/internal/auth/provider.go
@@ -17,20 +17,44 @@
 // under the License.
 //
 
-package pulsar
+package auth
 
 import (
+	"crypto/tls"
 	"fmt"
-	"time"
+	"github.com/pkg/errors"
+	"io"
 )
 
-const (
-	serviceUrl    = "pulsar://localhost:6650"
-	serviceUrlTls = "pulsar+ssl://localhost:6651"
+type Provider interface {
+	Init() error
 
-	caCertsPath   = "../integration-tests/certs/cacert.pem"
-)
+	Name() string
+
+	// return a client certificate chain, or nil if the data are not available
+	GetTlsCertificate() (*tls.Certificate, error)
+
+	//
+	GetData() ([]byte, error)
+
+	io.Closer
+}
+
+func NewProvider(name string, params string) (Provider, error) {
+	m := parseParams(params)
+
+	switch name {
+	case "":
+		return NewAuthDisabled(), nil
+
+	case "tls", "org.apache.pulsar.client.impl.auth.AuthenticationTls":
+		return NewAuthenticationTLSWithParams(m), nil
+
+	default:
+		return nil, errors.New(fmt.Sprintf("invalid auth provider '%s'", name))
+	}
+}
 
-func newTopicName() string {
-	return fmt.Sprintf("my-topic-%v", time.Now().Nanosecond())
+func parseParams(params string) map[string]string {
+	return nil
 }
diff --git a/pulsar/internal/auth/tls.go b/pulsar/internal/auth/tls.go
new file mode 100644
index 0000000..2f7e262
--- /dev/null
+++ b/pulsar/internal/auth/tls.go
@@ -0,0 +1,64 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package auth
+
+import "crypto/tls"
+
+type tlsAuthProvider struct {
+	certificatePath string
+	privateKeyPath  string
+}
+
+func NewAuthenticationTLSWithParams(params map[string]string) Provider {
+	return NewAuthenticationTLS(
+		params["tlsCertFile"],
+		params["tlsKeyFile"],
+	)
+}
+
+func NewAuthenticationTLS(certificatePath string, privateKeyPath string) Provider {
+	return &tlsAuthProvider{
+		certificatePath: certificatePath,
+		privateKeyPath:  privateKeyPath,
+	}
+}
+
+func (p *tlsAuthProvider) Init() error {
+	// Try to read certificates immediately to provide better error at startup
+	_, err := p.GetTlsCertificate()
+	return err
+}
+
+func (p *tlsAuthProvider) Name() string {
+	return "tls"
+}
+
+func (p *tlsAuthProvider) GetTlsCertificate() (*tls.Certificate, error) {
+	cert, err := tls.LoadX509KeyPair(p.certificatePath, p.privateKeyPath)
+	return &cert, err
+}
+
+func (p *tlsAuthProvider) GetData() ([]byte, error) {
+	return nil, nil
+}
+
+func (tlsAuthProvider) Close() error {
+	return nil
+}
diff --git a/pulsar/internal/connection.go b/pulsar/internal/connection.go
index fdcfd60..07b1d68 100644
--- a/pulsar/internal/connection.go
+++ b/pulsar/internal/connection.go
@@ -28,6 +28,7 @@ import (
 	"io/ioutil"
 	"net"
 	"net/url"
+	"pulsar-client-go/pulsar/internal/auth"
 	pb "pulsar-client-go/pulsar/internal/pulsar_proto"
 	"sync"
 	"sync/atomic"
@@ -99,9 +100,10 @@ type connection struct {
 	listeners        map[uint64]ConnectionListener
 
 	tlsOptions *TLSOptions
+	auth       auth.Provider
 }
 
-func newConnection(logicalAddr *url.URL, physicalAddr *url.URL, tlsOptions *TLSOptions) *connection {
+func newConnection(logicalAddr *url.URL, physicalAddr *url.URL, tlsOptions *TLSOptions, auth auth.Provider) *connection {
 	cnx := &connection{
 		state:                connectionInit,
 		logicalAddr:          logicalAddr,
@@ -112,6 +114,7 @@ func newConnection(logicalAddr *url.URL, physicalAddr *url.URL, tlsOptions *TLSO
 		lastDataReceivedTime: time.Now(),
 		pingTicker:           time.NewTicker(keepAliveInterval),
 		tlsOptions:           tlsOptions,
+		auth:                 auth,
 
 		incomingRequests: make(chan *request),
 		writeRequests:    make(chan []byte),
@@ -151,7 +154,7 @@ func (c *connection) connect() (ok bool) {
 		cnx, err = net.Dial("tcp", c.physicalAddr.Host)
 	} else {
 		// TLS connection
-		tlsConfig, err  = c.getTlsConfig()
+		tlsConfig, err = c.getTlsConfig()
 		if err != nil {
 			c.log.WithError(err).Warn("Failed to configure TLS ")
 			return false
@@ -176,11 +179,18 @@ func (c *connection) connect() (ok bool) {
 func (c *connection) doHandshake() (ok bool) {
 	// Send 'Connect' command to initiate handshake
 	version := int32(pb.ProtocolVersion_v13)
+
+	authData, err := c.auth.GetData()
+	if err != nil {
+		c.log.WithError(err).Warn("Failed to load auth credentials")
+		return false
+	}
+
 	c.writeCommand(baseCommand(pb.BaseCommand_CONNECT, &pb.CommandConnect{
 		ProtocolVersion: &version,
 		ClientVersion:   proto.String("Pulsar Go 0.1"),
-		// AuthMethodName: "token",
-		// AuthData: authData,
+		AuthMethodName:  proto.String(c.auth.Name()),
+		AuthData:        authData,
 	}))
 
 	cmd, _, err := c.reader.readSingleCommand()
@@ -461,5 +471,14 @@ func (c *connection) getTlsConfig() (*tls.Config, error) {
 		tlsConfig.ServerName = c.physicalAddr.Hostname()
 	}
 
+	cert, err := c.auth.GetTlsCertificate()
+	if err != nil {
+		return nil, err
+	}
+
+	if cert != nil {
+		tlsConfig.Certificates = []tls.Certificate{*cert}
+	}
+
 	return tlsConfig, nil
 }
diff --git a/pulsar/internal/connection_pool.go b/pulsar/internal/connection_pool.go
index 6540137..b8cdd0f 100644
--- a/pulsar/internal/connection_pool.go
+++ b/pulsar/internal/connection_pool.go
@@ -22,6 +22,7 @@ package internal
 import (
 	log "github.com/sirupsen/logrus"
 	"net/url"
+	"pulsar-client-go/pulsar/internal/auth"
 	"sync"
 )
 
@@ -35,11 +36,13 @@ type ConnectionPool interface {
 type connectionPool struct {
 	pool       sync.Map
 	tlsOptions *TLSOptions
+	auth       auth.Provider
 }
 
-func NewConnectionPool(tlsOptions *TLSOptions) ConnectionPool {
+func NewConnectionPool(tlsOptions *TLSOptions, auth auth.Provider) ConnectionPool {
 	return &connectionPool{
 		tlsOptions: tlsOptions,
+		auth:       auth,
 	}
 }
 
@@ -60,7 +63,8 @@ func (p *connectionPool) GetConnection(logicalAddr *url.URL, physicalAddr *url.U
 	}
 
 	// Try to create a new connection
-	newCnx, wasCached := p.pool.LoadOrStore(logicalAddr.Host, newConnection(logicalAddr, physicalAddr, p.tlsOptions))
+	newCnx, wasCached := p.pool.LoadOrStore(logicalAddr.Host,
+		newConnection(logicalAddr, physicalAddr, p.tlsOptions, p.auth))
 	cnx := newCnx.(*connection)
 	if !wasCached {
 		cnx.start()
diff --git a/pulsar/test_helper.go b/pulsar/test_helper.go
index c6c78e1..1e84a92 100644
--- a/pulsar/test_helper.go
+++ b/pulsar/test_helper.go
@@ -28,9 +28,15 @@ const (
 	serviceUrl    = "pulsar://localhost:6650"
 	serviceUrlTls = "pulsar+ssl://localhost:6651"
 
-	caCertsPath   = "../integration-tests/certs/cacert.pem"
+	caCertsPath       = "../integration-tests/certs/cacert.pem"
+	tlsClientCertPath = "../integration-tests/certs/client-cert.pem"
+	tlsClientKeyPath = "../integration-tests/certs/client-key.pem"
 )
 
 func newTopicName() string {
 	return fmt.Sprintf("my-topic-%v", time.Now().Nanosecond())
 }
+
+func newAuthTopicName() string {
+	return fmt.Sprintf("private/auth/my-topic-%v", time.Now().Nanosecond())
+}


[pulsar-client-go] 11/38: Added blocking queue implementation

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit b95a97191e2a5c63d67ab41940dcd57c1032c7ee
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Tue Apr 9 21:28:31 2019 -0700

    Added blocking queue implementation
---
 pulsar/impl/util/blocking_queue.go      | 125 ++++++++++++++++++++++++++++++++
 pulsar/impl/util/blocking_queue_test.go |  97 +++++++++++++++++++++++++
 2 files changed, 222 insertions(+)

diff --git a/pulsar/impl/util/blocking_queue.go b/pulsar/impl/util/blocking_queue.go
new file mode 100644
index 0000000..281d6ba
--- /dev/null
+++ b/pulsar/impl/util/blocking_queue.go
@@ -0,0 +1,125 @@
+package util
+
+import (
+	"sync"
+)
+
+type BlockingQueue interface {
+	// Enqueue one item, block if the queue is full
+	Put(item interface{})
+
+	// Dequeue one item, block until it's available
+	Take() interface{}
+
+	// Dequeue one item, return nil if queue is empty
+	Poll() interface{}
+
+	// Return one item without dequeing, return nil if queue is empty
+	Peek() interface{}
+
+	// Return the current size of the queue
+	Size() int
+}
+
+type blockingQueue struct {
+	items   []interface{}
+	headIdx int
+	tailIdx int
+	size    int
+	maxSize int
+
+	mutex      sync.Mutex
+	isNotEmpty *sync.Cond
+	isNotFull  *sync.Cond
+}
+
+func NewBlockingQueue(maxSize int) BlockingQueue {
+	bq := &blockingQueue{
+		items:   make([]interface{}, maxSize),
+		headIdx: 0,
+		tailIdx: 0,
+		size:    0,
+		maxSize: maxSize,
+	}
+
+	bq.isNotEmpty = sync.NewCond(&bq.mutex)
+	bq.isNotFull = sync.NewCond(&bq.mutex)
+	return bq
+}
+
+func (bq *blockingQueue) Put(item interface{}) {
+	bq.mutex.Lock()
+	defer bq.mutex.Unlock()
+
+	for ; bq.size == bq.maxSize; {
+		bq.isNotFull.Wait()
+	}
+
+	wasEmpty := bq.size == 0
+
+	bq.items[bq.tailIdx] = item
+	bq.size += 1
+	bq.tailIdx += 1
+	if bq.tailIdx >= bq.maxSize {
+		bq.tailIdx = 0
+	}
+
+	if wasEmpty {
+		// Wake up eventual reader waiting for next item
+		bq.isNotEmpty.Signal()
+	}
+}
+
+func (bq *blockingQueue) Take() interface{} {
+	bq.mutex.Lock()
+	defer bq.mutex.Unlock()
+
+	for ; bq.size == 0; {
+		bq.isNotEmpty.Wait()
+	}
+
+	return bq.dequeue()
+}
+
+func (bq *blockingQueue) Poll() interface{} {
+	bq.mutex.Lock()
+	defer bq.mutex.Unlock()
+
+	if bq.size == 0 {
+		return nil
+	}
+
+	return bq.dequeue()
+}
+
+func (bq *blockingQueue) Peek() interface{} {
+	bq.mutex.Lock()
+	defer bq.mutex.Unlock()
+
+	if bq.size == 0 {
+		return nil
+	} else {
+		return bq.items[bq.headIdx]
+	}
+}
+
+func (bq *blockingQueue) dequeue() interface{} {
+	item := bq.items[bq.headIdx]
+	bq.items[bq.headIdx] = nil
+
+	bq.headIdx += 1
+	if bq.headIdx == len(bq.items) {
+		bq.headIdx = 0
+	}
+
+	bq.size -= 1
+	bq.isNotFull.Signal()
+	return item
+}
+
+func (bq *blockingQueue) Size() int {
+	bq.mutex.Lock()
+	defer bq.mutex.Unlock()
+
+	return bq.size
+}
diff --git a/pulsar/impl/util/blocking_queue_test.go b/pulsar/impl/util/blocking_queue_test.go
new file mode 100644
index 0000000..0ecc8d1
--- /dev/null
+++ b/pulsar/impl/util/blocking_queue_test.go
@@ -0,0 +1,97 @@
+package util
+
+import (
+	"fmt"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestBlockingQueue(t *testing.T) {
+	q := NewBlockingQueue(10)
+
+	assert.Equal(t, 0, q.Size())
+	assert.Equal(t, nil, q.Poll())
+	assert.Equal(t, nil, q.Peek())
+
+	q.Put("test")
+	assert.Equal(t, 1, q.Size())
+
+	assert.Equal(t, "test", q.Peek())
+	assert.Equal(t, 1, q.Size())
+
+	assert.Equal(t, "test", q.Take())
+	assert.Equal(t, 0, q.Size())
+
+	ch := make(chan string)
+
+	go func() {
+		// Stays blocked until item is available
+		ch <- q.Take().(string)
+	}()
+
+	time.Sleep(100 * time.Millisecond)
+
+	select {
+	case _ = <-ch:
+		assert.Fail(t, "Should not have had a value at this point")
+	default:
+		// Good, no value yet
+	}
+
+	q.Put("test-2")
+
+	x := <-ch
+	assert.Equal(t, "test-2", x)
+
+	// Fill the queue
+	for i := 0; i < 10; i++ {
+		q.Put(fmt.Sprintf("i-%d", i))
+		assert.Equal(t, i+1, q.Size())
+	}
+
+	for i := 0; i < 10; i++ {
+		assert.Equal(t, fmt.Sprintf("i-%d", i), q.Take())
+	}
+
+	close(ch)
+}
+
+func TestBlockingQueueWaitWhenFull(t *testing.T) {
+	q := NewBlockingQueue(3)
+
+	q.Put("test-1")
+	q.Put("test-2")
+	q.Put("test-3")
+	assert.Equal(t, 3, q.Size())
+
+	ch := make(chan bool)
+
+	go func() {
+		// Stays blocked until space is available
+		q.Put("test-4")
+		ch <- true
+	}()
+
+	time.Sleep(100 * time.Millisecond)
+
+	select {
+	case _ = <-ch:
+		assert.Fail(t, "Should not have had a value at this point")
+	default:
+		// Good, no value yet
+	}
+
+	assert.Equal(t, "test-1", q.Poll())
+
+	// Now the go-routine should have completed
+	_ = <-ch
+	assert.Equal(t, 3, q.Size())
+
+	assert.Equal(t, "test-2", q.Take())
+	assert.Equal(t, "test-3", q.Take())
+	assert.Equal(t, "test-4", q.Take())
+
+	close(ch)
+}


[pulsar-client-go] 09/38: Basic publishing works

Posted by mm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git

commit f7cac7a6506b0091b9a5a291c48b968603a1c07a
Author: Matteo Merli <mm...@apache.org>
AuthorDate: Fri Apr 5 15:38:20 2019 -0700

    Basic publishing works
---
 pulsar/impl/batch_builder.go      | 92 +++++++++++++++++++++++++++++++++++---
 pulsar/impl/buffer.go             | 39 ++++++++++++++++
 pulsar/impl/checksum.go           |  9 ++++
 pulsar/impl/commands.go           | 61 +++++++++++++++++++++++++
 pulsar/impl/connection.go         | 42 +++++++++++++----
 pulsar/impl/utils.go              | 20 +++++++++
 pulsar/impl_partition_producer.go | 94 +++++++++++++++++++++++++++++++++------
 pulsar/message.go                 |  4 +-
 8 files changed, 331 insertions(+), 30 deletions(-)

diff --git a/pulsar/impl/batch_builder.go b/pulsar/impl/batch_builder.go
index 7eb2030..6b0df8e 100644
--- a/pulsar/impl/batch_builder.go
+++ b/pulsar/impl/batch_builder.go
@@ -1,23 +1,101 @@
 package impl
 
+import (
+	"github.com/golang/protobuf/proto"
+	log "github.com/sirupsen/logrus"
+	pb "pulsar-client-go-native/pulsar/pulsar_proto"
+	"time"
+)
+
+const MaxMessageSize = 5 * 1024 * 1024
+
+const MaxBatchSize = 128 * 1024
+
+const DefaultMaxMessagesPerBatch = 1000
+
 type BatchBuilder struct {
 	buffer Buffer
+
+	// Current number of messages in the batch
+	numMessages uint
+
+	// Max number of message allowed in the batch
+	maxMessages uint
+
+	producerName string
+	producerId   uint64
+
+	cmdSend     *pb.BaseCommand
+	msgMetadata *pb.MessageMetadata
 }
 
-func NewBatchBuilder() *BatchBuilder {
+func NewBatchBuilder(maxMessages uint, producerName string, producerId uint64) *BatchBuilder {
+	if maxMessages == 0 {
+		maxMessages = DefaultMaxMessagesPerBatch
+	}
 	return &BatchBuilder{
-		buffer: NewBuffer(4096),
+		buffer:       NewBuffer(4096),
+		numMessages:  0,
+		maxMessages:  maxMessages,
+		producerName: producerName,
+		producerId:   producerId,
+		cmdSend: baseCommand(pb.BaseCommand_SEND,
+			&pb.CommandSend{
+				ProducerId: &producerId,
+			}),
+		msgMetadata: &pb.MessageMetadata{
+			ProducerName: &producerName,
+		},
 	}
 }
 
-func (bb *BatchBuilder) isFull() bool {
-	return false
+func (bb *BatchBuilder) IsFull() bool {
+	return bb.numMessages >= bb.maxMessages || bb.buffer.ReadableBytes() > MaxBatchSize
+}
+
+func (bb *BatchBuilder) hasSpace(payload []byte) bool {
+	msgSize := uint32(len(payload))
+	return bb.numMessages > 0 && (bb.buffer.ReadableBytes()+msgSize) > MaxBatchSize
 }
 
-func (bb *BatchBuilder) hasSpace(size int) bool {
-	return false
+func (bb *BatchBuilder) Add(metadata *pb.SingleMessageMetadata, sequenceId uint64, payload []byte) bool {
+	if bb.hasSpace(payload) {
+		// The current batch is full. Producer has to call Flush() to
+		return false
+	}
+
+	if bb.numMessages == 0 {
+		bb.msgMetadata.SequenceId = proto.Uint64(sequenceId)
+		bb.msgMetadata.PublishTime = proto.Uint64(TimestampMillis(time.Now()))
+		bb.msgMetadata.SequenceId = proto.Uint64(sequenceId)
+		bb.msgMetadata.ProducerName = &bb.producerName
+
+		bb.cmdSend.Send.SequenceId = proto.Uint64(sequenceId)
+	}
+	serializeSingleMessage(bb.buffer, metadata, payload)
+
+	bb.numMessages += 1
+	return true
+}
+
+func (bb *BatchBuilder) reset() {
+	bb.numMessages = 0
+	bb.buffer.Clear()
 }
 
 func (bb *BatchBuilder) Flush() []byte {
-	return nil
+	log.Info("BatchBuilder flush: messages: ", bb.numMessages)
+	if bb.numMessages == 0 {
+		// No-Op for empty batch
+		return nil
+	}
+
+	bb.msgMetadata.NumMessagesInBatch = proto.Int32(int32(bb.numMessages))
+	bb.cmdSend.Send.NumMessages = proto.Int32(int32(bb.numMessages))
+
+	buffer := NewBuffer(4096)
+	serializeBatch(buffer, bb.cmdSend, bb.msgMetadata, bb.buffer.ReadableSlice())
+
+	bb.reset()
+	return buffer.ReadableSlice()
 }
diff --git a/pulsar/impl/buffer.go b/pulsar/impl/buffer.go
index 8e81f0c..3d03002 100644
--- a/pulsar/impl/buffer.go
+++ b/pulsar/impl/buffer.go
@@ -13,6 +13,8 @@ type Buffer interface {
 
 	Read(size uint32) []byte
 
+	Get(readerIndex uint32, size uint32) []byte
+
 	ReadableSlice() []byte
 
 	WritableSlice() []byte
@@ -23,12 +25,20 @@ type Buffer interface {
 	// Copy the available portion of data at the beginning of the buffer
 	MoveToFront()
 
+	ReadUint16() uint16
 	ReadUint32() uint32
 
+	WriteUint16(n uint16)
 	WriteUint32(n uint32)
 
+	WriterIndex() uint32
+	ReaderIndex() uint32
+
 	Write(s []byte)
 
+	Put(writerIdx uint32, s []byte)
+	PutUint32(n uint32, writerIdx uint32)
+
 	Resize(newSize uint32)
 
 	Clear()
@@ -71,6 +81,10 @@ func (b *buffer) Read(size uint32) []byte {
 	return res
 }
 
+func (b *buffer) Get(readerIdx uint32, size uint32) []byte {
+	return b.data[readerIdx : readerIdx+size]
+}
+
 func (b *buffer) ReadableSlice() []byte {
 	return b.data[b.readerIdx:b.writerIdx]
 }
@@ -83,6 +97,14 @@ func (b *buffer) WrittenBytes(size uint32) {
 	b.writerIdx += size
 }
 
+func (b *buffer) WriterIndex() uint32 {
+	return b.writerIdx
+}
+
+func (b *buffer) ReaderIndex() uint32 {
+	return b.readerIdx
+}
+
 func (b *buffer) MoveToFront() {
 	size := b.ReadableBytes()
 	copy(b.data, b.Read(size))
@@ -103,16 +125,33 @@ func (b *buffer) ReadUint32() uint32 {
 	return binary.BigEndian.Uint32(b.Read(4))
 }
 
+func (b *buffer) ReadUint16() uint16 {
+	return binary.BigEndian.Uint16(b.Read(2))
+}
+
 func (b *buffer) WriteUint32(n uint32) {
 	binary.BigEndian.PutUint32(b.WritableSlice(), n)
 	b.writerIdx += 4
 }
 
+func (b *buffer) PutUint32(n uint32, idx uint32) {
+	binary.BigEndian.PutUint32(b.data[idx:], n)
+}
+
+func (b *buffer) WriteUint16(n uint16) {
+	binary.BigEndian.PutUint16(b.WritableSlice(), n)
+	b.writerIdx += 2
+}
+
 func (b *buffer) Write(s []byte) {
 	copy(b.WritableSlice(), s)
 	b.writerIdx += uint32(len(s))
 }
 
+func (b *buffer) Put(writerIdx uint32, s []byte) {
+	copy(b.data[writerIdx:], s)
+}
+
 func (b *buffer) Clear() {
 	b.readerIdx = 0
 	b.writerIdx = 0
diff --git a/pulsar/impl/checksum.go b/pulsar/impl/checksum.go
new file mode 100644
index 0000000..cbea686
--- /dev/null
+++ b/pulsar/impl/checksum.go
@@ -0,0 +1,9 @@
+package impl
+
+import "hash/crc32"
+
+var crc32cTable = crc32.MakeTable(crc32.Castagnoli)
+
+func Crc32cCheckSum(data []byte) uint32 {
+	return crc32.Checksum(data, crc32cTable)
+}
\ No newline at end of file
diff --git a/pulsar/impl/commands.go b/pulsar/impl/commands.go
index ed9f6b9..6b1c840 100644
--- a/pulsar/impl/commands.go
+++ b/pulsar/impl/commands.go
@@ -8,6 +8,8 @@ import (
 
 const MaxFrameSize = 5 * 1024 * 1024
 
+const magicCrc32c uint16 = 0x0e01
+
 func baseCommand(cmdType pb.BaseCommand_Type, msg proto.Message) *pb.BaseCommand {
 	cmd := &pb.BaseCommand{
 		Type: &cmdType,
@@ -25,6 +27,8 @@ func baseCommand(cmdType pb.BaseCommand_Type, msg proto.Message) *pb.BaseCommand
 		cmd.Ping = msg.(*pb.CommandPing)
 	case pb.BaseCommand_PONG:
 		cmd.Pong = msg.(*pb.CommandPong)
+	case pb.BaseCommand_SEND:
+		cmd.Send = msg.(*pb.CommandSend)
 	default:
 		log.Panic("Missing command type: ", cmdType)
 	}
@@ -32,6 +36,63 @@ func baseCommand(cmdType pb.BaseCommand_Type, msg proto.Message) *pb.BaseCommand
 	return cmd
 }
 
+func serializeSingleMessage(wb Buffer, smm *pb.SingleMessageMetadata, payload []byte) {
+	serialized, err := proto.Marshal(smm)
+	if err != nil {
+		log.WithError(err).Fatal("Protobuf serialization error")
+	}
+
+	wb.Write(serialized)
+	wb.Write(payload)
+}
+
+func serializeBatch(wb Buffer, cmdSend *pb.BaseCommand, msgMetadata *pb.MessageMetadata, payload []byte) {
+	// Wire format
+	// [TOTAL_SIZE] [CMD_SIZE][CMD] [MAGIC_NUMBER][CHECKSUM] [METADATA_SIZE][METADATA] [PAYLOAD]
+	cmdSize := proto.Size(cmdSend)
+	msgMetadataSize := proto.Size(msgMetadata)
+	payloadSize := len(payload)
+
+	magicAndChecksumLength := 2 + 4 /* magic + checksumLength */
+	headerContentSize := 4 + cmdSize + magicAndChecksumLength + 4 + msgMetadataSize
+	// cmdLength + cmdSize + magicLength + checksumSize + msgMetadataLength + msgMetadataSize
+	totalSize := headerContentSize + payloadSize
+
+	wb.WriteUint32(uint32(totalSize))  // External frame
+
+	// Write cmd
+	wb.WriteUint32(uint32(cmdSize))
+	serialized, err := proto.Marshal(cmdSend)
+	if err != nil {
+		log.WithError(err).Fatal("Protobuf error when serializing cmdSend")
+	}
+
+	wb.Write(serialized)
+
+	// Create checksum placeholder
+	wb.WriteUint16(magicCrc32c)
+	checksumIdx := wb.WriterIndex()
+	wb.WriteUint32(0) // skip 4 bytes of checksum
+
+	// Write metadata
+	metadataStartIdx := wb.WriterIndex()
+	wb.WriteUint32(uint32(msgMetadataSize))
+	serialized, err = proto.Marshal(msgMetadata)
+	if err != nil {
+		log.WithError(err).Fatal("Protobuf error when serializing msgMetadata")
+	}
+
+	wb.Write(serialized)
+	wb.Write(payload)
+
+	// Write checksum at created checksum-placeholder
+	endIdx := wb.WriterIndex()
+	checksum := Crc32cCheckSum(wb.Get(metadataStartIdx, endIdx-metadataStartIdx))
+
+	// set computed checksum
+	wb.PutUint32(checksum, checksumIdx)
+}
+
 func ConvertFromStringMap(m map[string]string) []*pb.KeyValue {
 	list := make([]*pb.KeyValue, len(m))
 
diff --git a/pulsar/impl/connection.go b/pulsar/impl/connection.go
index 365f1b3..04fa10f 100644
--- a/pulsar/impl/connection.go
+++ b/pulsar/impl/connection.go
@@ -14,6 +14,7 @@ import (
 
 type Connection interface {
 	SendRequest(requestId uint64, req *pb.BaseCommand, callback func(command *pb.BaseCommand))
+	WriteData(data []byte)
 	Close()
 }
 
@@ -54,6 +55,7 @@ type connection struct {
 	requestIdGenerator uint64
 
 	incomingRequests chan *request
+	writeRequests    chan []byte
 	pendingReqs      map[uint64]*request
 }
 
@@ -67,6 +69,9 @@ func newConnection(logicalAddr *url.URL, physicalAddr *url.URL) *connection {
 		pendingReqs:          make(map[uint64]*request),
 		lastDataReceivedTime: time.Now(),
 		pingTicker:           time.NewTicker(keepAliveInterval),
+
+		incomingRequests: make(chan *request),
+		writeRequests:    make(chan []byte),
 	}
 	cnx.reader = newConnectionReader(cnx)
 	cnx.cond = sync.NewCond(cnx)
@@ -164,12 +169,27 @@ func (c *connection) run() {
 			c.pendingReqs[req.id] = req
 			c.writeCommand(req.cmd)
 
+		case data := <-c.writeRequests:
+			c.internalWriteData(data)
+
 		case _ = <-c.pingTicker.C:
 			c.sendPing()
 		}
 	}
 }
 
+func (c *connection) WriteData(data []byte) {
+	c.writeRequests <- data
+}
+
+func (c *connection) internalWriteData(data []byte) {
+	c.log.Info("Write data: ", len(data))
+	if _, err := c.cnx.Write(data); err != nil {
+		c.log.WithError(err).Warn("Failed to write on connection")
+		c.Close()
+	}
+}
+
 func (c *connection) writeCommand(cmd proto.Message) {
 	// Wire format
 	// [FRAME_SIZE] [CMD_SIZE][CMD]
@@ -190,11 +210,7 @@ func (c *connection) writeCommand(cmd proto.Message) {
 	}
 
 	c.writeBuffer.Write(serialized)
-
-	if _, err := c.cnx.Write(c.writeBuffer.ReadableSlice()); err != nil {
-		c.log.WithError(err).Warn("Failed to write on connection")
-		c.Close()
-	}
+	c.internalWriteData(c.writeBuffer.ReadableSlice())
 }
 
 func (c *connection) receivedCommand(cmd *pb.BaseCommand, headersAndPayload []byte) {
@@ -230,6 +246,8 @@ func (c *connection) receivedCommand(cmd *pb.BaseCommand, headersAndPayload []by
 	case pb.BaseCommand_CLOSE_PRODUCER:
 	case pb.BaseCommand_CLOSE_CONSUMER:
 	case pb.BaseCommand_SEND_RECEIPT:
+		c.log.Info("Got SEND_RECEIPT: ", cmd.GetSendReceipt())
+
 	case pb.BaseCommand_SEND_ERROR:
 
 	case pb.BaseCommand_MESSAGE:
@@ -246,13 +264,21 @@ func (c *connection) receivedCommand(cmd *pb.BaseCommand, headersAndPayload []by
 	}
 }
 
+func (c *connection) Write(data []byte) {
+	c.writeRequests <- data
+}
+
 func (c *connection) SendRequest(requestId uint64, req *pb.BaseCommand, callback func(command *pb.BaseCommand)) {
-	c.pendingReqs[requestId] = &request{
+	c.incomingRequests <- &request{
 		id:       requestId,
 		cmd:      req,
 		callback: callback,
 	}
-	c.writeCommand(req)
+}
+
+func (c *connection) internalSendRequest(req *request) {
+	c.pendingReqs[req.id] = req
+	c.writeCommand(req.cmd)
 }
 
 func (c *connection) handleResponse(requestId uint64, response *pb.BaseCommand) {
@@ -299,6 +325,7 @@ func (c *connection) Close() {
 		c.cnx.Close()
 		c.pingTicker.Stop()
 		close(c.incomingRequests)
+		close(c.writeRequests)
 		c.cnx = nil
 	}
 }
@@ -313,4 +340,3 @@ func (c *connection) changeState(state connectionState) {
 func (c *connection) newRequestId() uint64 {
 	return atomic.AddUint64(&c.requestIdGenerator, 1)
 }
-
diff --git a/pulsar/impl/utils.go b/pulsar/impl/utils.go
new file mode 100644
index 0000000..02d15b3
--- /dev/null
+++ b/pulsar/impl/utils.go
@@ -0,0 +1,20 @@
+package impl
+
+import (
+	"sync/atomic"
+	"time"
+)
+
+func TimestampMillis(t time.Time) uint64 {
+	return uint64(t.UnixNano()) / uint64(time.Millisecond)
+}
+
+// Perform atomic read and update
+func GetAndAdd(n *uint64, diff uint64) uint64 {
+	for {
+		v := *n
+		if atomic.CompareAndSwapUint64(n, v, v+diff) {
+			return v
+		}
+	}
+}
diff --git a/pulsar/impl_partition_producer.go b/pulsar/impl_partition_producer.go
index 7bb2165..2677770 100644
--- a/pulsar/impl_partition_producer.go
+++ b/pulsar/impl_partition_producer.go
@@ -2,10 +2,12 @@ package pulsar
 
 import (
 	"context"
+	"github.com/golang/protobuf/proto"
 	log "github.com/sirupsen/logrus"
 	"pulsar-client-go-native/pulsar/impl"
 	pb "pulsar-client-go-native/pulsar/pulsar_proto"
 	"sync"
+	"time"
 )
 
 type partitionProducer struct {
@@ -16,21 +18,36 @@ type partitionProducer struct {
 	cond   *sync.Cond
 	cnx    impl.Connection
 
-	producerName *string
-	producerId   uint64
+	options             *ProducerOptions
+	producerName        *string
+	producerId          uint64
+	batchBuilder        *impl.BatchBuilder
+	sequenceIdGenerator *uint64
+	batchFlushTicker    *time.Ticker
 
 	// Channel where app is posting messages to be published
 	eventsChan chan interface{}
 }
 
+const defaultBatchingMaxPublishDelay = 10 * time.Millisecond
+
 func newPartitionProducer(client *client, topic string, options *ProducerOptions) (*partitionProducer, error) {
 
+	var batchingMaxPublishDelay time.Duration
+	if options.BatchingMaxPublishDelay != 0 {
+		batchingMaxPublishDelay = options.BatchingMaxPublishDelay
+	} else {
+		batchingMaxPublishDelay = defaultBatchingMaxPublishDelay
+	}
+
 	p := &partitionProducer{
-		log:        log.WithField("topic", topic),
-		client:     client,
-		topic:      topic,
-		producerId: client.rpcClient.NewProducerId(),
-		eventsChan: make(chan interface{}),
+		log:              log.WithField("topic", topic),
+		client:           client,
+		topic:            topic,
+		options:          options,
+		producerId:       client.rpcClient.NewProducerId(),
+		eventsChan:       make(chan interface{}),
+		batchFlushTicker: time.NewTicker(batchingMaxPublishDelay),
 	}
 
 	if options.Name != "" {
@@ -74,6 +91,13 @@ func (p *partitionProducer) grabCnx() error {
 	}
 
 	p.producerName = res.Response.ProducerSuccess.ProducerName
+	if p.batchBuilder == nil {
+		p.batchBuilder = impl.NewBatchBuilder(p.options.BatchingMaxMessages, *p.producerName, p.producerId)
+	}
+	if p.sequenceIdGenerator == nil {
+		nextSequenceId := uint64(res.Response.ProducerSuccess.GetLastSequenceId() + 1)
+		p.sequenceIdGenerator = &nextSequenceId
+	}
 	p.cnx = res.Cnx
 	p.log.WithField("cnx", res.Cnx).Debug("Connected producer")
 	return nil
@@ -81,11 +105,15 @@ func (p *partitionProducer) grabCnx() error {
 
 func (p *partitionProducer) run() {
 	for {
-		i := <-p.eventsChan
-		switch v := i.(type) {
-		case *sendRequest:
-			p.log.Debug("Received send request: ", v)
-			v.callback(nil, v.msg, nil)
+		select {
+		case i := <-p.eventsChan:
+			switch v := i.(type) {
+			case *sendRequest:
+				p.internalSend(v)
+			}
+
+		case _ = <-p.batchFlushTicker.C:
+			p.internalFlush()
 		}
 	}
 }
@@ -98,6 +126,47 @@ func (p *partitionProducer) Name() string {
 	return *p.producerName
 }
 
+func (p *partitionProducer) internalSend(request *sendRequest) {
+	p.log.Debug("Received send request: ", *request)
+
+	msg := request.msg
+
+	if msg.ReplicationClusters == nil {
+		smm := &pb.SingleMessageMetadata{
+			PayloadSize: proto.Int(len(msg.Payload)),
+		}
+
+		if msg.EventTime != nil {
+			smm.EventTime = proto.Uint64(impl.TimestampMillis(*msg.EventTime))
+		}
+
+		if msg.Key != "" {
+			smm.PartitionKey = &msg.Key
+		}
+
+		if msg.Properties != nil {
+			smm.Properties = impl.ConvertFromStringMap(msg.Properties)
+		}
+
+		sequenceId := impl.GetAndAdd(p.sequenceIdGenerator, 1)
+		for ; p.batchBuilder.Add(smm, sequenceId, msg.Payload) == false; {
+			// The current batch is full.. flush it and retry
+			p.internalFlush()
+		}
+	} else {
+		p.log.Panic("TODO: serialize into single message")
+	}
+}
+
+func (p *partitionProducer) internalFlush() {
+	batchData := p.batchBuilder.Flush()
+	if batchData == nil {
+		return
+	}
+
+	p.cnx.WriteData(batchData)
+}
+
 func (p *partitionProducer) Send(ctx context.Context, msg *ProducerMessage) error {
 	wg := sync.WaitGroup{}
 	wg.Add(1)
@@ -117,7 +186,6 @@ func (p *partitionProducer) Send(ctx context.Context, msg *ProducerMessage) erro
 
 	wg.Wait()
 	return err
-	return nil
 }
 
 type sendRequest struct {
diff --git a/pulsar/message.go b/pulsar/message.go
index 60dd989..12bc269 100644
--- a/pulsar/message.go
+++ b/pulsar/message.go
@@ -32,13 +32,13 @@ type ProducerMessage struct {
 	Properties map[string]string
 
 	// Set the event time for a given message
-	EventTime time.Time
+	EventTime *time.Time
 
 	// Override the replication clusters for this message.
 	ReplicationClusters []string
 
 	// Set the sequence id to assign to the current message
-	SequenceID int64
+	SequenceID *int64
 }
 
 type Message interface {