mirror of
https://github.com/buger/goreplay.git
synced 2026-10-20 00:16:42 +00:00
Compare commits
10
Commits
1.3.3
...
memory-control
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6287727d2 | ||
|
|
a940bad19e | ||
|
|
32c005a63d | ||
|
|
c7d04738c1 | ||
|
|
01f5a1c9b5 | ||
|
|
c641e62786 | ||
|
|
fcad7155f2 | ||
|
|
257d4d36bd | ||
|
|
6cb06f97ca | ||
|
|
007dcb40c9 |
@@ -102,7 +102,7 @@ profile_test:
|
|||||||
|
|
||||||
# Used mainly for debugging, because docker container do not have access to parent machine ports
|
# Used mainly for debugging, because docker container do not have access to parent machine ports
|
||||||
run:
|
run:
|
||||||
$(RUN) go run $(LDFLAGS) $(SOURCE) --input-dummy=0 --output-http="http://localhost:9000" --input-raw-track-response --input-raw 127.0.0.1:9000 --verbose --debug --middleware "./examples/middleware/echo.sh" --output-file requests.gor
|
$(RUN) go run $(LDFLAGS) $(SOURCE) --input-dummy=0 --output-http="http://localhost:9000" --input-raw-track-response --input-raw 127.0.0.1:9000 --verbose 0 --middleware "./examples/middleware/echo.sh" --output-file requests.gor
|
||||||
|
|
||||||
run-2:
|
run-2:
|
||||||
$(RUN) go run $(LDFLAGS) $(SOURCE) --input-raw :8000 --input-raw-bpf-filter "dst port 8000" --output-stdout --output-http "http://localhost:8000" --input-dummy=0
|
$(RUN) go run $(LDFLAGS) $(SOURCE) --input-raw :8000 --input-raw-bpf-filter "dst port 8000" --output-stdout --output-http "http://localhost:8000" --input-dummy=0
|
||||||
|
|||||||
+99
-33
@@ -13,14 +13,17 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/buger/goreplay/size"
|
"github.com/buger/goreplay/size"
|
||||||
|
"github.com/buger/goreplay/tcp"
|
||||||
|
|
||||||
"github.com/google/gopacket"
|
"github.com/google/gopacket"
|
||||||
"github.com/google/gopacket/layers"
|
"github.com/google/gopacket/layers"
|
||||||
"github.com/google/gopacket/pcap"
|
"github.com/google/gopacket/pcap"
|
||||||
|
"github.com/pbnjay/memory"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler is a function that is used to handle packets
|
// PacketHandler is a function that is used to handle packets
|
||||||
type Handler func(*Packet)
|
type PacketHandler func(*tcp.Packet)
|
||||||
|
|
||||||
// PcapOptions options that can be set on a pcap capture handle,
|
// PcapOptions options that can be set on a pcap capture handle,
|
||||||
// these options take effect on inactive pcap handles
|
// these options take effect on inactive pcap handles
|
||||||
@@ -39,7 +42,7 @@ type Listener struct {
|
|||||||
sync.Mutex
|
sync.Mutex
|
||||||
Transport string // transport layer default to tcp
|
Transport string // transport layer default to tcp
|
||||||
Activate func() error // function is used to activate the engine. it must be called before reading packets
|
Activate func() error // function is used to activate the engine. it must be called before reading packets
|
||||||
Handles map[string]gopacket.PacketDataSource
|
Handles map[string]gopacket.ZeroCopyPacketDataSource
|
||||||
Interfaces []net.Interface
|
Interfaces []net.Interface
|
||||||
loopIndex int
|
loopIndex int
|
||||||
Reading chan bool // this channel is closed when the listener has started reading packets
|
Reading chan bool // this channel is closed when the listener has started reading packets
|
||||||
@@ -48,7 +51,8 @@ type Listener struct {
|
|||||||
port uint16 // src or/and dst port
|
port uint16 // src or/and dst port
|
||||||
trackResponse bool
|
trackResponse bool
|
||||||
|
|
||||||
host string // pcap file name or interface (name, hardware addr, index or ip address)
|
host string // pcap file name or interface (name, hardware addr, index or ip address)
|
||||||
|
maxAllowedMem int
|
||||||
|
|
||||||
closeDone chan struct{}
|
closeDone chan struct{}
|
||||||
quit chan struct{}
|
quit chan struct{}
|
||||||
@@ -96,7 +100,7 @@ func (eng *EngineType) String() (e string) {
|
|||||||
// NewListener creates and initialize a new Listener. if transport or/and engine are invalid/unsupported
|
// NewListener creates and initialize a new Listener. if transport or/and engine are invalid/unsupported
|
||||||
// is "tcp" and "pcap", are assumed. l.Engine and l.Transport can help to get the values used.
|
// is "tcp" and "pcap", are assumed. l.Engine and l.Transport can help to get the values used.
|
||||||
// if there is an error it will be associated with getting network interfaces
|
// if there is an error it will be associated with getting network interfaces
|
||||||
func NewListener(host string, port uint16, transport string, engine EngineType, trackResponse bool) (l *Listener, err error) {
|
func NewListener(host string, port uint16, transport string, engine EngineType, trackResponse bool, maxAllowedMem int) (l *Listener, err error) {
|
||||||
l = &Listener{}
|
l = &Listener{}
|
||||||
|
|
||||||
l.host = host
|
l.host = host
|
||||||
@@ -105,11 +109,12 @@ func NewListener(host string, port uint16, transport string, engine EngineType,
|
|||||||
if transport != "" {
|
if transport != "" {
|
||||||
l.Transport = transport
|
l.Transport = transport
|
||||||
}
|
}
|
||||||
l.Handles = make(map[string]gopacket.PacketDataSource)
|
l.Handles = make(map[string]gopacket.ZeroCopyPacketDataSource)
|
||||||
l.trackResponse = trackResponse
|
l.trackResponse = trackResponse
|
||||||
l.closeDone = make(chan struct{})
|
l.closeDone = make(chan struct{})
|
||||||
l.quit = make(chan struct{})
|
l.quit = make(chan struct{})
|
||||||
l.Reading = make(chan bool)
|
l.Reading = make(chan bool)
|
||||||
|
l.maxAllowedMem = maxAllowedMem
|
||||||
switch engine {
|
switch engine {
|
||||||
default:
|
default:
|
||||||
l.Engine = EnginePcap
|
l.Engine = EnginePcap
|
||||||
@@ -138,7 +143,7 @@ func (l *Listener) SetPcapOptions(opts PcapOptions) {
|
|||||||
// Listen listens for packets from the handles, and call handler on every packet received
|
// Listen listens for packets from the handles, and call handler on every packet received
|
||||||
// until the context done signal is sent or there is unrecoverable error on all handles.
|
// until the context done signal is sent or there is unrecoverable error on all handles.
|
||||||
// this function must be called after activating pcap handles
|
// this function must be called after activating pcap handles
|
||||||
func (l *Listener) Listen(ctx context.Context, handler Handler) (err error) {
|
func (l *Listener) Listen(ctx context.Context, handler PacketHandler) (err error) {
|
||||||
l.read(handler)
|
l.read(handler)
|
||||||
done := ctx.Done()
|
done := ctx.Done()
|
||||||
select {
|
select {
|
||||||
@@ -152,7 +157,7 @@ func (l *Listener) Listen(ctx context.Context, handler Handler) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListenBackground is like listen but can run concurrently and signal error through channel
|
// ListenBackground is like listen but can run concurrently and signal error through channel
|
||||||
func (l *Listener) ListenBackground(ctx context.Context, handler Handler) chan error {
|
func (l *Listener) ListenBackground(ctx context.Context, handler PacketHandler) chan error {
|
||||||
err := make(chan error, 1)
|
err := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(err)
|
defer close(err)
|
||||||
@@ -172,34 +177,36 @@ func (l *Listener) Filter(ifi net.Interface) (filter string) {
|
|||||||
if l.port != 0 {
|
if l.port != 0 {
|
||||||
port = fmt.Sprintf("port %d", l.port)
|
port = fmt.Sprintf("port %d", l.port)
|
||||||
}
|
}
|
||||||
dir := " dst " // direction
|
filter = fmt.Sprintf("%s dst %s", l.Transport, port)
|
||||||
if l.trackResponse {
|
if l.trackResponse {
|
||||||
dir = " "
|
filter = fmt.Sprintf("%s %s", l.Transport, port)
|
||||||
}
|
}
|
||||||
filter = fmt.Sprintf("(%s%s%s)", l.Transport, dir, port)
|
|
||||||
if listenAll(l.host) || isDevice(l.host, ifi) {
|
if listenAll(l.host) || isDevice(l.host, ifi) {
|
||||||
return
|
return "(" + filter + ")"
|
||||||
}
|
}
|
||||||
filter = fmt.Sprintf("(%s%s%s and host %s)", l.Transport, dir, port, l.host)
|
filter = fmt.Sprintf("(host %s and (%s))", l.host, filter)
|
||||||
|
|
||||||
|
log.Println("BPF filter: " + filter)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// PcapDumpHandler returns a handler to write packet data in PCAP
|
// PcapDumpHandler returns a handler to write packet data in PCAP
|
||||||
// format, See http://wiki.wireshark.org/Development/LibpcapFileFormathandler.
|
// format, See http://wiki.wireshark.org/Development/LibpcapFileFormathandler.
|
||||||
// if link layer is invalid Ethernet is assumed
|
// if link layer is invalid Ethernet is assumed
|
||||||
func PcapDumpHandler(file *os.File, link layers.LinkType) (handler func(packet *Packet) error, err error) {
|
// func PcapDumpHandler(file *os.File, link layers.LinkType) (handler func(packet *tcp.Packet) error, err error) {
|
||||||
if link.String() == "" {
|
// if link.String() == "" {
|
||||||
link = layers.LinkTypeEthernet
|
// link = layers.LinkTypeEthernet
|
||||||
}
|
// }
|
||||||
w := NewWriterNanos(file)
|
// w := NewWriterNanos(file)
|
||||||
err = w.WriteFileHeader(64<<10, link)
|
// err = w.WriteFileHeader(64<<10, link)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
return func(packet *Packet) error {
|
// return func(packet *tcp.Packet) error {
|
||||||
return w.WritePacket(*packet.Info, packet.Data)
|
// return w.WritePacket(*packet.Info, packet.Data)
|
||||||
}, nil
|
// }, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// PcapHandle returns new pcap Handle from dev on success.
|
// PcapHandle returns new pcap Handle from dev on success.
|
||||||
// this function should be called after setting all necessary options for this listener
|
// this function should be called after setting all necessary options for this listener
|
||||||
@@ -262,6 +269,7 @@ func (l *Listener) PcapHandle(ifi net.Interface) (handle *pcap.Handle, err error
|
|||||||
} else {
|
} else {
|
||||||
l.BPFFilter = l.Filter(ifi)
|
l.BPFFilter = l.Filter(ifi)
|
||||||
}
|
}
|
||||||
|
fmt.Println("Interface:", ifi.Name, ". BPF Filter:", l.BPFFilter)
|
||||||
err = handle.SetBPFFilter(l.BPFFilter)
|
err = handle.SetBPFFilter(l.BPFFilter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handle.Close()
|
handle.Close()
|
||||||
@@ -286,6 +294,7 @@ func (l *Listener) SocketHandle(ifi net.Interface) (handle Socket, err error) {
|
|||||||
} else {
|
} else {
|
||||||
l.BPFFilter = l.Filter(ifi)
|
l.BPFFilter = l.Filter(ifi)
|
||||||
}
|
}
|
||||||
|
fmt.Println("BPF Filter: ", l.BPFFilter)
|
||||||
if err = handle.SetBPFFilter(l.BPFFilter); err != nil {
|
if err = handle.SetBPFFilter(l.BPFFilter); err != nil {
|
||||||
handle.Close()
|
handle.Close()
|
||||||
return nil, fmt.Errorf("BPF filter error: %q%s, interface: %q", err, l.BPFFilter, ifi.Name)
|
return nil, fmt.Errorf("BPF filter error: %q%s, interface: %q", err, l.BPFFilter, ifi.Name)
|
||||||
@@ -294,11 +303,11 @@ func (l *Listener) SocketHandle(ifi net.Interface) (handle Socket, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Listener) read(handler Handler) {
|
func (l *Listener) read(handler PacketHandler) {
|
||||||
l.Lock()
|
l.Lock()
|
||||||
defer l.Unlock()
|
defer l.Unlock()
|
||||||
for key, handle := range l.Handles {
|
for key, handle := range l.Handles {
|
||||||
go func(key string, hndl gopacket.PacketDataSource) {
|
go func(key string, hndl gopacket.ZeroCopyPacketDataSource) {
|
||||||
defer l.closeHandles(key)
|
defer l.closeHandles(key)
|
||||||
linkSize := 14
|
linkSize := 14
|
||||||
linkType := int(layers.LinkTypeEthernet)
|
linkType := int(layers.LinkTypeEthernet)
|
||||||
@@ -312,14 +321,28 @@ func (l *Listener) read(handler Handler) {
|
|||||||
return // can't find the linktype size
|
return // can't find the linktype size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-l.quit:
|
case <-l.quit:
|
||||||
return
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
if l.maxAllowedMem > 0 {
|
||||||
|
usedMemory, _ := memUsage()
|
||||||
|
if int(usedMemory) > l.maxAllowedMem {
|
||||||
|
log.Printf("[WARNING] Using more memory (%vmb) than allowed (%vmb). Enabling throttling for 1 second\n", bToMb(usedMemory), bToMb(uint64(l.maxAllowedMem)))
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
data, ci, err := hndl.ReadPacketData()
|
data, ci, err := hndl.ZeroCopyReadPacketData()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
handler(NewPacket(data, linkType, linkSize, &ci))
|
pckt, err := tcp.ParsePacket(data, linkType, linkSize, &ci)
|
||||||
|
if err == nil {
|
||||||
|
handler(pckt)
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if enext, ok := err.(pcap.NextError); ok && enext == pcap.NextErrorTimeoutExpired {
|
if enext, ok := err.(pcap.NextError); ok && enext == pcap.NextErrorTimeoutExpired {
|
||||||
@@ -332,11 +355,11 @@ func (l *Listener) read(handler Handler) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err == io.EOF || err == io.ErrClosedPipe {
|
if err == io.EOF || err == io.ErrClosedPipe {
|
||||||
|
log.Printf("stopped reading from %s interface with error %s\n", key, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if os.Getenv("GORDEBUG") != "0" {
|
|
||||||
log.Printf("stopped reading from %s interface with error %s\n", key, err)
|
log.Printf("stopped reading from %s interface with error %s\n", key, err)
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -478,3 +501,46 @@ func listenAll(addr string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pcapLinkTypeLength(lType int) (int, bool) {
|
||||||
|
switch layers.LinkType(lType) {
|
||||||
|
case layers.LinkTypeEthernet:
|
||||||
|
return 14, true
|
||||||
|
case layers.LinkTypeNull, layers.LinkTypeLoop:
|
||||||
|
return 4, true
|
||||||
|
case layers.LinkTypeRaw, 12, 14:
|
||||||
|
return 0, true
|
||||||
|
case layers.LinkTypeIPv4, layers.LinkTypeIPv6:
|
||||||
|
// (TODO:) look out for IP encapsulation?
|
||||||
|
return 0, true
|
||||||
|
case layers.LinkTypeLinuxSLL:
|
||||||
|
return 16, true
|
||||||
|
case layers.LinkTypeFDDI:
|
||||||
|
return 13, true
|
||||||
|
case 226 /*DLT_IPNET*/ :
|
||||||
|
// https://www.tcpdump.org/linktypes/LINKTYPE_IPNET.html
|
||||||
|
return 24, true
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func memUsage() (uint64, uint64) {
|
||||||
|
var m runtime.MemStats
|
||||||
|
runtime.ReadMemStats(&m)
|
||||||
|
// fmt.Printf("Current memory usage: ")
|
||||||
|
// // For info on each, see: https://golang.org/pkg/runtime/#MemStats
|
||||||
|
// fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc))
|
||||||
|
// fmt.Printf("\tTotalAlloc = %v MiB", bToMb(m.TotalAlloc))
|
||||||
|
// fmt.Printf("\tSys = %v MiB", bToMb(m.Sys))
|
||||||
|
// fmt.Printf("\tNumGC = %v\n", m.NumGC)
|
||||||
|
|
||||||
|
// fmt.Printf("Total available memory %v MiB\n", bToMb(memory.TotalMemory()))
|
||||||
|
// fmt.Printf("Currently using %.2f %% of memory\n", 100*float64(m.Alloc+m.Sys)/float64(memory.TotalMemory()))
|
||||||
|
|
||||||
|
return m.Alloc + m.Sys, memory.TotalMemory()
|
||||||
|
}
|
||||||
|
|
||||||
|
func bToMb(b uint64) uint64 {
|
||||||
|
return b / 1024 / 1024
|
||||||
|
}
|
||||||
|
|||||||
+20
-20
@@ -133,25 +133,25 @@ func TestPcapHandler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSocketHandler(t *testing.T) {
|
// func TestSocketHandler(t *testing.T) {
|
||||||
l, err := NewListener(LoopBack.Name, 8000, "", EngineRawSocket, true)
|
// l, err := NewListener(LoopBack.Name, 8000, "", EngineRawSocket, true)
|
||||||
err = l.Activate()
|
// err = l.Activate()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
defer l.Handles[LoopBack.Name].(*SockRaw).Close()
|
// defer l.Handles[LoopBack.Name].(*SockRaw).Close()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
t.Errorf("expected error to be nil, got %v", err)
|
// t.Errorf("expected error to be nil, got %v", err)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
for i := 0; i < 5; i++ {
|
// for i := 0; i < 5; i++ {
|
||||||
_, _ = net.Dial("tcp", "127.0.0.1:8000")
|
// _, _ = net.Dial("tcp", "127.0.0.1:8000")
|
||||||
}
|
// }
|
||||||
sts, _ := l.Handles[LoopBack.Name].(*SockRaw).Stats()
|
// sts, _ := l.Handles[LoopBack.Name].(*SockRaw).Stats()
|
||||||
if sts.Packets < 5 {
|
// if sts.Packets < 5 {
|
||||||
t.Errorf("expected >=5 packets got %d", sts.Packets)
|
// t.Errorf("expected >=5 packets got %d", sts.Packets)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func BenchmarkPcapDump(b *testing.B) {
|
func BenchmarkPcapDump(b *testing.B) {
|
||||||
f, err := ioutil.TempFile("", "pcap_file")
|
f, err := ioutil.TempFile("", "pcap_file")
|
||||||
@@ -220,7 +220,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handler(n, counter *int32) Handler {
|
func handler(n, counter *int32) PacketHandler {
|
||||||
return func(p *Packet) {
|
return func(p *Packet) {
|
||||||
nn := int32(len(p.Data))
|
nn := int32(len(p.Data))
|
||||||
atomic.AddInt32(n, nn)
|
atomic.AddInt32(n, nn)
|
||||||
|
|||||||
@@ -1,180 +0,0 @@
|
|||||||
package capture
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/google/gopacket"
|
|
||||||
"github.com/google/gopacket/layers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Packet properties of a pcaket
|
|
||||||
type Packet struct {
|
|
||||||
Data []byte
|
|
||||||
LinkLayer []byte
|
|
||||||
NetLayer []byte
|
|
||||||
NetOptsLen int // length of extension headers(IPV6) or options(IPV4)
|
|
||||||
TransLayer []byte
|
|
||||||
TransOptsLen int // length of tcp options
|
|
||||||
Payload []byte
|
|
||||||
|
|
||||||
LinkType int
|
|
||||||
Info *gopacket.CaptureInfo
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPacket parses packet and stop at the first error encountered
|
|
||||||
// pckt.Error will be nil if packet was parsed successfully
|
|
||||||
func NewPacket(data []byte, lType, lTypeLen int, cp *gopacket.CaptureInfo) (p *Packet) {
|
|
||||||
p = new(Packet)
|
|
||||||
p.Info = cp
|
|
||||||
p.Data = data
|
|
||||||
if len(data) < lTypeLen {
|
|
||||||
p.Err = ErrHdrLength("Link")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.LinkType = lType
|
|
||||||
p.LinkLayer = data[:lTypeLen]
|
|
||||||
if len(data) <= lTypeLen {
|
|
||||||
p.Err = ErrHdrMissing("IPv4 or IPv6")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data = data[lTypeLen:]
|
|
||||||
var proto byte
|
|
||||||
if data[0]>>4 == 4 {
|
|
||||||
// IPv4 header
|
|
||||||
if len(data) < 20 {
|
|
||||||
p.Err = ErrHdrLength("IPv4")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
proto = data[9]
|
|
||||||
ihl := int(data[0]&0x0F) * 4
|
|
||||||
if ihl < 20 {
|
|
||||||
p.Err = ErrHdrInvalid("IPv4's IHL")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(data) < ihl {
|
|
||||||
p.Err = ErrHdrLength("IPv4 opts")
|
|
||||||
p.NetLayer = data[:20]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.NetOptsLen = ihl - 20
|
|
||||||
p.NetLayer = data[:ihl]
|
|
||||||
} else if data[0]>>4 == 6 {
|
|
||||||
if len(data) < 40 {
|
|
||||||
p.Err = ErrHdrLength("IPv6")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
proto = data[6]
|
|
||||||
totalLen := 40
|
|
||||||
for ipv6ExtensionHdr(proto) {
|
|
||||||
hdr := len(data) - totalLen
|
|
||||||
if hdr < 8 {
|
|
||||||
p.Err = ErrHdrExpected("IPv6 opts")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
extLen := 8
|
|
||||||
if proto != 44 {
|
|
||||||
extLen = int(data[totalLen+1]+1) * 8
|
|
||||||
}
|
|
||||||
if hdr < extLen {
|
|
||||||
p.Err = ErrHdrLength("IPv6 opts")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
p.NetOptsLen += extLen
|
|
||||||
proto = data[totalLen]
|
|
||||||
totalLen += extLen
|
|
||||||
}
|
|
||||||
p.NetLayer = data[:totalLen]
|
|
||||||
if p.Err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
p.Err = ErrHdrExpected("IPv4 or IPv6")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if proto != 6 {
|
|
||||||
p.Err = ErrHdrExpected("TCP")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(data) <= len(p.NetLayer) {
|
|
||||||
p.Err = ErrHdrMissing("TCP")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data = data[len(p.NetLayer):]
|
|
||||||
// TCP header
|
|
||||||
if len(data) < 20 {
|
|
||||||
p.Err = ErrHdrLength("TCP")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dOf := int(data[12]>>4) * 4
|
|
||||||
if dOf < 20 {
|
|
||||||
p.Err = ErrHdrInvalid("TCP's data offset")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(data) < dOf {
|
|
||||||
p.Err = ErrHdrLength("TCP opts")
|
|
||||||
p.TransLayer = data[:20]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.TransLayer = data[:dOf]
|
|
||||||
p.TransOptsLen = dOf - 20
|
|
||||||
if len(data) > dOf {
|
|
||||||
p.Payload = data[dOf:]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrHdrLength returned on short header length
|
|
||||||
type ErrHdrLength string
|
|
||||||
|
|
||||||
func (err ErrHdrLength) Error() string {
|
|
||||||
return "short " + string(err) + " length"
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrHdrMissing returned on missing header(s)
|
|
||||||
type ErrHdrMissing string
|
|
||||||
|
|
||||||
func (err ErrHdrMissing) Error() string {
|
|
||||||
return "missing " + string(err) + " header(s)"
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrHdrExpected returned when header(s) are different from the one expected
|
|
||||||
type ErrHdrExpected string
|
|
||||||
|
|
||||||
func (err ErrHdrExpected) Error() string {
|
|
||||||
return "expected " + string(err) + " header(s)"
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrHdrInvalid returned when header(s) are different from the one expected
|
|
||||||
type ErrHdrInvalid string
|
|
||||||
|
|
||||||
func (err ErrHdrInvalid) Error() string {
|
|
||||||
return "invalid " + string(err) + " value"
|
|
||||||
}
|
|
||||||
|
|
||||||
func pcapLinkTypeLength(lType int) (int, bool) {
|
|
||||||
switch layers.LinkType(lType) {
|
|
||||||
case layers.LinkTypeEthernet:
|
|
||||||
return 14, true
|
|
||||||
case layers.LinkTypeNull, layers.LinkTypeLoop:
|
|
||||||
return 4, true
|
|
||||||
case layers.LinkTypeRaw, 12, 14:
|
|
||||||
return 0, true
|
|
||||||
case layers.LinkTypeIPv4, layers.LinkTypeIPv6:
|
|
||||||
// (TODO:) look out for IP encapsulation?
|
|
||||||
return 0, true
|
|
||||||
case layers.LinkTypeLinuxSLL:
|
|
||||||
return 16, true
|
|
||||||
case layers.LinkTypeFDDI:
|
|
||||||
return 13, true
|
|
||||||
case 226 /*DLT_IPNET*/ :
|
|
||||||
// https://www.tcpdump.org/linktypes/LINKTYPE_IPNET.html
|
|
||||||
return 24, true
|
|
||||||
default:
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://en.wikipedia.org/wiki/IPv6_packet#Extension_headers
|
|
||||||
func ipv6ExtensionHdr(b byte) bool {
|
|
||||||
// TODO: support all extension headers
|
|
||||||
return b == 0 || b == 43 || b == 44
|
|
||||||
}
|
|
||||||
@@ -108,7 +108,7 @@ func NewSocket(ifi net.Interface) (*SockRaw, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReadPacketData implements gopacket.PacketDataSource.
|
// ReadPacketData implements gopacket.PacketDataSource.
|
||||||
func (sock *SockRaw) ReadPacketData() (buf []byte, ci gopacket.CaptureInfo, err error) {
|
func (sock *SockRaw) ZeroCopyReadPacketData() (buf []byte, ci gopacket.CaptureInfo, err error) {
|
||||||
sock.mu.Lock()
|
sock.mu.Lock()
|
||||||
defer sock.mu.Unlock()
|
defer sock.mu.Unlock()
|
||||||
var tpHdr *unix.Tpacket2Hdr
|
var tpHdr *unix.Tpacket2Hdr
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
// Socket is any interface that defines the behaviors of Socket
|
// Socket is any interface that defines the behaviors of Socket
|
||||||
type Socket interface {
|
type Socket interface {
|
||||||
ReadPacketData() ([]byte, gopacket.CaptureInfo, error)
|
ZeroCopyReadPacketData() ([]byte, gopacket.CaptureInfo, error)
|
||||||
WritePacketData([]byte) error
|
WritePacketData([]byte) error
|
||||||
SetBPFFilter(string) error
|
SetBPFFilter(string) error
|
||||||
SetPromiscuous(bool) error
|
SetPromiscuous(bool) error
|
||||||
|
|||||||
+27
@@ -5,10 +5,13 @@ import (
|
|||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/buger/goreplay/byteutils"
|
"github.com/buger/goreplay/byteutils"
|
||||||
|
|
||||||
|
"github.com/pbnjay/memory"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Emitter represents an abject to manage plugins communication
|
// Emitter represents an abject to manage plugins communication
|
||||||
@@ -60,6 +63,7 @@ func (e *Emitter) Start(plugins *InOutPlugins, middlewareCmd string) {
|
|||||||
|
|
||||||
// Close closes all the goroutine and waits for it to finish.
|
// Close closes all the goroutine and waits for it to finish.
|
||||||
func (e *Emitter) Close() {
|
func (e *Emitter) Close() {
|
||||||
|
fmt.Println("Closing!")
|
||||||
for _, p := range e.plugins.All {
|
for _, p := range e.plugins.All {
|
||||||
if cp, ok := p.(io.Closer); ok {
|
if cp, ok := p.(io.Closer); ok {
|
||||||
cp.Close()
|
cp.Close()
|
||||||
@@ -69,6 +73,8 @@ func (e *Emitter) Close() {
|
|||||||
// wait for everything to stop
|
// wait for everything to stop
|
||||||
e.Wait()
|
e.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println("Closed!")
|
||||||
e.plugins.All = nil // avoid Close to make changes again
|
e.plugins.All = nil // avoid Close to make changes again
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +94,7 @@ func CopyMulty(src PluginReader, writers ...PluginWriter) error {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg != nil && len(msg.Data) > 0 {
|
if msg != nil && len(msg.Data) > 0 {
|
||||||
if len(msg.Data) > int(Settings.CopyBufferSize) {
|
if len(msg.Data) > int(Settings.CopyBufferSize) {
|
||||||
msg.Data = msg.Data[:Settings.CopyBufferSize]
|
msg.Data = msg.Data[:Settings.CopyBufferSize]
|
||||||
@@ -175,3 +182,23 @@ func CopyMulty(src PluginReader, writers ...PluginWriter) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func memUsage() (uint64, uint64) {
|
||||||
|
var m runtime.MemStats
|
||||||
|
runtime.ReadMemStats(&m)
|
||||||
|
// fmt.Printf("Current memory usage: ")
|
||||||
|
// // For info on each, see: https://golang.org/pkg/runtime/#MemStats
|
||||||
|
// fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc))
|
||||||
|
// fmt.Printf("\tTotalAlloc = %v MiB", bToMb(m.TotalAlloc))
|
||||||
|
// fmt.Printf("\tSys = %v MiB", bToMb(m.Sys))
|
||||||
|
// fmt.Printf("\tNumGC = %v\n", m.NumGC)
|
||||||
|
|
||||||
|
// fmt.Printf("Total available memory %v MiB\n", bToMb(memory.TotalMemory()))
|
||||||
|
// fmt.Printf("Currently using %.2f %% of memory\n", 100*float64(m.Alloc+m.Sys)/float64(memory.TotalMemory()))
|
||||||
|
|
||||||
|
return m.Alloc + m.Sys, memory.TotalMemory()
|
||||||
|
}
|
||||||
|
|
||||||
|
func bToMb(b uint64) uint64 {
|
||||||
|
return b / 1024 / 1024
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ require (
|
|||||||
github.com/google/gopacket v1.1.18
|
github.com/google/gopacket v1.1.18
|
||||||
github.com/klauspost/compress v1.10.10 // indirect
|
github.com/klauspost/compress v1.10.10 // indirect
|
||||||
github.com/mattbaird/elastigo v0.0.0-20170123220020-2fe47fd29e4b
|
github.com/mattbaird/elastigo v0.0.0-20170123220020-2fe47fd29e4b
|
||||||
|
github.com/pbnjay/memory v0.0.0-20201129165224-b12e5d931931
|
||||||
github.com/pierrec/lz4 v2.5.2+incompatible // indirect
|
github.com/pierrec/lz4 v2.5.2+incompatible // indirect
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
|
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
|
||||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||||
|
github.com/stretchr/testify v1.5.1
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ github.com/bitly/go-hostpool v0.1.0 h1:XKmsF6k5el6xHG3WPJ8U0Ku/ye7njX7W81Ng7O2io
|
|||||||
github.com/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENUpMkpg42fw=
|
github.com/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENUpMkpg42fw=
|
||||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
|
||||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -42,7 +41,6 @@ github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2
|
|||||||
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/klauspost/compress v1.9.8 h1:VMAMUUOh+gaxKTMk+zqbjsSjsIcUcL/LF4o63i82QyA=
|
|
||||||
github.com/klauspost/compress v1.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
github.com/klauspost/compress v1.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I=
|
github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I=
|
||||||
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
@@ -54,6 +52,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/mattbaird/elastigo v0.0.0-20170123220020-2fe47fd29e4b h1:v29yPGHhOqw7VHEnTeQFAth3SsBrmwc8JfuhNY0G34k=
|
github.com/mattbaird/elastigo v0.0.0-20170123220020-2fe47fd29e4b h1:v29yPGHhOqw7VHEnTeQFAth3SsBrmwc8JfuhNY0G34k=
|
||||||
github.com/mattbaird/elastigo v0.0.0-20170123220020-2fe47fd29e4b/go.mod h1:5MWrJXKRQyhQdUCF+vu6U5c4nQpg70vW3eHaU0/AYbU=
|
github.com/mattbaird/elastigo v0.0.0-20170123220020-2fe47fd29e4b/go.mod h1:5MWrJXKRQyhQdUCF+vu6U5c4nQpg70vW3eHaU0/AYbU=
|
||||||
|
github.com/pbnjay/memory v0.0.0-20201129165224-b12e5d931931 h1:EeWknjeRU+R3O4ghG7XZCpgSfJNStZyEP8aWyQwJM8s=
|
||||||
|
github.com/pbnjay/memory v0.0.0-20201129165224-b12e5d931931/go.mod h1:RMU2gJXhratVxBDTFeOdNhd540tG57lt9FIUV0YLvIQ=
|
||||||
github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI=
|
github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI=
|
||||||
github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
@@ -74,13 +74,11 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
|||||||
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
|
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
|
||||||
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
|
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 h1:+ELyKg6m8UBf0nPFSqD0mi7zUfwPyXo23HNjMnXPz7w=
|
|
||||||
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
|
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
@@ -89,7 +87,6 @@ golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
@@ -97,7 +94,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
|||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@@ -111,7 +107,6 @@ gopkg.in/jcmturner/gokrb5.v7 v7.5.0 h1:a9tsXlIDD9SKxotJMK3niV7rPZAJeX2aD/0yg3qlI
|
|||||||
gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
|
gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
|
||||||
gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU=
|
gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU=
|
||||||
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
|
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
emitter.Close()
|
emitter.Close()
|
||||||
os.Exit(exit)
|
os.Exit(exit)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func profileCPU(cpuprofile string) {
|
func profileCPU(cpuprofile string) {
|
||||||
|
|||||||
+24
-14
@@ -114,7 +114,7 @@ func (i *RAWInput) PluginRead() (*Message, error) {
|
|||||||
msg.Data = msgTCP.Data()
|
msg.Data = msgTCP.Data()
|
||||||
}
|
}
|
||||||
var msgType byte = ResponsePayload
|
var msgType byte = ResponsePayload
|
||||||
if msgTCP.IsIncoming {
|
if msgTCP.IsRequest {
|
||||||
msgType = RequestPayload
|
msgType = RequestPayload
|
||||||
if i.RealIPHeader != "" {
|
if i.RealIPHeader != "" {
|
||||||
msg.Data = proto.SetHeader(msg.Data, []byte(i.RealIPHeader), []byte(msgTCP.SrcAddr))
|
msg.Data = proto.SetHeader(msg.Data, []byte(i.RealIPHeader), []byte(msgTCP.SrcAddr))
|
||||||
@@ -134,13 +134,14 @@ func (i *RAWInput) PluginRead() (*Message, error) {
|
|||||||
stat := msgTCP.Stats
|
stat := msgTCP.Stats
|
||||||
go i.addStats(stat)
|
go i.addStats(stat)
|
||||||
}
|
}
|
||||||
|
msgTCP.Finalize()
|
||||||
msgTCP = nil
|
msgTCP = nil
|
||||||
return &msg, nil
|
return &msg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *RAWInput) listen(address string) {
|
func (i *RAWInput) listen(address string) {
|
||||||
var err error
|
var err error
|
||||||
i.listener, err = capture.NewListener(i.host, i.port, "", i.Engine, i.TrackResponse)
|
i.listener, err = capture.NewListener(i.host, i.port, "", i.Engine, i.TrackResponse, Settings.maxAllowedMem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -149,15 +150,15 @@ func (i *RAWInput) listen(address string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
pool := tcp.NewMessagePool(i.CopyBufferSize, i.Expire, Debug, i.handler)
|
parser := tcp.NewMessageParser(i.CopyBufferSize, i.Expire, Debug, i.messageEmitter)
|
||||||
pool.MatchUUID(i.TrackResponse)
|
|
||||||
if i.Protocol == ProtocolHTTP {
|
if i.Protocol == ProtocolHTTP {
|
||||||
pool.Start = http1StartHint
|
parser.Start = http1StartHint
|
||||||
pool.End = http1EndHint
|
parser.End = http1EndHint
|
||||||
}
|
}
|
||||||
var ctx context.Context
|
var ctx context.Context
|
||||||
ctx, i.cancelListener = context.WithCancel(context.Background())
|
ctx, i.cancelListener = context.WithCancel(context.Background())
|
||||||
errCh := i.listener.ListenBackground(ctx, pool.Handler)
|
errCh := i.listener.ListenBackground(ctx, parser.PacketHandler)
|
||||||
<-i.listener.Reading
|
<-i.listener.Reading
|
||||||
Debug(1, i)
|
Debug(1, i)
|
||||||
go func() {
|
go func() {
|
||||||
@@ -166,7 +167,7 @@ func (i *RAWInput) listen(address string) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *RAWInput) handler(m *tcp.Message) {
|
func (i *RAWInput) messageEmitter(m *tcp.Message) {
|
||||||
i.message <- m
|
i.message <- m
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,14 +207,23 @@ func (i *RAWInput) addStats(mStats tcp.Stats) {
|
|||||||
i.Unlock()
|
i.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func http1StartHint(pckt *tcp.Packet) (isIncoming, isOutgoing bool) {
|
func http1StartHint(pckt *tcp.Packet) (isRequest, isResponse bool) {
|
||||||
isIncoming = proto.HasRequestTitle(pckt.Payload)
|
if proto.HasRequestTitle(pckt.Payload) {
|
||||||
if isIncoming {
|
return true, false
|
||||||
return
|
|
||||||
}
|
}
|
||||||
return false, proto.HasResponseTitle(pckt.Payload)
|
|
||||||
|
if proto.HasResponseTitle(pckt.Payload) {
|
||||||
|
return false, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// No request or response detected
|
||||||
|
return false, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func http1EndHint(m *tcp.Message) bool {
|
func http1EndHint(m *tcp.Message) bool {
|
||||||
return proto.HasFullPayload(m.Data(), m)
|
if m.MissingChunk() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return proto.HasFullPayload(m, m.PacketData()...)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -300,7 +300,7 @@ func (c *HTTPClient) Send(data []byte) ([]byte, error) {
|
|||||||
if !c.config.OriginalHost {
|
if !c.config.OriginalHost {
|
||||||
req.Host = c.config.url.Host
|
req.Host = c.config.url.Host
|
||||||
}
|
}
|
||||||
|
|
||||||
// fix #862
|
// fix #862
|
||||||
if c.config.url.Path == "" && c.config.url.RawQuery == "" {
|
if c.config.url.Path == "" && c.config.url.RawQuery == "" {
|
||||||
req.URL.Scheme = c.config.url.Scheme
|
req.URL.Scheme = c.config.url.Scheme
|
||||||
@@ -308,7 +308,7 @@ func (c *HTTPClient) Send(data []byte) ([]byte, error) {
|
|||||||
} else {
|
} else {
|
||||||
req.URL = c.config.url
|
req.URL = c.config.url
|
||||||
}
|
}
|
||||||
|
|
||||||
// force connection to not be closed, which can affect the global client
|
// force connection to not be closed, which can affect the global client
|
||||||
req.Close = false
|
req.Close = false
|
||||||
// it's an error if this is not equal to empty string
|
// it's an error if this is not equal to empty string
|
||||||
|
|||||||
+20
-2
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
@@ -18,6 +19,8 @@ type TCPOutput struct {
|
|||||||
bufStats *GorStat
|
bufStats *GorStat
|
||||||
config *TCPOutputConfig
|
config *TCPOutputConfig
|
||||||
workerIndex uint32
|
workerIndex uint32
|
||||||
|
|
||||||
|
close bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// TCPOutputConfig tcp output configuration
|
// TCPOutputConfig tcp output configuration
|
||||||
@@ -54,6 +57,10 @@ func (o *TCPOutput) worker(bufferIndex int) {
|
|||||||
retries := 0
|
retries := 0
|
||||||
conn, err := o.connect(o.address)
|
conn, err := o.connect(o.address)
|
||||||
for {
|
for {
|
||||||
|
if o.close {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -118,9 +125,16 @@ func (o *TCPOutput) PluginWrite(msg *Message) (n int, err error) {
|
|||||||
|
|
||||||
func (o *TCPOutput) connect(address string) (conn net.Conn, err error) {
|
func (o *TCPOutput) connect(address string) (conn net.Conn, err error) {
|
||||||
if o.config.Secure {
|
if o.config.Secure {
|
||||||
conn, err = tls.Dial("tcp", address, &tls.Config{InsecureSkipVerify: o.config.SkipVerify})
|
var d tls.Dialer
|
||||||
|
d.Config = &tls.Config{InsecureSkipVerify: o.config.SkipVerify}
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
conn, err = d.DialContext(ctx, "tcp", address)
|
||||||
} else {
|
} else {
|
||||||
conn, err = net.Dial("tcp", address)
|
var d net.Dialer
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
conn, err = d.DialContext(ctx, "tcp", address)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -129,3 +143,7 @@ func (o *TCPOutput) connect(address string) (conn net.Conn, err error) {
|
|||||||
func (o *TCPOutput) String() string {
|
func (o *TCPOutput) String() string {
|
||||||
return fmt.Sprintf("TCP output %s, limit: %d", o.address, o.limit)
|
return fmt.Sprintf("TCP output %s, limit: %d", o.address, o.limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *TCPOutput) Close() {
|
||||||
|
o.close = true
|
||||||
|
}
|
||||||
|
|||||||
+143
-106
@@ -58,10 +58,15 @@ func MIMEHeadersStartPos(payload []byte) int {
|
|||||||
// If not found, value will be blank, and headerStart will be -1
|
// If not found, value will be blank, and headerStart will be -1
|
||||||
// Do not support multi-line headers.
|
// Do not support multi-line headers.
|
||||||
func header(payload []byte, name []byte) (value []byte, headerStart, headerEnd, valueStart, valueEnd int) {
|
func header(payload []byte, name []byte) (value []byte, headerStart, headerEnd, valueStart, valueEnd int) {
|
||||||
headerStart = MIMEHeadersStartPos(payload)
|
if HasTitle(payload) {
|
||||||
if headerStart < 0 {
|
headerStart = MIMEHeadersStartPos(payload)
|
||||||
return
|
if headerStart < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
headerStart = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
var colonIndex int
|
var colonIndex int
|
||||||
for headerStart < len(payload) {
|
for headerStart < len(payload) {
|
||||||
headerEnd = bytes.IndexByte(payload[headerStart:], '\n')
|
headerEnd = bytes.IndexByte(payload[headerStart:], '\n')
|
||||||
@@ -398,140 +403,172 @@ func HasTitle(payload []byte) bool {
|
|||||||
return HasRequestTitle(payload) || HasResponseTitle(payload)
|
return HasRequestTitle(payload) || HasResponseTitle(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckChunked checks HTTP/1 chunked data integrity and return the final index
|
// CheckChunked checks HTTP/1 chunked data integrity(https://tools.ietf.org/html/rfc7230#section-4.1)
|
||||||
// of chunks(index after '0\r\n\r\n') or -1 if there is missing data
|
// and returns the length of total valid scanned chunks(including chunk size, extensions and CRLFs) and
|
||||||
// or there is bad format
|
// full is true if all chunks was scanned.
|
||||||
func CheckChunked(buf []byte) (chunkEnd int) {
|
func CheckChunked(bufs ...[]byte) (chunkEnd int, full bool) {
|
||||||
var (
|
var buf []byte
|
||||||
ok bool
|
if len(bufs) > 0 {
|
||||||
chkLen int
|
buf = bufs[0]
|
||||||
sz int
|
}
|
||||||
ext int
|
for chunkEnd < len(buf) {
|
||||||
)
|
sz := bytes.IndexByte(buf[chunkEnd:], '\r')
|
||||||
for {
|
|
||||||
sz = bytes.IndexByte(buf[chunkEnd:], '\r')
|
|
||||||
if sz < 1 {
|
if sz < 1 {
|
||||||
return -1
|
break
|
||||||
}
|
}
|
||||||
// ignoring chunks extensions https://github.com/golang/go/issues/13135
|
// don't parse chunk extensions https://github.com/golang/go/issues/13135.
|
||||||
// but chunks extensions are no longer a thing
|
// chunks extensions are no longer a thing, but we do check if the byte
|
||||||
ext = bytes.IndexByte(buf[chunkEnd:chunkEnd+sz], ';')
|
// following the parsed hex number is ';'
|
||||||
if ext < 0 {
|
sz += chunkEnd
|
||||||
ext = sz
|
chkLen, ok := atoI(buf[chunkEnd:sz], 16)
|
||||||
|
if !ok && bytes.IndexByte(buf[chunkEnd:sz], ';') < 1 {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
chkLen, ok = atoI(buf[chunkEnd:chunkEnd+ext], 16)
|
sz++ // + '\n'
|
||||||
if !ok {
|
// total length = SIZE + CRLF + OCTETS + CRLF
|
||||||
return -1
|
allChunk := sz + chkLen + 2
|
||||||
|
if allChunk >= len(buf) ||
|
||||||
|
buf[sz]&buf[allChunk] != '\n' ||
|
||||||
|
buf[allChunk-1] != '\r' {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
chunkEnd += (sz + 2)
|
chunkEnd = allChunk + 1
|
||||||
if chkLen == 0 {
|
if chkLen == 0 {
|
||||||
if !bytes.Equal(buf[chunkEnd:chunkEnd+2], CRLF) {
|
full = true
|
||||||
return -1
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProtocolStateSetter is an interface used to provide protocol state for future use
|
||||||
|
type ProtocolStateSetter interface {
|
||||||
|
SetProtocolState(interface{})
|
||||||
|
ProtocolState() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpProto struct {
|
||||||
|
body int // body index
|
||||||
|
headerStart int
|
||||||
|
headerParsed bool // we checked necessary headers
|
||||||
|
hasFullBody bool // all chunks has been parsed
|
||||||
|
isChunked bool // Transfer-Encoding: chunked
|
||||||
|
bodyLen int // Content-Length's value
|
||||||
|
hasTrailer bool // Trailer header?
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasFullPayload checks if this message has full or valid payloads and returns true.
|
||||||
|
// Message param is optional but recommended on cases where 'data' is storing
|
||||||
|
// partial-to-full stream of bytes(packets).
|
||||||
|
func HasFullPayload(m ProtocolStateSetter, payloads ...[]byte) bool {
|
||||||
|
var state *httpProto
|
||||||
|
if m != nil {
|
||||||
|
state, _ = m.ProtocolState().(*httpProto)
|
||||||
|
}
|
||||||
|
if state == nil {
|
||||||
|
state = new(httpProto)
|
||||||
|
if m != nil {
|
||||||
|
m.SetProtocolState(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if state.headerStart < 1 {
|
||||||
|
for _, data := range payloads {
|
||||||
|
state.headerStart = MIMEHeadersStartPos(data)
|
||||||
|
if state.headerStart < 0 {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
return chunkEnd + 2
|
|
||||||
}
|
}
|
||||||
// ideally chunck length and at least len("\r\n0\r\n\r\n")
|
|
||||||
if len(buf[chunkEnd:]) < chkLen+7 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
chunkEnd += chkLen
|
|
||||||
// chunks must end with CRLF
|
|
||||||
if !bytes.Equal(buf[chunkEnd:chunkEnd+2], CRLF) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
chunkEnd += 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Feedback is an interface used to provide feedback or store dummy data for future use
|
|
||||||
type Feedback interface {
|
|
||||||
SetFeedback(interface{})
|
|
||||||
Feedback() interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type feedback struct {
|
|
||||||
body int // body index
|
|
||||||
hdrStart int
|
|
||||||
headers textproto.MIMEHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasFullPayload reports if this http has full payloads
|
|
||||||
func HasFullPayload(data []byte, f Feedback) bool {
|
|
||||||
var feed *feedback
|
|
||||||
var ok bool
|
|
||||||
var body []byte
|
|
||||||
if f != nil {
|
|
||||||
feed, ok = f.Feedback().(*feedback)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
feed = new(feedback)
|
|
||||||
}
|
|
||||||
if f != nil {
|
|
||||||
f.SetFeedback(feed)
|
|
||||||
}
|
|
||||||
if feed.hdrStart < 1 {
|
|
||||||
feed.hdrStart = MIMEHeadersStartPos(data)
|
|
||||||
if feed.hdrStart < 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if feed.body < 1 {
|
|
||||||
feed.body = MIMEHeadersEndPos(data)
|
|
||||||
if feed.body < 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if feed.headers == nil {
|
|
||||||
feed.headers = GetHeaders(data[feed.hdrStart:feed.body])
|
|
||||||
if feed.headers == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(data) > feed.body {
|
|
||||||
body = data[feed.body:]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if feed.headers.Get("Transfer-Encoding") == "chunked" {
|
if state.body < 1 {
|
||||||
|
var pos int
|
||||||
|
for _, data := range payloads {
|
||||||
|
endPos := MIMEHeadersEndPos(data)
|
||||||
|
if endPos < 0 {
|
||||||
|
pos += len(data)
|
||||||
|
} else {
|
||||||
|
pos += endPos
|
||||||
|
}
|
||||||
|
|
||||||
|
if endPos > 0 {
|
||||||
|
state.body = pos
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !state.headerParsed {
|
||||||
|
var pos int
|
||||||
|
for _, data := range payloads {
|
||||||
|
chunked := Header(data, []byte("Transfer-Encoding"))
|
||||||
|
|
||||||
|
if len(chunked) > 0 && bytes.Index(data, []byte("chunked")) > 0 {
|
||||||
|
state.isChunked = true
|
||||||
|
// trailers are generally not allowed in non-chunks body
|
||||||
|
state.hasTrailer = len(Header(data, []byte("Trailer"))) > 0
|
||||||
|
} else {
|
||||||
|
contentLen := Header(data, []byte("Content-Length"))
|
||||||
|
state.bodyLen, _ = atoI(contentLen, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += len(data)
|
||||||
|
|
||||||
|
if state.bodyLen > 0 || pos >= state.body {
|
||||||
|
state.headerParsed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyLen := 0
|
||||||
|
for _, data := range payloads {
|
||||||
|
bodyLen += len(data)
|
||||||
|
}
|
||||||
|
bodyLen -= state.body
|
||||||
|
|
||||||
|
if state.isChunked {
|
||||||
// check chunks
|
// check chunks
|
||||||
if len(body) < 1 {
|
if bodyLen < 1 {
|
||||||
return false
|
|
||||||
}
|
|
||||||
var chunkEnd int
|
|
||||||
if chunkEnd = CheckChunked(body); chunkEnd < 1 {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// check trailer headers
|
// check trailer headers
|
||||||
if feed.headers.Get("Trailer") == "" {
|
if state.hasTrailer {
|
||||||
return true
|
if bytes.HasSuffix(payloads[len(payloads)-1], []byte("\r\n\r\n")) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if bytes.HasSuffix(payloads[len(payloads)-1], []byte("0\r\n\r\n")) {
|
||||||
|
state.hasFullBody = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// trailer headers(whether chunked or plain) should end with empty line
|
|
||||||
return len(body) > chunkEnd && MIMEHeadersEndPos(body[chunkEnd:]) != -1
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for content-length header
|
// check for content-length header
|
||||||
if header := feed.headers.Get("Content-Length"); header != "" {
|
return state.bodyLen == bodyLen
|
||||||
num, ok := atoI([]byte(header), 10)
|
|
||||||
// trailers are generally not allowed in non-chunks body
|
|
||||||
return ok && num == len(body)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// this works with positive integers
|
// this works with positive integers
|
||||||
func atoI(s []byte, base int) (num int, ok bool) {
|
func atoI(s []byte, base int) (num int, ok bool) {
|
||||||
var v int
|
var v int
|
||||||
|
ok = true
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
if s[i] > 127 {
|
if s[i] > 127 {
|
||||||
return 0, false
|
ok = false
|
||||||
|
break
|
||||||
}
|
}
|
||||||
v = int(hexTable[s[i]])
|
v = int(hexTable[s[i]])
|
||||||
if v >= base || (v == 0 && s[i] != '0') {
|
if v >= base || (v == 0 && s[i] != '0') {
|
||||||
return 0, false
|
ok = false
|
||||||
|
break
|
||||||
}
|
}
|
||||||
num = (num * base) + v
|
num = (num * base) + v
|
||||||
}
|
}
|
||||||
return num, true
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var hexTable = [128]byte{
|
var hexTable = [128]byte{
|
||||||
|
|||||||
+27
-38
@@ -388,63 +388,61 @@ func TestHasRequestTitle(t *testing.T) {
|
|||||||
|
|
||||||
func TestCheckChunks(t *testing.T) {
|
func TestCheckChunks(t *testing.T) {
|
||||||
var m = "4\r\nWiki\r\n5\r\npedia\r\nE\r\n in\r\n\r\nchunks.\r\n0\r\n\r\n"
|
var m = "4\r\nWiki\r\n5\r\npedia\r\nE\r\n in\r\n\r\nchunks.\r\n0\r\n\r\n"
|
||||||
chunkEnd := CheckChunked([]byte(m))
|
chunkEnd, _ := CheckChunked([]byte(m))
|
||||||
expected := bytes.Index([]byte(m), []byte("0\r\n")) + 5
|
expected := len(m)
|
||||||
if chunkEnd != expected {
|
if chunkEnd != expected {
|
||||||
t.Errorf("expected %d to equal %d", chunkEnd, expected)
|
t.Errorf("expected %d to equal %d", chunkEnd, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
m = "7\r\nMozia\r\n9\r\nDeveloper\r\n7\r\nNetwork\r\n0\r\n\r\n"
|
m = "7\r\nMozia\r\n9\r\nDeveloper\r\n7\r\nNetwork\r\n0\r\n\r\n"
|
||||||
chunkEnd = CheckChunked([]byte(m))
|
chunkEnd, _ = CheckChunked([]byte(m))
|
||||||
if chunkEnd != -1 {
|
if chunkEnd != 0 {
|
||||||
t.Errorf("expected %d to equal %d", chunkEnd, -1)
|
t.Errorf("expected %d to equal %d", chunkEnd, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// with trailers
|
// with trailers
|
||||||
m = "4\r\nWiki\r\n5\r\npedia\r\nE\r\n in\r\n\r\nchunks.\r\n0\r\n\r\nEXpires"
|
m = "4\r\nWiki\r\n5\r\npedia\r\nE\r\n in\r\n\r\nchunks.\r\n0\r\n\r\nEXpires"
|
||||||
chunkEnd = CheckChunked([]byte(m))
|
chunkEnd, _ = CheckChunked([]byte(m))
|
||||||
expected = bytes.Index([]byte(m), []byte("0\r\n")) + 5
|
expected = len(m) - 7
|
||||||
if chunkEnd != expected {
|
if chunkEnd != expected {
|
||||||
t.Errorf("expected %d to equal %d", chunkEnd, expected)
|
t.Errorf("expected %d to equal %d", chunkEnd, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
// last chunk inside the the body
|
// last chunk inside the body
|
||||||
// with trailers
|
// with trailers
|
||||||
m = "4\r\nWiki\r\n5\r\npedia\r\nE\r\n in\r\n\r\nchunks.\r\n3\r\n0\r\n\r\n0\r\n\r\nEXpires"
|
m = "4\r\nWiki\r\n5\r\npedia\r\nE\r\n in\r\n\r\nchunks.\r\n3\r\n0\r\n\r\n0\r\n\r\nEXpires"
|
||||||
chunkEnd = CheckChunked([]byte(m))
|
chunkEnd, _ = CheckChunked([]byte(m))
|
||||||
expected = bytes.Index([]byte(m), []byte("0\r\n")) + 10
|
expected = len(m) - 7
|
||||||
if chunkEnd != expected {
|
if chunkEnd != expected {
|
||||||
t.Errorf("expected %d to equal %d", chunkEnd, expected)
|
t.Errorf("expected %d to equal %d", chunkEnd, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
// checks with chucks-extensions
|
// checks with chucks-extensions
|
||||||
m = "4\r\nWiki\r\n5\r\npedia\r\nE; name='quoted string'\r\n in\r\n\r\nchunks.\r\n3\r\n0\r\n\r\n0\r\n\r\nEXpires"
|
m = "4\r\nWiki\r\n5\r\npedia\r\nE; name='quoted string'\r\n in\r\n\r\nchunks.\r\n3\r\n0\r\n\r\n0\r\n\r\nEXpires"
|
||||||
chunkEnd = CheckChunked([]byte(m))
|
chunkEnd, _ = CheckChunked([]byte(m))
|
||||||
expected = bytes.Index([]byte(m), []byte("0\r\n")) + 10
|
expected = len(m) - 7
|
||||||
if chunkEnd != expected {
|
if chunkEnd != expected {
|
||||||
t.Errorf("expected %d to equal %d", chunkEnd, expected)
|
t.Errorf("expected %d to equal %d", chunkEnd, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHasFullPayload(t *testing.T) {
|
func TestHasFullPayload(t *testing.T) {
|
||||||
var m = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n7\r\nMozilla\r\n9\r\nDeveloper\r\n7\r\nNetwork\r\n0\r\n\r\n"
|
var m string
|
||||||
got := HasFullPayload([]byte(m), nil)
|
var got, expected bool
|
||||||
expected := true
|
|
||||||
if got != expected {
|
|
||||||
t.Errorf("expected %v to equal %v", got, expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check with invalid chunk format
|
got = HasFullPayload(nil,
|
||||||
m = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n7\r\nMozia\r\n9\r\nDeveloper\r\n7\r\nNetwork\r\n0\r\n\r\n"
|
[]byte("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n"),
|
||||||
got = HasFullPayload([]byte(m), nil)
|
[]byte("Transfer-Encoding: chunked\r\n\r\n"),
|
||||||
expected = false
|
[]byte("7\r\nMozilla\r\n9\r\nDeveloper\r\n"),
|
||||||
|
[]byte("7\r\nNetwork\r\n0\r\n\r\n"))
|
||||||
|
expected = true
|
||||||
if got != expected {
|
if got != expected {
|
||||||
t.Errorf("expected %v to equal %v", got, expected)
|
t.Errorf("expected %v to equal %v", got, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check chunks with trailers
|
// check chunks with trailers
|
||||||
m = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\nTrailer: Expires\r\n\r\n7\r\nMozilla\r\n9\r\nDeveloper\r\n7\r\nNetwork\r\n0\r\n\r\nExpires: Wed, 21 Oct 2015 07:28:00 GMT\r\n\r\n"
|
m = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\nTrailer: Expires\r\n\r\n7\r\nMozilla\r\n9\r\nDeveloper\r\n7\r\nNetwork\r\n0\r\n\r\nExpires: Wed, 21 Oct 2015 07:28:00 GMT\r\n\r\n"
|
||||||
got = HasFullPayload([]byte(m), nil)
|
got = HasFullPayload(nil, []byte(m))
|
||||||
expected = true
|
expected = true
|
||||||
if got != expected {
|
if got != expected {
|
||||||
t.Errorf("expected %v to equal %v", got, expected)
|
t.Errorf("expected %v to equal %v", got, expected)
|
||||||
@@ -452,7 +450,7 @@ func TestHasFullPayload(t *testing.T) {
|
|||||||
|
|
||||||
// check with missing trailers
|
// check with missing trailers
|
||||||
m = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\nTrailer: Expires\r\n\r\n7\r\nMozilla\r\n9\r\nDeveloper\r\n7\r\nNetwork\r\n0\r\n\r\nExpires: Wed, 21 Oct 2015 07:28:00"
|
m = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\nTrailer: Expires\r\n\r\n7\r\nMozilla\r\n9\r\nDeveloper\r\n7\r\nNetwork\r\n0\r\n\r\nExpires: Wed, 21 Oct 2015 07:28:00"
|
||||||
got = HasFullPayload([]byte(m), nil)
|
got = HasFullPayload(nil, []byte(m))
|
||||||
expected = false
|
expected = false
|
||||||
if got != expected {
|
if got != expected {
|
||||||
t.Errorf("expected %v to equal %v", got, expected)
|
t.Errorf("expected %v to equal %v", got, expected)
|
||||||
@@ -460,7 +458,7 @@ func TestHasFullPayload(t *testing.T) {
|
|||||||
|
|
||||||
// check with content-length
|
// check with content-length
|
||||||
m = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 23\r\n\r\nMozillaDeveloperNetwork"
|
m = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 23\r\n\r\nMozillaDeveloperNetwork"
|
||||||
got = HasFullPayload([]byte(m), nil)
|
got = HasFullPayload(nil, []byte(m))
|
||||||
expected = true
|
expected = true
|
||||||
if got != expected {
|
if got != expected {
|
||||||
t.Errorf("expected %v to equal %v", got, expected)
|
t.Errorf("expected %v to equal %v", got, expected)
|
||||||
@@ -468,7 +466,7 @@ func TestHasFullPayload(t *testing.T) {
|
|||||||
|
|
||||||
// check missing total length
|
// check missing total length
|
||||||
m = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 23\r\n\r\nMozillaDeveloperNet"
|
m = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 23\r\n\r\nMozillaDeveloperNet"
|
||||||
got = HasFullPayload([]byte(m), nil)
|
got = HasFullPayload(nil, []byte(m))
|
||||||
expected = false
|
expected = false
|
||||||
if got != expected {
|
if got != expected {
|
||||||
t.Errorf("expected %v to equal %v", got, expected)
|
t.Errorf("expected %v to equal %v", got, expected)
|
||||||
@@ -476,7 +474,7 @@ func TestHasFullPayload(t *testing.T) {
|
|||||||
|
|
||||||
// check with no body
|
// check with no body
|
||||||
m = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n"
|
m = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n"
|
||||||
got = HasFullPayload([]byte(m), nil)
|
got = HasFullPayload(nil, []byte(m))
|
||||||
expected = true
|
expected = true
|
||||||
if got != expected {
|
if got != expected {
|
||||||
t.Errorf("expected %v to equal %v", got, expected)
|
t.Errorf("expected %v to equal %v", got, expected)
|
||||||
@@ -484,18 +482,9 @@ func TestHasFullPayload(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkHasFullPayload(b *testing.B) {
|
func BenchmarkHasFullPayload(b *testing.B) {
|
||||||
var buf bytes.Buffer
|
data := []byte("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n1e\r\n111111111111111111111111111111\r\n0\r\n\r\n")
|
||||||
buf.Write([]byte("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n"))
|
|
||||||
var chunk = []byte("1e\r\n111111111111111111111111111111\r\n")
|
|
||||||
for i := 0; i < 5000; i++ {
|
|
||||||
buf.Write(chunk)
|
|
||||||
}
|
|
||||||
buf.Write([]byte("0\r\n\r\n"))
|
|
||||||
data := buf.Bytes()
|
|
||||||
b.ResetTimer() // ignores the upper initialization
|
|
||||||
b.ReportMetric(float64(5000), "chunks/op")
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
if !HasFullPayload(data, nil) {
|
if !HasFullPayload(nil, data) {
|
||||||
b.Fail()
|
b.Fail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
@@ -4,8 +4,12 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/buger/goreplay/size"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DEMO indicates that goreplay is running in demo mode
|
// DEMO indicates that goreplay is running in demo mode
|
||||||
@@ -59,6 +63,9 @@ type AppSettings struct {
|
|||||||
OutputHTTP MultiOption `json:"output-http"`
|
OutputHTTP MultiOption `json:"output-http"`
|
||||||
PrettifyHTTP bool `json:"prettify-http"`
|
PrettifyHTTP bool `json:"prettify-http"`
|
||||||
|
|
||||||
|
MaxAllowedMem string `json:"max-allowed-mem"`
|
||||||
|
maxAllowedMem int `json:"-"`
|
||||||
|
|
||||||
OutputHTTPConfig HTTPOutputConfig
|
OutputHTTPConfig HTTPOutputConfig
|
||||||
|
|
||||||
OutputBinary MultiOption `json:"output-binary"`
|
OutputBinary MultiOption `json:"output-binary"`
|
||||||
@@ -142,6 +149,8 @@ func init() {
|
|||||||
flag.BoolVar(&Settings.Monitor, "input-raw-monitor", false, "enable RF monitor mode")
|
flag.BoolVar(&Settings.Monitor, "input-raw-monitor", false, "enable RF monitor mode")
|
||||||
flag.BoolVar(&Settings.Stats, "input-raw-stats", false, "enable stats generator on raw TCP messages")
|
flag.BoolVar(&Settings.Stats, "input-raw-stats", false, "enable stats generator on raw TCP messages")
|
||||||
|
|
||||||
|
flag.StringVar(&Settings.MaxAllowedMem, "max-allowed-mem", "", "Allow specify memory which can be consumed by the process. Example: --max-allowed-mem 10%, --max-allowed-mem 200m")
|
||||||
|
|
||||||
flag.StringVar(&Settings.Middleware, "middleware", "", "Used for modifying traffic using external command")
|
flag.StringVar(&Settings.Middleware, "middleware", "", "Used for modifying traffic using external command")
|
||||||
|
|
||||||
flag.Var(&Settings.OutputHTTP, "output-http", "Forwards incoming requests to given http address.\n\t# Redirect all incoming requests to staging.com address \n\tgor --input-raw :80 --output-http http://staging.com")
|
flag.Var(&Settings.OutputHTTP, "output-http", "Forwards incoming requests to given http address.\n\t# Redirect all incoming requests to staging.com address \n\tgor --input-raw :80 --output-http http://staging.com")
|
||||||
@@ -217,6 +226,21 @@ func checkSettings() {
|
|||||||
if Settings.CopyBufferSize < 1 {
|
if Settings.CopyBufferSize < 1 {
|
||||||
Settings.CopyBufferSize.Set("5mb")
|
Settings.CopyBufferSize.Set("5mb")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if Settings.MaxAllowedMem != "" {
|
||||||
|
_, totalMemory := memUsage()
|
||||||
|
fmt.Println("Total available memory:", bToMb(totalMemory), "Mb")
|
||||||
|
if strings.HasSuffix(Settings.MaxAllowedMem, "%") {
|
||||||
|
pct, _ := strconv.Atoi(strings.TrimSuffix(Settings.MaxAllowedMem, "%"))
|
||||||
|
Settings.maxAllowedMem = int((float64(pct) / 100) * float64(totalMemory))
|
||||||
|
} else {
|
||||||
|
var s size.Size
|
||||||
|
s.Set(Settings.MaxAllowedMem)
|
||||||
|
Settings.maxAllowedMem = int(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Limiting memory usage to:", bToMb(uint64(Settings.maxAllowedMem)), "Mb")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var previousDebugTime = time.Now()
|
var previousDebugTime = time.Now()
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ maxSize := 5 << 20
|
|||||||
debugger := func(debugLevel int, data ...interface{}){} // debugger can also be nil
|
debugger := func(debugLevel int, data ...interface{}){} // debugger can also be nil
|
||||||
messageHandler := func(mssg *tcp.Message){}
|
messageHandler := func(mssg *tcp.Message){}
|
||||||
|
|
||||||
mssgPool := tcp.NewMessagePool(maxMessageSize, messageExpire, debugger, messageHandler)
|
mssgPool := tcp.NewMessageParser(maxMessageSize, messageExpire, debugger, messageHandler)
|
||||||
listener.Listen(ctx, mssgPool.Handler)
|
listener.Listen(ctx, mssgPool.Handler)
|
||||||
|
|
||||||
you can use pool.End or/and pool.Start to set custom session behaviors
|
you can use pool.End or/and pool.Start to set custom session behaviors
|
||||||
|
|||||||
+229
-184
@@ -1,95 +1,96 @@
|
|||||||
package tcp
|
package tcp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/buger/goreplay/capture"
|
|
||||||
"github.com/buger/goreplay/size"
|
"github.com/buger/goreplay/size"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Stats every message carry its own stats object
|
// Stats every message carry its own stats object
|
||||||
type Stats struct {
|
type Stats struct {
|
||||||
LostData int
|
LostData int
|
||||||
Length int // length of the data
|
Length int // length of the data
|
||||||
Start time.Time // first packet's timestamp
|
Start time.Time // first packet's timestamp
|
||||||
End time.Time // last packet's timestamp
|
End time.Time // last packet's timestamp
|
||||||
SrcAddr string
|
SrcAddr string
|
||||||
DstAddr string
|
DstAddr string
|
||||||
IsIncoming bool
|
IsRequest bool
|
||||||
TimedOut bool // timeout before getting the whole message
|
TimedOut bool // timeout before getting the whole message
|
||||||
Truncated bool // last packet truncated due to max message size
|
Truncated bool // last packet truncated due to max message size
|
||||||
IPversion byte
|
IPversion byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message is the representation of a tcp message
|
// Message is the representation of a tcp message
|
||||||
type Message struct {
|
type Message struct {
|
||||||
packets []*Packet
|
packets []*Packet
|
||||||
done chan bool
|
parser *MessageParser
|
||||||
pool *MessagePool
|
|
||||||
buf *bytes.Buffer
|
|
||||||
feedback interface{}
|
feedback interface{}
|
||||||
Stats
|
Stats
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMessage ...
|
|
||||||
func NewMessage(srcAddr, dstAddr string, ipVersion uint8) (m *Message) {
|
|
||||||
m = new(Message)
|
|
||||||
m.DstAddr = dstAddr
|
|
||||||
m.SrcAddr = srcAddr
|
|
||||||
m.IPversion = ipVersion
|
|
||||||
m.done = make(chan bool)
|
|
||||||
m.buf = &bytes.Buffer{}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// UUID returns the UUID of a TCP request and its response.
|
// UUID returns the UUID of a TCP request and its response.
|
||||||
func (m *Message) UUID() []byte {
|
func (m *Message) UUID() []byte {
|
||||||
var key uint64
|
var streamID uint64
|
||||||
pckt := m.packets[0]
|
pckt := m.packets[0]
|
||||||
|
|
||||||
// check if response or request have generated the ID before.
|
// check if response or request have generated the ID before.
|
||||||
if m.pool.uuids != nil {
|
if m.IsRequest {
|
||||||
lst := len(pckt.SrcIP) - 4
|
streamID = uint64(pckt.SrcPort)<<48 | uint64(pckt.DstPort)<<32 |
|
||||||
if m.IsIncoming {
|
uint64(ip2int(pckt.SrcIP))
|
||||||
key = uint64(pckt.SrcPort)<<48 | uint64(pckt.DstPort)<<32 |
|
} else {
|
||||||
uint64(_uint32(pckt.SrcIP[lst:]))
|
streamID = uint64(pckt.DstPort)<<48 | uint64(pckt.SrcPort)<<32 |
|
||||||
} else {
|
uint64(ip2int(pckt.DstIP))
|
||||||
key = uint64(pckt.DstPort)<<48 | uint64(pckt.SrcPort)<<32 |
|
|
||||||
uint64(_uint32(pckt.DstIP[lst:]))
|
|
||||||
}
|
|
||||||
if uuidHex, ok := m.pool.uuids[key]; ok {
|
|
||||||
delete(m.pool.uuids, key)
|
|
||||||
return uuidHex
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
id := make([]byte, 12, 12)
|
id := make([]byte, 12)
|
||||||
binary.BigEndian.PutUint32(id, pckt.Seq)
|
binary.BigEndian.PutUint64(id, streamID)
|
||||||
tStamp := m.End.UnixNano()
|
|
||||||
binary.BigEndian.PutUint64(id[4:], uint64(tStamp))
|
if m.IsRequest {
|
||||||
uuidHex := make([]byte, 24, 24)
|
binary.BigEndian.PutUint32(id[8:], pckt.Ack)
|
||||||
hex.Encode(uuidHex[:], id[:])
|
} else {
|
||||||
if m.pool.uuids != nil {
|
binary.BigEndian.PutUint32(id[8:], pckt.Seq)
|
||||||
if len(m.pool.uuids) >= 1000 {
|
|
||||||
m.pool.cleanUUIDs()
|
|
||||||
}
|
|
||||||
m.pool.uuids[key] = uuidHex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uuidHex := make([]byte, 24)
|
||||||
|
hex.Encode(uuidHex[:], id[:])
|
||||||
|
|
||||||
return uuidHex
|
return uuidHex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Message) add(pckt *Packet) {
|
func (m *Message) add(packet *Packet) {
|
||||||
m.Length += len(pckt.Payload)
|
// fmt.Println("SEQ:", packet.Seq, " - ", len(packet.Payload))
|
||||||
m.LostData += int(pckt.Lost)
|
|
||||||
m.packets = append(m.packets, pckt)
|
// Skip duplicates
|
||||||
m.End = pckt.Timestamp
|
for _, p := range m.packets {
|
||||||
m.buf.Write(pckt.Payload)
|
if p.Seq == packet.Seq {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Packets not always captured in same Seq order, and sometimes we need to prepend
|
||||||
|
if len(m.packets) == 0 || packet.Seq > m.packets[len(m.packets)-1].Seq {
|
||||||
|
m.packets = append(m.packets, packet)
|
||||||
|
} else if packet.Seq < m.packets[0].Seq {
|
||||||
|
m.packets = append([]*Packet{packet}, m.packets...)
|
||||||
|
} else { // insert somewhere in the middle...
|
||||||
|
for i, p := range m.packets {
|
||||||
|
if packet.Seq < p.Seq {
|
||||||
|
m.packets = append(m.packets[:i], append([]*Packet{packet}, m.packets[i:]...)...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Length += len(packet.Payload)
|
||||||
|
m.LostData += int(packet.Lost)
|
||||||
|
|
||||||
|
if packet.Timestamp.After(m.End) || m.End.IsZero() {
|
||||||
|
m.End = packet.Timestamp
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Packets returns packets of the message
|
// Packets returns packets of the message
|
||||||
@@ -97,18 +98,57 @@ func (m *Message) Packets() []*Packet {
|
|||||||
return m.packets
|
return m.packets
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data returns data in this message
|
func (m *Message) MissingChunk() bool {
|
||||||
func (m *Message) Data() []byte {
|
nextSeq := m.packets[0].Seq
|
||||||
return m.buf.Bytes()
|
|
||||||
|
for _, p := range m.packets {
|
||||||
|
if p.Seq != nextSeq {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
nextSeq += uint32(len(p.Payload))
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFeedback set feedback/data that can be used later, e.g with End or Start hint
|
func (m *Message) PacketData() [][]byte {
|
||||||
func (m *Message) SetFeedback(feedback interface{}) {
|
var totalLen int
|
||||||
|
for _, p := range m.packets {
|
||||||
|
totalLen += len(p.Payload)
|
||||||
|
}
|
||||||
|
tmp := make([][]byte, totalLen)
|
||||||
|
|
||||||
|
for i, p := range m.packets {
|
||||||
|
tmp[i] = p.Payload
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data returns data in this message
|
||||||
|
func (m *Message) Data() []byte {
|
||||||
|
var totalLen int
|
||||||
|
for _, p := range m.packets {
|
||||||
|
totalLen += len(p.Payload)
|
||||||
|
}
|
||||||
|
tmp := make([]byte, totalLen)
|
||||||
|
|
||||||
|
var i int
|
||||||
|
for _, p := range m.packets {
|
||||||
|
i += copy(tmp[i:], p.Payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProtocolState set feedback/data that can be used later, e.g with End or Start hint
|
||||||
|
func (m *Message) SetProtocolState(feedback interface{}) {
|
||||||
m.feedback = feedback
|
m.feedback = feedback
|
||||||
}
|
}
|
||||||
|
|
||||||
// Feedback returns feedback associated to this message
|
// ProtocolState returns feedback associated to this message
|
||||||
func (m *Message) Feedback() interface{} {
|
func (m *Message) ProtocolState() interface{} {
|
||||||
return m.feedback
|
return m.feedback
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,171 +157,176 @@ func (m *Message) Sort() {
|
|||||||
sort.SliceStable(m.packets, func(i, j int) bool { return m.packets[i].Seq < m.packets[j].Seq })
|
sort.SliceStable(m.packets, func(i, j int) bool { return m.packets[i].Seq < m.packets[j].Seq })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler message handler
|
func (m *Message) Finalize() {
|
||||||
type Handler func(*Message)
|
// Allow re-use memory
|
||||||
|
for _, p := range m.packets {
|
||||||
|
packetPool.Put(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emitter message handler
|
||||||
|
type Emitter func(*Message)
|
||||||
|
|
||||||
// Debugger is the debugger function. first params is the indicator of the issue's priority
|
// Debugger is the debugger function. first params is the indicator of the issue's priority
|
||||||
// the higher the number, the lower the priority. it can be 4 <= level <= 6.
|
// the higher the number, the lower the priority. it can be 4 <= level <= 6.
|
||||||
type Debugger func(int, ...interface{})
|
type Debugger func(int, ...interface{})
|
||||||
|
|
||||||
// HintEnd hints the pool to stop the session, see MessagePool.End
|
// HintEnd hints the parser to stop the session, see MessageParser.End
|
||||||
// when set, it will be executed before checking FIN or RST flag
|
// when set, it will be executed before checking FIN or RST flag
|
||||||
type HintEnd func(*Message) bool
|
type HintEnd func(*Message) bool
|
||||||
|
|
||||||
// HintStart hints the pool to start the reassembling the message, see MessagePool.Start
|
// HintStart hints the parser to start the reassembling the message, see MessageParser.Start
|
||||||
// when set, it will be called after checking SYN flag
|
// when set, it will be called after checking SYN flag
|
||||||
type HintStart func(*Packet) (IsIncoming, IsOutgoing bool)
|
type HintStart func(*Packet) (IsRequest, IsOutgoing bool)
|
||||||
|
|
||||||
// MessagePool holds data of all tcp messages in progress(still receiving/sending packets).
|
// MessageParser holds data of all tcp messages in progress(still receiving/sending packets).
|
||||||
// message is identified by its source port and dst port, and last 4bytes of src IP.
|
// message is identified by its source port and dst port, and last 4bytes of src IP.
|
||||||
type MessagePool struct {
|
type MessageParser struct {
|
||||||
sync.Mutex
|
|
||||||
debug Debugger
|
debug Debugger
|
||||||
maxSize size.Size // maximum message size, default 5mb
|
maxSize size.Size // maximum message size, default 5mb
|
||||||
pool map[uint64]*Message
|
m map[uint64]*Message
|
||||||
uuids map[uint64][]byte
|
emit Emitter
|
||||||
handler Handler
|
|
||||||
messageExpire time.Duration // the maximum time to wait for the final packet, minimum is 100ms
|
messageExpire time.Duration // the maximum time to wait for the final packet, minimum is 100ms
|
||||||
End HintEnd
|
End HintEnd
|
||||||
Start HintStart
|
Start HintStart
|
||||||
|
ticker *time.Ticker
|
||||||
|
packets chan *Packet
|
||||||
|
msgs int32 // messages in the parser
|
||||||
|
close chan struct{} // to signal that we are able to close
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMessagePool returns a new instance of message pool
|
// NewMessageParser returns a new instance of message parser
|
||||||
func NewMessagePool(maxSize size.Size, messageExpire time.Duration, debugger Debugger, handler Handler) (pool *MessagePool) {
|
func NewMessageParser(maxSize size.Size, messageExpire time.Duration, debugger Debugger, emitHandler Emitter) (parser *MessageParser) {
|
||||||
pool = new(MessagePool)
|
parser = new(MessageParser)
|
||||||
pool.debug = debugger
|
parser.debug = debugger
|
||||||
pool.handler = handler
|
parser.emit = emitHandler
|
||||||
pool.messageExpire = time.Millisecond * 100
|
parser.messageExpire = time.Millisecond * 100
|
||||||
if pool.messageExpire < messageExpire {
|
if parser.messageExpire < messageExpire {
|
||||||
pool.messageExpire = messageExpire
|
parser.messageExpire = messageExpire
|
||||||
}
|
}
|
||||||
pool.maxSize = maxSize
|
parser.maxSize = maxSize
|
||||||
if pool.maxSize < 1 {
|
if parser.maxSize < 1 {
|
||||||
pool.maxSize = 5 << 20
|
parser.maxSize = 5 << 20
|
||||||
}
|
}
|
||||||
pool.pool = make(map[uint64]*Message)
|
parser.packets = make(chan *Packet, 1000)
|
||||||
return pool
|
parser.m = make(map[uint64]*Message)
|
||||||
|
parser.ticker = time.NewTicker(time.Millisecond * 50)
|
||||||
|
parser.close = make(chan struct{}, 1)
|
||||||
|
go parser.wait()
|
||||||
|
return parser
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler returns packet handler
|
var totalCounter int
|
||||||
func (pool *MessagePool) Handler(packet *capture.Packet) {
|
|
||||||
|
// Packet returns packet handler
|
||||||
|
func (parser *MessageParser) PacketHandler(packet *Packet) {
|
||||||
|
parser.packets <- packet
|
||||||
|
|
||||||
|
totalCounter++
|
||||||
|
|
||||||
|
if totalCounter%10000 == 0 {
|
||||||
|
fmt.Println(totalCounter, len(parser.packets))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (parser *MessageParser) wait() {
|
||||||
|
var (
|
||||||
|
pckt *Packet
|
||||||
|
now time.Time
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case pckt = <-parser.packets:
|
||||||
|
parser.processPacket(pckt)
|
||||||
|
case now = <-parser.ticker.C:
|
||||||
|
parser.timer(now)
|
||||||
|
case <-parser.close:
|
||||||
|
parser.ticker.Stop()
|
||||||
|
// parser.Close should wait for this function to return
|
||||||
|
parser.close <- struct{}{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (parser *MessageParser) processPacket(pckt *Packet) {
|
||||||
var in, out bool
|
var in, out bool
|
||||||
pckt, err := ParsePacket(packet)
|
|
||||||
if err != nil {
|
// Trying to build unique hash, but there is small chance of collision
|
||||||
go pool.say(4, fmt.Sprintf("error decoding packet(%dBytes):%s\n", packet.Info.CaptureLength, err))
|
// No matter if it is request or response, all packets in the same message have same
|
||||||
return
|
m, ok := parser.m[pckt.MessageID()]
|
||||||
}
|
|
||||||
pool.Lock()
|
|
||||||
defer pool.Unlock()
|
|
||||||
lst := len(pckt.SrcIP) - 4
|
|
||||||
key := uint64(pckt.SrcPort)<<48 | uint64(pckt.DstPort)<<32 |
|
|
||||||
uint64(_uint32(pckt.SrcIP[lst:]))
|
|
||||||
m, ok := pool.pool[key]
|
|
||||||
if pckt.RST {
|
|
||||||
if ok {
|
|
||||||
m.done <- true
|
|
||||||
<-m.done
|
|
||||||
}
|
|
||||||
key = uint64(pckt.DstPort)<<48 | uint64(pckt.SrcPort)<<32 |
|
|
||||||
uint64(_uint32(pckt.DstIP[lst:]))
|
|
||||||
m, ok = pool.pool[key]
|
|
||||||
if ok {
|
|
||||||
m.done <- true
|
|
||||||
<-m.done
|
|
||||||
}
|
|
||||||
go pool.say(4, fmt.Sprintf("RST flag from %s to %s at %s\n", pckt.Src(), pckt.Dst(), pckt.Timestamp))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch {
|
switch {
|
||||||
case ok:
|
case ok:
|
||||||
pool.addPacket(m, pckt)
|
parser.addPacket(m, pckt)
|
||||||
return
|
return
|
||||||
case pckt.SYN:
|
case parser.Start != nil:
|
||||||
in = !pckt.ACK
|
if in, out = parser.Start(pckt); !(in || out) {
|
||||||
case pool.Start != nil:
|
// Packet can be received out of order, so give it another chance
|
||||||
if in, out = pool.Start(pckt); !(in || out) {
|
if pckt.Retry < 1 && len(pckt.Payload) > 0 {
|
||||||
|
// Requeue not known packets
|
||||||
|
pckt.Retry++
|
||||||
|
|
||||||
|
select {
|
||||||
|
case parser.packets <- pckt:
|
||||||
|
default:
|
||||||
|
packetPool.Put(pckt)
|
||||||
|
// fmt.Println("Skipping packet")
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
m = NewMessage(pckt.Src(), pckt.Dst(), pckt.Version)
|
|
||||||
m.IsIncoming = in
|
m = new(Message)
|
||||||
pool.pool[key] = m
|
m.IsRequest = in
|
||||||
|
parser.m[pckt.MessageID()] = m
|
||||||
m.Start = pckt.Timestamp
|
m.Start = pckt.Timestamp
|
||||||
m.pool = pool
|
m.parser = parser
|
||||||
go pool.dispatch(key, m)
|
parser.addPacket(m, pckt)
|
||||||
pool.addPacket(m, pckt)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchUUID instructs the pool to use same UUID for request and responses
|
func (parser *MessageParser) addPacket(m *Message, pckt *Packet) {
|
||||||
// this function should be called at initial stage of the pool
|
trunc := m.Length + len(pckt.Payload) - int(parser.maxSize)
|
||||||
func (pool *MessagePool) MatchUUID(match bool) {
|
|
||||||
if match {
|
|
||||||
pool.uuids = make(map[uint64][]byte)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pool.uuids = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// run GC on UUID map
|
|
||||||
func (pool *MessagePool) cleanUUIDs() {
|
|
||||||
var tStamp int64
|
|
||||||
now := time.Now().UnixNano()
|
|
||||||
for k, v := range pool.uuids {
|
|
||||||
// there is a timestamp wrapped in every ID
|
|
||||||
tStamp = int64(binary.BigEndian.Uint64(v[4:]))
|
|
||||||
if time.Duration(now-tStamp) > pool.messageExpire {
|
|
||||||
delete(pool.uuids, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pool *MessagePool) dispatch(key uint64, m *Message) {
|
|
||||||
select {
|
|
||||||
case <-m.done:
|
|
||||||
defer func() { m.done <- true }() // signal that message was dispatched
|
|
||||||
case <-time.After(pool.messageExpire):
|
|
||||||
pool.Lock()
|
|
||||||
defer pool.Unlock()
|
|
||||||
// avoid dispathing message twice
|
|
||||||
if _, ok := pool.pool[key]; !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m.TimedOut = true
|
|
||||||
}
|
|
||||||
delete(pool.pool, key)
|
|
||||||
pool.handler(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pool *MessagePool) addPacket(m *Message, pckt *Packet) {
|
|
||||||
trunc := m.Length + len(pckt.Payload) - int(pool.maxSize)
|
|
||||||
if trunc > 0 {
|
if trunc > 0 {
|
||||||
m.Truncated = true
|
m.Truncated = true
|
||||||
pckt.Payload = pckt.Payload[:int(pool.maxSize)-m.Length]
|
pckt.Payload = pckt.Payload[:int(parser.maxSize)-m.Length]
|
||||||
}
|
}
|
||||||
m.add(pckt)
|
m.add(pckt)
|
||||||
switch {
|
switch {
|
||||||
|
|
||||||
// if one of this cases matches, we dispatch the message
|
// if one of this cases matches, we dispatch the message
|
||||||
case trunc >= 0:
|
case trunc >= 0:
|
||||||
case pckt.FIN:
|
case parser.End != nil && parser.End(m):
|
||||||
case pool.End != nil && pool.End(m):
|
|
||||||
default:
|
default:
|
||||||
// continue to receive packets
|
// continue to receive packets
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
m.done <- true
|
|
||||||
<-m.done
|
parser.Emit(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// this function should not block other pool operations
|
func (parser *MessageParser) Emit(m *Message) {
|
||||||
func (pool *MessagePool) say(level int, args ...interface{}) {
|
delete(parser.m, m.packets[0].MessageID())
|
||||||
if pool.debug != nil {
|
parser.emit(m)
|
||||||
pool.debug(level, args...)
|
}
|
||||||
|
|
||||||
|
func (parser *MessageParser) timer(now time.Time) {
|
||||||
|
for _, m := range parser.m {
|
||||||
|
if now.Sub(m.End) > parser.messageExpire {
|
||||||
|
m.TimedOut = true
|
||||||
|
parser.Emit(m)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func _uint32(b []byte) uint32 {
|
// this function should not block other parser operations
|
||||||
return binary.BigEndian.Uint32(b)
|
func (parser *MessageParser) Debug(level int, args ...interface{}) {
|
||||||
|
if parser.debug != nil {
|
||||||
|
parser.debug(level, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (parser *MessageParser) Close() error {
|
||||||
|
parser.close <- struct{}{}
|
||||||
|
<-parser.close // wait for timer to be closed!
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+169
-27
@@ -2,14 +2,28 @@ package tcp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/buger/goreplay/capture"
|
"github.com/google/gopacket"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func copySlice(b, a []byte) []byte {
|
||||||
|
if cap(b) < len(a) {
|
||||||
|
b = make([]byte, len(a))
|
||||||
|
}
|
||||||
|
copy(b, a)
|
||||||
|
return b[:len(a)]
|
||||||
|
}
|
||||||
|
|
||||||
|
var packetPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return new(Packet)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Packet represent data and layers of packet.
|
Packet represent data and layers of packet.
|
||||||
parser extracts information from pcap Packet. functions of *Packet doesn't validate if packet is nil,
|
parser extracts information from pcap Packet. functions of *Packet doesn't validate if packet is nil,
|
||||||
@@ -17,54 +31,137 @@ calllers must make sure that ParsePacket has'nt returned any error before callin
|
|||||||
function.
|
function.
|
||||||
*/
|
*/
|
||||||
type Packet struct {
|
type Packet struct {
|
||||||
|
messageID uint64
|
||||||
SrcIP, DstIP net.IP
|
SrcIP, DstIP net.IP
|
||||||
Version uint8
|
Version uint8
|
||||||
SrcPort, DstPort uint16
|
SrcPort, DstPort uint16
|
||||||
Ack, Seq uint32
|
Ack, Seq uint32
|
||||||
ACK, SYN, FIN, RST bool
|
ACK, SYN, FIN, RST bool
|
||||||
Lost uint32
|
Lost uint32
|
||||||
|
Retry int
|
||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
Payload []byte
|
Payload []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParsePacket parse raw packets
|
// ParsePacket parse raw packets
|
||||||
func ParsePacket(packet *capture.Packet) (pckt *Packet, err error) {
|
func ParsePacket(data []byte, lType, lTypeLen int, cp *gopacket.CaptureInfo) (pckt *Packet, err error) {
|
||||||
// early check of error
|
pckt = packetPool.Get().(*Packet)
|
||||||
if packet == nil {
|
pckt.Retry = 0
|
||||||
return nil, errors.New("empty packet")
|
pckt.messageID = 0
|
||||||
|
|
||||||
|
// TODO: check resolution
|
||||||
|
pckt.Timestamp = cp.Timestamp
|
||||||
|
|
||||||
|
if len(data) < lTypeLen {
|
||||||
|
return nil, ErrHdrLength("Link")
|
||||||
}
|
}
|
||||||
if packet.Err != nil {
|
if len(data) <= lTypeLen {
|
||||||
return nil, packet.Err
|
return nil, ErrHdrMissing("IPv4 or IPv6")
|
||||||
}
|
}
|
||||||
|
|
||||||
var t Packet
|
ldata := data[lTypeLen:]
|
||||||
pckt = &t
|
var proto byte
|
||||||
// TODO: check resolution
|
var netLayer, transLayer []byte
|
||||||
pckt.Timestamp = packet.Info.Timestamp
|
|
||||||
if (packet.NetLayer[0] >> 4) == 4 {
|
if ldata[0]>>4 == 4 {
|
||||||
|
// IPv4 header
|
||||||
|
if len(ldata) < 20 {
|
||||||
|
return nil, ErrHdrLength("IPv4")
|
||||||
|
}
|
||||||
|
proto = ldata[9]
|
||||||
|
ihl := int(ldata[0]&0x0F) * 4
|
||||||
|
if ihl < 20 {
|
||||||
|
return nil, ErrHdrInvalid("IPv4's IHL")
|
||||||
|
}
|
||||||
|
if len(ldata) < ihl {
|
||||||
|
return nil, ErrHdrLength("IPv4 opts")
|
||||||
|
}
|
||||||
|
netLayer = ldata[:ihl]
|
||||||
|
} else if ldata[0]>>4 == 6 {
|
||||||
|
if len(ldata) < 40 {
|
||||||
|
return nil, ErrHdrLength("IPv6")
|
||||||
|
}
|
||||||
|
proto = ldata[6]
|
||||||
|
totalLen := 40
|
||||||
|
for ipv6ExtensionHdr(proto) {
|
||||||
|
hdr := len(ldata) - totalLen
|
||||||
|
if hdr < 8 {
|
||||||
|
return nil, ErrHdrExpected("IPv6 opts")
|
||||||
|
}
|
||||||
|
extLen := 8
|
||||||
|
if proto != 44 {
|
||||||
|
extLen = int(ldata[totalLen+1]+1) * 8
|
||||||
|
}
|
||||||
|
if hdr < extLen {
|
||||||
|
return nil, ErrHdrLength("IPv6 opts")
|
||||||
|
}
|
||||||
|
proto = ldata[totalLen]
|
||||||
|
totalLen += extLen
|
||||||
|
}
|
||||||
|
netLayer = ldata[:totalLen]
|
||||||
|
} else {
|
||||||
|
return nil, ErrHdrExpected("IPv4 or IPv6")
|
||||||
|
}
|
||||||
|
if proto != 6 {
|
||||||
|
return nil, ErrHdrExpected("TCP")
|
||||||
|
}
|
||||||
|
if len(data) <= len(netLayer) {
|
||||||
|
return nil, ErrHdrMissing("TCP")
|
||||||
|
}
|
||||||
|
ndata := ldata[len(netLayer):]
|
||||||
|
// TCP header
|
||||||
|
if len(ndata) < 20 {
|
||||||
|
return nil, ErrHdrLength("TCP")
|
||||||
|
}
|
||||||
|
dOf := int(ndata[12]>>4) * 4
|
||||||
|
if dOf < 20 {
|
||||||
|
return nil, ErrHdrInvalid("TCP's ndata offset")
|
||||||
|
}
|
||||||
|
if len(ndata) < dOf {
|
||||||
|
return nil, ErrHdrLength("TCP opts")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ndata[dOf:]) == 0 {
|
||||||
|
return nil, fmt.Errorf("Packet without Data")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (netLayer[0] >> 4) == 4 {
|
||||||
// IPv4 header
|
// IPv4 header
|
||||||
pckt.Version = 4
|
pckt.Version = 4
|
||||||
pckt.SrcIP = packet.NetLayer[12:16]
|
pckt.SrcIP = netLayer[12:16]
|
||||||
pckt.DstIP = packet.NetLayer[16:20]
|
pckt.DstIP = netLayer[16:20]
|
||||||
} else {
|
} else {
|
||||||
// IPv6 header
|
// IPv6 header
|
||||||
pckt.Version = 6
|
pckt.Version = 6
|
||||||
pckt.SrcIP = packet.NetLayer[8:24]
|
pckt.SrcIP = netLayer[8:24]
|
||||||
pckt.DstIP = packet.NetLayer[24:40]
|
pckt.DstIP = netLayer[24:40]
|
||||||
}
|
}
|
||||||
pckt.SrcPort = binary.BigEndian.Uint16(packet.TransLayer[0:2])
|
|
||||||
pckt.DstPort = binary.BigEndian.Uint16(packet.TransLayer[2:4])
|
transLayer = ndata[:dOf]
|
||||||
pckt.Seq = binary.BigEndian.Uint32(packet.TransLayer[4:8])
|
|
||||||
pckt.Ack = binary.BigEndian.Uint32(packet.TransLayer[8:12])
|
pckt.SrcPort = binary.BigEndian.Uint16(transLayer[0:2])
|
||||||
pckt.FIN = packet.TransLayer[13]&0x01 != 0
|
pckt.DstPort = binary.BigEndian.Uint16(transLayer[2:4])
|
||||||
pckt.SYN = packet.TransLayer[13]&0x02 != 0
|
pckt.Seq = binary.BigEndian.Uint32(transLayer[4:8])
|
||||||
pckt.RST = packet.TransLayer[13]&0x04 != 0
|
pckt.Ack = binary.BigEndian.Uint32(transLayer[8:12])
|
||||||
pckt.ACK = packet.TransLayer[13]&0x10 != 0
|
pckt.FIN = transLayer[13]&0x01 != 0
|
||||||
pckt.Lost = uint32(packet.Info.Length - packet.Info.CaptureLength)
|
pckt.SYN = transLayer[13]&0x02 != 0
|
||||||
pckt.Payload = packet.Payload
|
pckt.RST = transLayer[13]&0x04 != 0
|
||||||
|
pckt.ACK = transLayer[13]&0x10 != 0
|
||||||
|
pckt.Lost = uint32(cp.Length - cp.CaptureLength)
|
||||||
|
pckt.Payload = copySlice(pckt.Payload, ndata[dOf:])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pckt *Packet) MessageID() uint64 {
|
||||||
|
if pckt.messageID == 0 {
|
||||||
|
// All packets in the same message will share the same ID
|
||||||
|
pckt.messageID = uint64(pckt.SrcPort)<<48 | uint64(pckt.DstPort)<<32 |
|
||||||
|
(uint64(ip2int(pckt.SrcIP)) + uint64(ip2int(pckt.DstIP)) + uint64(pckt.Ack))
|
||||||
|
}
|
||||||
|
|
||||||
|
return pckt.messageID
|
||||||
|
}
|
||||||
|
|
||||||
// Src returns the source socket of a packet
|
// Src returns the source socket of a packet
|
||||||
func (pckt *Packet) Src() string {
|
func (pckt *Packet) Src() string {
|
||||||
return fmt.Sprintf("%s:%d", pckt.SrcIP, pckt.SrcPort)
|
return fmt.Sprintf("%s:%d", pckt.SrcIP, pckt.SrcPort)
|
||||||
@@ -74,3 +171,48 @@ func (pckt *Packet) Src() string {
|
|||||||
func (pckt *Packet) Dst() string {
|
func (pckt *Packet) Dst() string {
|
||||||
return fmt.Sprintf("%s:%d", pckt.DstIP, pckt.DstPort)
|
return fmt.Sprintf("%s:%d", pckt.DstIP, pckt.DstPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrHdrLength returned on short header length
|
||||||
|
type ErrHdrLength string
|
||||||
|
|
||||||
|
func (err ErrHdrLength) Error() string {
|
||||||
|
return "short " + string(err) + " length"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrHdrMissing returned on missing header(s)
|
||||||
|
type ErrHdrMissing string
|
||||||
|
|
||||||
|
func (err ErrHdrMissing) Error() string {
|
||||||
|
return "missing " + string(err) + " header(s)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrHdrExpected returned when header(s) are different from the one expected
|
||||||
|
type ErrHdrExpected string
|
||||||
|
|
||||||
|
func (err ErrHdrExpected) Error() string {
|
||||||
|
return "expected " + string(err) + " header(s)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrHdrInvalid returned when header(s) are different from the one expected
|
||||||
|
type ErrHdrInvalid string
|
||||||
|
|
||||||
|
func (err ErrHdrInvalid) Error() string {
|
||||||
|
return "invalid " + string(err) + " value"
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://en.wikipedia.org/wiki/IPv6_packet#Extension_headers
|
||||||
|
func ipv6ExtensionHdr(b byte) bool {
|
||||||
|
// TODO: support all extension headers
|
||||||
|
return b == 0 || b == 43 || b == 44
|
||||||
|
}
|
||||||
|
|
||||||
|
func ip2int(ip net.IP) uint32 {
|
||||||
|
if len(ip) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ip) == 16 {
|
||||||
|
return binary.BigEndian.Uint32(ip[12:16])
|
||||||
|
}
|
||||||
|
return binary.BigEndian.Uint32(ip)
|
||||||
|
}
|
||||||
|
|||||||
+178
-99
@@ -3,17 +3,20 @@ package tcp
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
||||||
|
// "runtime"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/buger/goreplay/capture"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/buger/goreplay/proto"
|
"github.com/buger/goreplay/proto"
|
||||||
"github.com/google/gopacket"
|
"github.com/google/gopacket"
|
||||||
"github.com/google/gopacket/layers"
|
"github.com/google/gopacket/layers"
|
||||||
)
|
)
|
||||||
|
|
||||||
func generateHeader(seq uint32, length uint16) []byte {
|
func generateHeader(request bool, seq uint32, length uint16) []byte {
|
||||||
hdr := make([]byte, 4+24+24, 4+24+24)
|
hdr := make([]byte, 4+24+24)
|
||||||
binary.BigEndian.PutUint32(hdr, uint32(layers.ProtocolFamilyIPv4))
|
binary.BigEndian.PutUint32(hdr, uint32(layers.ProtocolFamilyIPv4))
|
||||||
|
|
||||||
ip := hdr[4:]
|
ip := hdr[4:]
|
||||||
@@ -26,44 +29,154 @@ func generateHeader(seq uint32, length uint16) []byte {
|
|||||||
// set tcp header
|
// set tcp header
|
||||||
tcp := ip[24:]
|
tcp := ip[24:]
|
||||||
tcp[12] = 6 << 4
|
tcp[12] = 6 << 4
|
||||||
binary.BigEndian.PutUint16(tcp, 5535)
|
|
||||||
binary.BigEndian.PutUint16(tcp[2:], 8000)
|
if request {
|
||||||
|
binary.BigEndian.PutUint16(tcp, 5535)
|
||||||
|
binary.BigEndian.PutUint16(tcp[2:], 8000)
|
||||||
|
} else {
|
||||||
|
binary.BigEndian.PutUint16(tcp, 8000)
|
||||||
|
binary.BigEndian.PutUint16(tcp[2:], 5535)
|
||||||
|
}
|
||||||
binary.BigEndian.PutUint32(tcp[4:], seq)
|
binary.BigEndian.PutUint32(tcp[4:], seq)
|
||||||
return hdr
|
return hdr
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPackets(start uint32, _len int, payload []byte) []*capture.Packet {
|
func GetPackets(request bool, start uint32, _len int, payload []byte) []*Packet {
|
||||||
var packets = make([]*capture.Packet, _len)
|
var packets = make([]*Packet, _len)
|
||||||
for i := start; i < start+uint32(_len); i++ {
|
for i := start; i < start+uint32(_len); i++ {
|
||||||
d := append(generateHeader(i, uint16(len(payload))), payload...)
|
d := append(generateHeader(request, i, uint16(len(payload))), payload...)
|
||||||
ci := &gopacket.CaptureInfo{Length: len(d), CaptureLength: len(d), Timestamp: time.Now()}
|
ci := &gopacket.CaptureInfo{Length: len(d), CaptureLength: len(d), Timestamp: time.Now()}
|
||||||
packets[i-start] = capture.NewPacket(d, int(layers.LinkTypeLoop), 4, ci)
|
|
||||||
|
if len(payload) > 0 {
|
||||||
|
packets[i-start], _ = ParsePacket(d, int(layers.LinkTypeLoop), 4, ci)
|
||||||
|
} else {
|
||||||
|
packets[i-start] = new(Packet)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return packets
|
return packets
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMessageParserWithHint(t *testing.T) {
|
func TestRequestResponseMapping(t *testing.T) {
|
||||||
var mssg = make(chan *Message, 3)
|
packets := []*Packet{
|
||||||
pool := NewMessagePool(1<<20, time.Second, nil, func(m *Message) { mssg <- m })
|
{SrcPort: 60000, DstPort: 80, Ack: 1, Seq: 1, Timestamp: time.Unix(1, 0), Payload: []byte("GET / HTTP/1.1\r\n")},
|
||||||
pool.Start = func(pckt *Packet) (bool, bool) {
|
{SrcPort: 60000, DstPort: 80, Ack: 1, Seq: 17, Timestamp: time.Unix(2, 0), Payload: []byte("Host: localhost\r\n\r\n")},
|
||||||
|
|
||||||
|
// Seq of first response packet match Ack of first request packet
|
||||||
|
{SrcPort: 80, DstPort: 60000, Ack: 36, Seq: 1, Timestamp: time.Unix(3, 0), Payload: []byte("HTTP/1.1 200 OK\r\n")},
|
||||||
|
{SrcPort: 80, DstPort: 60000, Ack: 36, Seq: 18, Timestamp: time.Unix(4, 0), Payload: []byte("Content-Length: 0\r\n\r\n")},
|
||||||
|
|
||||||
|
// Same TCP stream
|
||||||
|
{SrcPort: 60000, DstPort: 80, Ack: 39, Seq: 36, Timestamp: time.Unix(5, 0), Payload: []byte("GET / HTTP/1.1\r\n")},
|
||||||
|
{SrcPort: 60000, DstPort: 80, Ack: 39, Seq: 52, Timestamp: time.Unix(6, 0), Payload: []byte("Host: localhost\r\n\r\n")},
|
||||||
|
|
||||||
|
// Seq of first response packet match Ack of first request packet
|
||||||
|
{SrcPort: 80, DstPort: 60000, Ack: 71, Seq: 39, Timestamp: time.Unix(7, 0), Payload: []byte("HTTP/1.1 200 OK\r\n")},
|
||||||
|
{SrcPort: 80, DstPort: 60000, Ack: 71, Seq: 56, Timestamp: time.Unix(8, 0), Payload: []byte("Content-Length: 0\r\n\r\n")},
|
||||||
|
}
|
||||||
|
|
||||||
|
var mssg = make(chan *Message, 4)
|
||||||
|
parser := NewMessageParser(1<<20, time.Second, nil, func(m *Message) { mssg <- m })
|
||||||
|
parser.Start = func(pckt *Packet) (bool, bool) {
|
||||||
return proto.HasRequestTitle(pckt.Payload), proto.HasResponseTitle(pckt.Payload)
|
return proto.HasRequestTitle(pckt.Payload), proto.HasResponseTitle(pckt.Payload)
|
||||||
}
|
}
|
||||||
pool.End = func(m *Message) bool {
|
parser.End = func(m *Message) bool {
|
||||||
return proto.HasFullPayload(m.Data(), m)
|
return proto.HasFullPayload(m, m.Data())
|
||||||
}
|
}
|
||||||
packets := GetPackets(1, 30, nil)
|
|
||||||
packets[0].TransLayer[13] = 2 // SYN flag
|
for _, packet := range packets {
|
||||||
packets[10].TransLayer[13] = 2 // SYN flag
|
parser.PacketHandler(packet)
|
||||||
packets[29].TransLayer[13] = 1 // FIN flag
|
}
|
||||||
packets[4] = GetPackets(5, 1, []byte("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n7"))[0]
|
|
||||||
packets[5] = GetPackets(6, 1, []byte("\r\nMozilla\r\n9\r\nDeveloper\r"))[0]
|
messages := []*Message{}
|
||||||
packets[6] = GetPackets(7, 1, []byte("\n7\r\nNetwork\r\n0\r\n\r\n"))[0]
|
for i := 0; i < 4; i++ {
|
||||||
packets[14] = GetPackets(5, 1, []byte("POST / HTTP/1.1\r\nContent-Type: text/plain\r\nContent-Length: 23\r\n\r\n"))[0]
|
select {
|
||||||
packets[15] = GetPackets(6, 1, []byte("MozillaDeveloper"))[0]
|
case <-time.After(time.Second):
|
||||||
packets[16] = GetPackets(7, 1, []byte("Network"))[0]
|
t.Errorf("can't parse packets fast enough")
|
||||||
packets[24] = GetPackets(5, 1, []byte("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 0\r\n\r"))[0]
|
return
|
||||||
|
case m := <-mssg:
|
||||||
|
messages = append(messages, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, messages[0].IsRequest, true)
|
||||||
|
assert.Equal(t, messages[1].IsRequest, false)
|
||||||
|
assert.Equal(t, messages[2].IsRequest, true)
|
||||||
|
assert.Equal(t, messages[3].IsRequest, false)
|
||||||
|
|
||||||
|
assert.Equal(t, messages[0].UUID(), messages[1].UUID())
|
||||||
|
assert.Equal(t, messages[2].UUID(), messages[3].UUID())
|
||||||
|
|
||||||
|
assert.NotEqual(t, messages[0].UUID(), messages[2].UUID())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageParserWithHint(t *testing.T) {
|
||||||
|
var mssg = make(chan *Message, 3)
|
||||||
|
parser := NewMessageParser(1<<20, time.Second, nil, func(m *Message) { mssg <- m })
|
||||||
|
parser.Start = func(pckt *Packet) (bool, bool) {
|
||||||
|
return proto.HasRequestTitle(pckt.Payload), proto.HasResponseTitle(pckt.Payload)
|
||||||
|
}
|
||||||
|
parser.End = func(m *Message) bool {
|
||||||
|
return proto.HasFullPayload(m, m.Data())
|
||||||
|
}
|
||||||
|
packets := GetPackets(true, 1, 30, nil)
|
||||||
|
packets[4] = GetPackets(false, 4, 1, []byte("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n7"))[0]
|
||||||
|
packets[5] = GetPackets(false, 5, 1, []byte("\r\nMozilla\r\n9\r\nDeveloper\r"))[0]
|
||||||
|
packets[6] = GetPackets(false, 6, 1, []byte("\n7\r\nNetwork\r\n0\r\n\r\n"))[0]
|
||||||
|
packets[14] = GetPackets(true, 14, 1, []byte("POST / HTTP/1.1\r\nContent-Type: text/plain\r\nContent-Length: 23\r\n\r\n"))[0]
|
||||||
|
packets[15] = GetPackets(true, 15, 1, []byte("MozillaDeveloper"))[0]
|
||||||
|
packets[16] = GetPackets(true, 16, 1, []byte("Network"))[0]
|
||||||
|
packets[24] = GetPackets(true, 24, 1, []byte("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 0\r\n\r\n"))[0]
|
||||||
|
|
||||||
for i := 0; i < 30; i++ {
|
for i := 0; i < 30; i++ {
|
||||||
pool.Handler(packets[i])
|
parser.PacketHandler(packets[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
messages := []*Message{}
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
select {
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Errorf("can't parse packets fast enough")
|
||||||
|
return
|
||||||
|
case m := <-mssg:
|
||||||
|
messages = append(messages, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.HasSuffix(messages[0].Data(), []byte("\n7\r\nNetwork\r\n0\r\n\r\n")) {
|
||||||
|
t.Errorf("expected to %q to have suffix %q", messages[0].Data(), []byte("\n7\r\nNetwork\r\n0\r\n\r\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.HasSuffix(messages[1].Data(), []byte("Network")) {
|
||||||
|
t.Errorf("expected to %q to have suffix %q", messages[1].Data(), []byte("Network"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.HasSuffix(messages[2].Data(), []byte("Content-Length: 0\r\n\r\n")) {
|
||||||
|
t.Errorf("expected to %q to have suffix %q", messages[2].Data(), []byte("Content-Length: 0\r\n\r\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageParserWrongOrder(t *testing.T) {
|
||||||
|
var mssg = make(chan *Message, 3)
|
||||||
|
parser := NewMessageParser(1<<20, time.Second, nil, func(m *Message) { mssg <- m })
|
||||||
|
parser.Start = func(pckt *Packet) (bool, bool) {
|
||||||
|
return proto.HasRequestTitle(pckt.Payload), proto.HasResponseTitle(pckt.Payload)
|
||||||
|
}
|
||||||
|
parser.End = func(m *Message) bool {
|
||||||
|
return proto.HasFullPayload(m, m.Data())
|
||||||
|
}
|
||||||
|
packets := GetPackets(true, 1, 30, nil)
|
||||||
|
packets[6] = GetPackets(false, 4, 1, []byte("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n7"))[0]
|
||||||
|
packets[5] = GetPackets(false, 5, 1, []byte("\r\nMozilla\r\n9\r\nDeveloper\r"))[0]
|
||||||
|
packets[4] = GetPackets(false, 6, 1, []byte("\n7\r\nNetwork\r\n0\r\n\r\n"))[0]
|
||||||
|
// Duplicate with same seq
|
||||||
|
packets[7] = GetPackets(false, 6, 1, []byte("\n7\r\nNetwork\r\n0\r\n\r\n"))[0]
|
||||||
|
|
||||||
|
packets[16] = GetPackets(true, 14, 1, []byte("POST / HTTP/1.1\r\nContent-Type: text/plain\r\nContent-Length: 23\r\n\r\n"))[0]
|
||||||
|
packets[15] = GetPackets(true, 15, 1, []byte("MozillaDeveloper"))[0]
|
||||||
|
packets[14] = GetPackets(true, 16, 1, []byte("Network"))[0]
|
||||||
|
|
||||||
|
for i := 0; i < 30; i++ {
|
||||||
|
parser.PacketHandler(packets[i])
|
||||||
}
|
}
|
||||||
var m *Message
|
var m *Message
|
||||||
select {
|
select {
|
||||||
@@ -85,28 +198,16 @@ func TestMessageParserWithHint(t *testing.T) {
|
|||||||
if !bytes.HasSuffix(m.Data(), []byte("Network")) {
|
if !bytes.HasSuffix(m.Data(), []byte("Network")) {
|
||||||
t.Errorf("expected to %q to have suffix %q", m.Data(), []byte("Network"))
|
t.Errorf("expected to %q to have suffix %q", m.Data(), []byte("Network"))
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
t.Errorf("can't parse packets fast enough")
|
|
||||||
return
|
|
||||||
case m = <-mssg:
|
|
||||||
}
|
|
||||||
if !bytes.HasSuffix(m.Data(), []byte("Content-Length: 0\r\n\r")) {
|
|
||||||
t.Errorf("expected to %q to have suffix %q", m.Data(), []byte("Content-Length: 0\r\n\r"))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMessageParserWithoutHint(t *testing.T) {
|
func TestMessageParserWithoutHint(t *testing.T) {
|
||||||
var mssg = make(chan *Message, 1)
|
var mssg = make(chan *Message, 1)
|
||||||
var data [63 << 10]byte
|
var data [63 << 10]byte
|
||||||
packets := GetPackets(1, 10, data[:])
|
packets := GetPackets(true, 1, 10, data[:])
|
||||||
packets[0].TransLayer[13] = 2 // SYN flag
|
|
||||||
packets[9].TransLayer[13] = 1 // FIN flag
|
p := NewMessageParser(63<<10*10, time.Second, nil, func(m *Message) { mssg <- m })
|
||||||
p := NewMessagePool(63<<10*10, time.Second, nil, func(m *Message) { mssg <- m })
|
|
||||||
for _, v := range packets {
|
for _, v := range packets {
|
||||||
p.Handler(v)
|
p.PacketHandler(v)
|
||||||
}
|
}
|
||||||
var m *Message
|
var m *Message
|
||||||
select {
|
select {
|
||||||
@@ -123,14 +224,12 @@ func TestMessageParserWithoutHint(t *testing.T) {
|
|||||||
func TestMessageMaxSizeReached(t *testing.T) {
|
func TestMessageMaxSizeReached(t *testing.T) {
|
||||||
var mssg = make(chan *Message, 2)
|
var mssg = make(chan *Message, 2)
|
||||||
var data [63 << 10]byte
|
var data [63 << 10]byte
|
||||||
packets := GetPackets(1, 2, data[:])
|
packets := GetPackets(true, 1, 2, data[:])
|
||||||
packets = append(packets, GetPackets(3, 1, make([]byte, 63<<10+10))...)
|
packets = append(packets, GetPackets(true, 1, 1, make([]byte, 63<<10+10))...)
|
||||||
packets[0].TransLayer[13] = 2 // SYN flag
|
|
||||||
packets[2].TransLayer[13] = 2 // SYN flag
|
p := NewMessageParser(63<<10+10, time.Second, nil, func(m *Message) { mssg <- m })
|
||||||
packets[2].NetLayer[15] = 3 // changing address
|
|
||||||
p := NewMessagePool(63<<10+10, time.Second, nil, func(m *Message) { mssg <- m })
|
|
||||||
for _, v := range packets {
|
for _, v := range packets {
|
||||||
p.Handler(v)
|
p.PacketHandler(v)
|
||||||
}
|
}
|
||||||
var m *Message
|
var m *Message
|
||||||
select {
|
select {
|
||||||
@@ -163,12 +262,11 @@ func TestMessageMaxSizeReached(t *testing.T) {
|
|||||||
func TestMessageTimeoutReached(t *testing.T) {
|
func TestMessageTimeoutReached(t *testing.T) {
|
||||||
var mssg = make(chan *Message, 2)
|
var mssg = make(chan *Message, 2)
|
||||||
var data [63 << 10]byte
|
var data [63 << 10]byte
|
||||||
packets := GetPackets(1, 2, data[:])
|
packets := GetPackets(true, 1, 2, data[:])
|
||||||
packets[0].TransLayer[13] = 2 // SYN flag
|
p := NewMessageParser(1<<20, 0, nil, func(m *Message) { mssg <- m })
|
||||||
p := NewMessagePool(1<<20, 0, nil, func(m *Message) { mssg <- m })
|
p.PacketHandler(packets[0])
|
||||||
p.Handler(packets[0])
|
time.Sleep(time.Millisecond * 400)
|
||||||
time.Sleep(time.Millisecond * 200)
|
p.PacketHandler(packets[1])
|
||||||
p.Handler(packets[1])
|
|
||||||
m := <-mssg
|
m := <-mssg
|
||||||
if m.Length != 63<<10 {
|
if m.Length != 63<<10 {
|
||||||
t.Errorf("expected %d to equal %d", m.Length, 63<<10)
|
t.Errorf("expected %d to equal %d", m.Length, 63<<10)
|
||||||
@@ -179,22 +277,19 @@ func TestMessageTimeoutReached(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMessageUUID(t *testing.T) {
|
func TestMessageUUID(t *testing.T) {
|
||||||
packets := GetPackets(1, 10, nil)
|
packets := GetPackets(true, 1, 10, nil)
|
||||||
packets[0].TransLayer[13] = 2 // SYN flag
|
|
||||||
packets[4].TransLayer[13] = 1 // FIN flag
|
|
||||||
packets[5].TransLayer[13] = 2 // SYN flag
|
|
||||||
packets[9].TransLayer[13] = 1 // FIN flag
|
|
||||||
var uuid, uuid1 []byte
|
var uuid, uuid1 []byte
|
||||||
pool := NewMessagePool(0, 0, nil, func(msg *Message) {
|
parser := NewMessageParser(0, 0, nil, func(msg *Message) {
|
||||||
if len(uuid) == 0 {
|
if len(uuid) == 0 {
|
||||||
uuid = msg.UUID()
|
uuid = msg.UUID()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
uuid1 = msg.UUID()
|
uuid1 = msg.UUID()
|
||||||
})
|
})
|
||||||
pool.MatchUUID(true)
|
|
||||||
for _, p := range packets {
|
for _, p := range packets {
|
||||||
pool.Handler(p)
|
parser.PacketHandler(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
if string(uuid) != string(uuid1) {
|
if string(uuid) != string(uuid1) {
|
||||||
@@ -203,17 +298,15 @@ func TestMessageUUID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkMessageUUID(b *testing.B) {
|
func BenchmarkMessageUUID(b *testing.B) {
|
||||||
packets := GetPackets(1, 5, nil)
|
packets := GetPackets(true, 1, 5, nil)
|
||||||
packets[0].TransLayer[13] = 2 // SYN flag
|
|
||||||
packets[4].TransLayer[13] = 1 // FIN flag
|
|
||||||
var uuid []byte
|
var uuid []byte
|
||||||
var msg *Message
|
var msg *Message
|
||||||
pool := NewMessagePool(0, 0, nil, func(m *Message) {
|
parser := NewMessageParser(0, 0, nil, func(m *Message) {
|
||||||
msg = m
|
msg = m
|
||||||
})
|
})
|
||||||
pool.MatchUUID(true)
|
|
||||||
for _, p := range packets {
|
for _, p := range packets {
|
||||||
pool.Handler(p)
|
parser.PacketHandler(p)
|
||||||
}
|
}
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
@@ -225,8 +318,8 @@ func BenchmarkMessageUUID(b *testing.B) {
|
|||||||
func BenchmarkPacketParseAndSort(b *testing.B) {
|
func BenchmarkPacketParseAndSort(b *testing.B) {
|
||||||
m := new(Message)
|
m := new(Message)
|
||||||
m.packets = make([]*Packet, 100)
|
m.packets = make([]*Packet, 100)
|
||||||
for i, v := range GetPackets(1, 100, nil) {
|
for i, v := range GetPackets(true, 1, 100, nil) {
|
||||||
m.packets[i], _ = ParsePacket(v)
|
m.packets[i] = v
|
||||||
}
|
}
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
@@ -235,19 +328,18 @@ func BenchmarkPacketParseAndSort(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkMessageParserWithoutHint(b *testing.B) {
|
func BenchmarkMessageParserWithoutHint(b *testing.B) {
|
||||||
|
// runtime.GOMAXPROCS(8)
|
||||||
var mssg = make(chan *Message, 1)
|
var mssg = make(chan *Message, 1)
|
||||||
var chunk = []byte("111111111111111111111111111111")
|
var chunk = []byte("111111111111111111111111111111")
|
||||||
packets := GetPackets(1, 1000, chunk)
|
packets := GetPackets(true, 1, 1000, chunk)
|
||||||
packets[0].TransLayer[13] = 2 // SYN flag
|
p := NewMessageParser(1<<20, time.Second*2, nil, func(m *Message) {
|
||||||
packets[1000-1].TransLayer[13] = 1 // FIN flag
|
|
||||||
p := NewMessagePool(1<<20, time.Second*2, nil, func(m *Message) {
|
|
||||||
mssg <- m
|
mssg <- m
|
||||||
})
|
})
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
b.ReportMetric(float64(1000), "packets/op")
|
b.ReportMetric(float64(1000), "packets/op")
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
for _, v := range packets {
|
for _, v := range packets {
|
||||||
p.Handler(v)
|
p.PacketHandler(v)
|
||||||
}
|
}
|
||||||
<-mssg
|
<-mssg
|
||||||
}
|
}
|
||||||
@@ -261,46 +353,33 @@ func BenchmarkMessageParserWithHint(b *testing.B) {
|
|||||||
buf[i] = chunk
|
buf[i] = chunk
|
||||||
}
|
}
|
||||||
buf[1001] = []byte("0\r\n\r\n")
|
buf[1001] = []byte("0\r\n\r\n")
|
||||||
packets := make([]*capture.Packet, len(buf))
|
packets := make([]*Packet, len(buf))
|
||||||
for i := 0; i < len(buf); i++ {
|
for i := 0; i < len(buf); i++ {
|
||||||
packets[i] = GetPackets(uint32(i+10), 1, buf[i])[0]
|
packets[i] = GetPackets(false, 1, 1, buf[i])[0]
|
||||||
}
|
}
|
||||||
var mssg = make(chan *Message, 1)
|
var mssg = make(chan *Message, 1)
|
||||||
pool := NewMessagePool(1<<30, time.Second*10, nil, func(m *Message) { mssg <- m })
|
parser := NewMessageParser(1<<30, time.Second*10, nil, func(m *Message) { mssg <- m })
|
||||||
pool.Start = func(pckt *Packet) (bool, bool) {
|
parser.Start = func(pckt *Packet) (bool, bool) {
|
||||||
return false, proto.HasResponseTitle(pckt.Payload)
|
return false, proto.HasResponseTitle(pckt.Payload)
|
||||||
}
|
}
|
||||||
pool.End = func(m *Message) bool {
|
parser.End = func(m *Message) bool {
|
||||||
return proto.HasFullPayload(m.Data(), m)
|
return proto.HasFullPayload(m, m.Data())
|
||||||
}
|
}
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
b.ReportMetric(float64(len(packets)), "packets/op")
|
b.ReportMetric(float64(len(packets)), "packets/op")
|
||||||
b.ReportMetric(float64(1000), "chunks/op")
|
b.ReportMetric(float64(1000), "chunks/op")
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
for j := range packets {
|
for j := range packets {
|
||||||
pool.Handler(packets[j])
|
parser.PacketHandler(packets[j])
|
||||||
}
|
}
|
||||||
<-mssg
|
<-mssg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkNewAndParsePacket(b *testing.B) {
|
func BenchmarkNewAndParsePacket(b *testing.B) {
|
||||||
data := append(generateHeader(1024, 10), make([]byte, 10)...)
|
data := append(generateHeader(true, 1024, 10), make([]byte, 10)...)
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
ParsePacket(capture.NewPacket(data, int(layers.LinkTypeLoop), 4, &gopacket.CaptureInfo{}))
|
ParsePacket(data, int(layers.LinkTypeLoop), 4, &gopacket.CaptureInfo{})
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkNewPacket(b *testing.B) {
|
|
||||||
packet := capture.NewPacket(
|
|
||||||
append(generateHeader(1024, 10), make([]byte, 10)...),
|
|
||||||
int(layers.LinkTypeLoop),
|
|
||||||
4,
|
|
||||||
&gopacket.CaptureInfo{},
|
|
||||||
)
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
ParsePacket(packet)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-27
@@ -1,27 +0,0 @@
|
|||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
|
||||||
*.o
|
|
||||||
*.a
|
|
||||||
*.so
|
|
||||||
*.test
|
|
||||||
|
|
||||||
# Folders
|
|
||||||
_obj
|
|
||||||
_test
|
|
||||||
.vagrant
|
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
|
||||||
*.cgo2.c
|
|
||||||
_cgo_defun.c
|
|
||||||
_cgo_gotypes.go
|
|
||||||
_cgo_export.*
|
|
||||||
|
|
||||||
_testmain.go
|
|
||||||
|
|
||||||
*.exe
|
|
||||||
|
|
||||||
coverage.txt
|
|
||||||
profile.out
|
|
||||||
-74
@@ -1,74 +0,0 @@
|
|||||||
run:
|
|
||||||
timeout: 5m
|
|
||||||
deadline: 10m
|
|
||||||
|
|
||||||
linters-settings:
|
|
||||||
govet:
|
|
||||||
check-shadowing: false
|
|
||||||
golint:
|
|
||||||
min-confidence: 0
|
|
||||||
gocyclo:
|
|
||||||
min-complexity: 99
|
|
||||||
maligned:
|
|
||||||
suggest-new: true
|
|
||||||
dupl:
|
|
||||||
threshold: 100
|
|
||||||
goconst:
|
|
||||||
min-len: 2
|
|
||||||
min-occurrences: 3
|
|
||||||
misspell:
|
|
||||||
locale: US
|
|
||||||
goimports:
|
|
||||||
local-prefixes: github.com/Shopify/sarama
|
|
||||||
gocritic:
|
|
||||||
enabled-tags:
|
|
||||||
- diagnostic
|
|
||||||
- experimental
|
|
||||||
- opinionated
|
|
||||||
- performance
|
|
||||||
- style
|
|
||||||
disabled-checks:
|
|
||||||
- wrapperFunc
|
|
||||||
- ifElseChain
|
|
||||||
funlen:
|
|
||||||
lines: 300
|
|
||||||
statements: 300
|
|
||||||
|
|
||||||
linters:
|
|
||||||
disable-all: true
|
|
||||||
enable:
|
|
||||||
- bodyclose
|
|
||||||
- deadcode
|
|
||||||
- depguard
|
|
||||||
- dogsled
|
|
||||||
# - dupl
|
|
||||||
- errcheck
|
|
||||||
- funlen
|
|
||||||
# - gocritic
|
|
||||||
- gocyclo
|
|
||||||
- gofmt
|
|
||||||
- goimports
|
|
||||||
# - golint
|
|
||||||
- gosec
|
|
||||||
# - gosimple
|
|
||||||
- govet
|
|
||||||
# - ineffassign
|
|
||||||
- interfacer
|
|
||||||
# - misspell
|
|
||||||
# - nakedret
|
|
||||||
# - scopelint
|
|
||||||
# - staticcheck
|
|
||||||
- structcheck
|
|
||||||
# - stylecheck
|
|
||||||
- typecheck
|
|
||||||
- unconvert
|
|
||||||
- unused
|
|
||||||
- varcheck
|
|
||||||
- whitespace
|
|
||||||
# - goconst
|
|
||||||
# - gochecknoinits
|
|
||||||
|
|
||||||
issues:
|
|
||||||
exclude:
|
|
||||||
- consider giving a name to these results
|
|
||||||
- include an explanation for nolint directive
|
|
||||||
-912
@@ -1,912 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
#### Unreleased
|
|
||||||
|
|
||||||
#### Version 1.26.1 (2020-02-04)
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Add requests-in-flight metric ([1539](https://github.com/Shopify/sarama/pull/1539))
|
|
||||||
- Fix misleading example for cluster admin ([1595](https://github.com/Shopify/sarama/pull/1595))
|
|
||||||
- Replace Travis with GitHub Actions, linters housekeeping ([1573](https://github.com/Shopify/sarama/pull/1573))
|
|
||||||
- Allow BalanceStrategy to provide custom assignment data ([1592](https://github.com/Shopify/sarama/pull/1592))
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Adds back Consumer.Offsets.CommitInterval to fix API ([1590](https://github.com/Shopify/sarama/pull/1590))
|
|
||||||
- Fix error message s/CommitInterval/AutoCommit.Interval ([1589](https://github.com/Shopify/sarama/pull/1589))
|
|
||||||
|
|
||||||
#### Version 1.26.0 (2020-01-24)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Enable zstd compression
|
|
||||||
([1574](https://github.com/Shopify/sarama/pull/1574),
|
|
||||||
[1582](https://github.com/Shopify/sarama/pull/1582))
|
|
||||||
- Support headers in tools kafka-console-producer
|
|
||||||
([1549](https://github.com/Shopify/sarama/pull/1549))
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Add SASL AuthIdentity to SASL frames (authzid)
|
|
||||||
([1585](https://github.com/Shopify/sarama/pull/1585)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Sending messages with ZStd compression enabled fails in multiple ways
|
|
||||||
([1252](https://github.com/Shopify/sarama/issues/1252)).
|
|
||||||
- Use the broker for any admin on BrokerConfig
|
|
||||||
([1571](https://github.com/Shopify/sarama/pull/1571)).
|
|
||||||
- Set DescribeConfigRequest Version field
|
|
||||||
([1576](https://github.com/Shopify/sarama/pull/1576)).
|
|
||||||
- ConsumerGroup flooding logs with client/metadata update req
|
|
||||||
([1578](https://github.com/Shopify/sarama/pull/1578)).
|
|
||||||
- MetadataRequest version in DescribeCluster
|
|
||||||
([1580](https://github.com/Shopify/sarama/pull/1580)).
|
|
||||||
- Fix deadlock in consumer group handleError
|
|
||||||
([1581](https://github.com/Shopify/sarama/pull/1581))
|
|
||||||
- Fill in the Fetch{Request,Response} protocol
|
|
||||||
([1582](https://github.com/Shopify/sarama/pull/1582)).
|
|
||||||
- Retry topic request on ControllerNotAvailable
|
|
||||||
([1586](https://github.com/Shopify/sarama/pull/1586)).
|
|
||||||
|
|
||||||
#### Version 1.25.0 (2020-01-13)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Support TLS protocol in kafka-producer-performance
|
|
||||||
([1538](https://github.com/Shopify/sarama/pull/1538)).
|
|
||||||
- Add support for kafka 2.4.0
|
|
||||||
([1552](https://github.com/Shopify/sarama/pull/1552)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Allow the Consumer to disable auto-commit offsets
|
|
||||||
([1164](https://github.com/Shopify/sarama/pull/1164)).
|
|
||||||
- Produce records with consistent timestamps
|
|
||||||
([1455](https://github.com/Shopify/sarama/pull/1455)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix incorrect SetTopicMetadata name mentions
|
|
||||||
([1534](https://github.com/Shopify/sarama/pull/1534)).
|
|
||||||
- Fix client.tryRefreshMetadata Println
|
|
||||||
([1535](https://github.com/Shopify/sarama/pull/1535)).
|
|
||||||
- Fix panic on calling updateMetadata on closed client
|
|
||||||
([1531](https://github.com/Shopify/sarama/pull/1531)).
|
|
||||||
- Fix possible faulty metrics in TestFuncProducing
|
|
||||||
([1545](https://github.com/Shopify/sarama/pull/1545)).
|
|
||||||
|
|
||||||
#### Version 1.24.1 (2019-10-31)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Add DescribeLogDirs Request/Response pair
|
|
||||||
([1520](https://github.com/Shopify/sarama/pull/1520)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix ClusterAdmin returning invalid controller ID on DescribeCluster
|
|
||||||
([1518](https://github.com/Shopify/sarama/pull/1518)).
|
|
||||||
- Fix issue with consumergroup not rebalancing when new partition is added
|
|
||||||
([1525](https://github.com/Shopify/sarama/pull/1525)).
|
|
||||||
- Ensure consistent use of read/write deadlines
|
|
||||||
([1529](https://github.com/Shopify/sarama/pull/1529)).
|
|
||||||
|
|
||||||
#### Version 1.24.0 (2019-10-09)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Add sticky partition assignor
|
|
||||||
([1416](https://github.com/Shopify/sarama/pull/1416)).
|
|
||||||
- Switch from cgo zstd package to pure Go implementation
|
|
||||||
([1477](https://github.com/Shopify/sarama/pull/1477)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Allow creating ClusterAdmin from client
|
|
||||||
([1415](https://github.com/Shopify/sarama/pull/1415)).
|
|
||||||
- Set KafkaVersion in ListAcls method
|
|
||||||
([1452](https://github.com/Shopify/sarama/pull/1452)).
|
|
||||||
- Set request version in CreateACL ClusterAdmin method
|
|
||||||
([1458](https://github.com/Shopify/sarama/pull/1458)).
|
|
||||||
- Set request version in DeleteACL ClusterAdmin method
|
|
||||||
([1461](https://github.com/Shopify/sarama/pull/1461)).
|
|
||||||
- Handle missed error codes on TopicMetaDataRequest and GroupCoordinatorRequest
|
|
||||||
([1464](https://github.com/Shopify/sarama/pull/1464)).
|
|
||||||
- Remove direct usage of gofork
|
|
||||||
([1465](https://github.com/Shopify/sarama/pull/1465)).
|
|
||||||
- Add support for Go 1.13
|
|
||||||
([1478](https://github.com/Shopify/sarama/pull/1478)).
|
|
||||||
- Improve behavior of NewMockListAclsResponse
|
|
||||||
([1481](https://github.com/Shopify/sarama/pull/1481)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix race condition in consumergroup example
|
|
||||||
([1434](https://github.com/Shopify/sarama/pull/1434)).
|
|
||||||
- Fix brokerProducer goroutine leak
|
|
||||||
([1442](https://github.com/Shopify/sarama/pull/1442)).
|
|
||||||
- Use released version of lz4 library
|
|
||||||
([1469](https://github.com/Shopify/sarama/pull/1469)).
|
|
||||||
- Set correct version in MockDeleteTopicsResponse
|
|
||||||
([1484](https://github.com/Shopify/sarama/pull/1484)).
|
|
||||||
- Fix CLI help message typo
|
|
||||||
([1494](https://github.com/Shopify/sarama/pull/1494)).
|
|
||||||
|
|
||||||
Known Issues:
|
|
||||||
- Please **don't** use Zstd, as it doesn't work right now.
|
|
||||||
See https://github.com/Shopify/sarama/issues/1252
|
|
||||||
|
|
||||||
#### Version 1.23.1 (2019-07-22)
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix fetch delete bug record
|
|
||||||
([1425](https://github.com/Shopify/sarama/pull/1425)).
|
|
||||||
- Handle SASL/OAUTHBEARER token rejection
|
|
||||||
([1428](https://github.com/Shopify/sarama/pull/1428)).
|
|
||||||
|
|
||||||
#### Version 1.23.0 (2019-07-02)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Add support for Kafka 2.3.0
|
|
||||||
([1418](https://github.com/Shopify/sarama/pull/1418)).
|
|
||||||
- Add support for ListConsumerGroupOffsets v2
|
|
||||||
([1374](https://github.com/Shopify/sarama/pull/1374)).
|
|
||||||
- Add support for DeleteConsumerGroup
|
|
||||||
([1417](https://github.com/Shopify/sarama/pull/1417)).
|
|
||||||
- Add support for SASLVersion configuration
|
|
||||||
([1410](https://github.com/Shopify/sarama/pull/1410)).
|
|
||||||
- Add kerberos support
|
|
||||||
([1366](https://github.com/Shopify/sarama/pull/1366)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Improve sasl_scram_client example
|
|
||||||
([1406](https://github.com/Shopify/sarama/pull/1406)).
|
|
||||||
- Fix shutdown and race-condition in consumer-group example
|
|
||||||
([1404](https://github.com/Shopify/sarama/pull/1404)).
|
|
||||||
- Add support for error codes 77—81
|
|
||||||
([1397](https://github.com/Shopify/sarama/pull/1397)).
|
|
||||||
- Pool internal objects allocated per message
|
|
||||||
([1385](https://github.com/Shopify/sarama/pull/1385)).
|
|
||||||
- Reduce packet decoder allocations
|
|
||||||
([1373](https://github.com/Shopify/sarama/pull/1373)).
|
|
||||||
- Support timeout when fetching metadata
|
|
||||||
([1359](https://github.com/Shopify/sarama/pull/1359)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix fetch size integer overflow
|
|
||||||
([1376](https://github.com/Shopify/sarama/pull/1376)).
|
|
||||||
- Handle and log throttled FetchResponses
|
|
||||||
([1383](https://github.com/Shopify/sarama/pull/1383)).
|
|
||||||
- Refactor misspelled word Resouce to Resource
|
|
||||||
([1368](https://github.com/Shopify/sarama/pull/1368)).
|
|
||||||
|
|
||||||
#### Version 1.22.1 (2019-04-29)
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Use zstd 1.3.8
|
|
||||||
([1350](https://github.com/Shopify/sarama/pull/1350)).
|
|
||||||
- Add support for SaslHandshakeRequest v1
|
|
||||||
([1354](https://github.com/Shopify/sarama/pull/1354)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix V5 MetadataRequest nullable topics array
|
|
||||||
([1353](https://github.com/Shopify/sarama/pull/1353)).
|
|
||||||
- Use a different SCRAM client for each broker connection
|
|
||||||
([1349](https://github.com/Shopify/sarama/pull/1349)).
|
|
||||||
- Fix AllowAutoTopicCreation for MetadataRequest greater than v3
|
|
||||||
([1344](https://github.com/Shopify/sarama/pull/1344)).
|
|
||||||
|
|
||||||
#### Version 1.22.0 (2019-04-09)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Add Offline Replicas Operation to Client
|
|
||||||
([1318](https://github.com/Shopify/sarama/pull/1318)).
|
|
||||||
- Allow using proxy when connecting to broker
|
|
||||||
([1326](https://github.com/Shopify/sarama/pull/1326)).
|
|
||||||
- Implement ReadCommitted
|
|
||||||
([1307](https://github.com/Shopify/sarama/pull/1307)).
|
|
||||||
- Add support for Kafka 2.2.0
|
|
||||||
([1331](https://github.com/Shopify/sarama/pull/1331)).
|
|
||||||
- Add SASL SCRAM-SHA-512 and SCRAM-SHA-256 mechanismes
|
|
||||||
([1331](https://github.com/Shopify/sarama/pull/1295)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Unregister all broker metrics on broker stop
|
|
||||||
([1232](https://github.com/Shopify/sarama/pull/1232)).
|
|
||||||
- Add SCRAM authentication example
|
|
||||||
([1303](https://github.com/Shopify/sarama/pull/1303)).
|
|
||||||
- Add consumergroup examples
|
|
||||||
([1304](https://github.com/Shopify/sarama/pull/1304)).
|
|
||||||
- Expose consumer batch size metric
|
|
||||||
([1296](https://github.com/Shopify/sarama/pull/1296)).
|
|
||||||
- Add TLS options to console producer and consumer
|
|
||||||
([1300](https://github.com/Shopify/sarama/pull/1300)).
|
|
||||||
- Reduce client close bookkeeping
|
|
||||||
([1297](https://github.com/Shopify/sarama/pull/1297)).
|
|
||||||
- Satisfy error interface in create responses
|
|
||||||
([1154](https://github.com/Shopify/sarama/pull/1154)).
|
|
||||||
- Please lint gods
|
|
||||||
([1346](https://github.com/Shopify/sarama/pull/1346)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix multi consumer group instance crash
|
|
||||||
([1338](https://github.com/Shopify/sarama/pull/1338)).
|
|
||||||
- Update lz4 to latest version
|
|
||||||
([1347](https://github.com/Shopify/sarama/pull/1347)).
|
|
||||||
- Retry ErrNotCoordinatorForConsumer in new consumergroup session
|
|
||||||
([1231](https://github.com/Shopify/sarama/pull/1231)).
|
|
||||||
- Fix cleanup error handler
|
|
||||||
([1332](https://github.com/Shopify/sarama/pull/1332)).
|
|
||||||
- Fix rate condition in PartitionConsumer
|
|
||||||
([1156](https://github.com/Shopify/sarama/pull/1156)).
|
|
||||||
|
|
||||||
#### Version 1.21.0 (2019-02-24)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Add CreateAclRequest, DescribeAclRequest, DeleteAclRequest
|
|
||||||
([1236](https://github.com/Shopify/sarama/pull/1236)).
|
|
||||||
- Add DescribeTopic, DescribeConsumerGroup, ListConsumerGroups, ListConsumerGroupOffsets admin requests
|
|
||||||
([1178](https://github.com/Shopify/sarama/pull/1178)).
|
|
||||||
- Implement SASL/OAUTHBEARER
|
|
||||||
([1240](https://github.com/Shopify/sarama/pull/1240)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Add Go mod support
|
|
||||||
([1282](https://github.com/Shopify/sarama/pull/1282)).
|
|
||||||
- Add error codes 73—76
|
|
||||||
([1239](https://github.com/Shopify/sarama/pull/1239)).
|
|
||||||
- Add retry backoff function
|
|
||||||
([1160](https://github.com/Shopify/sarama/pull/1160)).
|
|
||||||
- Maintain metadata in the producer even when retries are disabled
|
|
||||||
([1189](https://github.com/Shopify/sarama/pull/1189)).
|
|
||||||
- Include ReplicaAssignment in ListTopics
|
|
||||||
([1274](https://github.com/Shopify/sarama/pull/1274)).
|
|
||||||
- Add producer performance tool
|
|
||||||
([1222](https://github.com/Shopify/sarama/pull/1222)).
|
|
||||||
- Add support LogAppend timestamps
|
|
||||||
([1258](https://github.com/Shopify/sarama/pull/1258)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix potential deadlock when a heartbeat request fails
|
|
||||||
([1286](https://github.com/Shopify/sarama/pull/1286)).
|
|
||||||
- Fix consuming compacted topic
|
|
||||||
([1227](https://github.com/Shopify/sarama/pull/1227)).
|
|
||||||
- Set correct Kafka version for DescribeConfigsRequest v1
|
|
||||||
([1277](https://github.com/Shopify/sarama/pull/1277)).
|
|
||||||
- Update kafka test version
|
|
||||||
([1273](https://github.com/Shopify/sarama/pull/1273)).
|
|
||||||
|
|
||||||
#### Version 1.20.1 (2019-01-10)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Add optional replica id in offset request
|
|
||||||
([1100](https://github.com/Shopify/sarama/pull/1100)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Implement DescribeConfigs Request + Response v1 & v2
|
|
||||||
([1230](https://github.com/Shopify/sarama/pull/1230)).
|
|
||||||
- Reuse compression objects
|
|
||||||
([1185](https://github.com/Shopify/sarama/pull/1185)).
|
|
||||||
- Switch from png to svg for GoDoc link in README
|
|
||||||
([1243](https://github.com/Shopify/sarama/pull/1243)).
|
|
||||||
- Fix typo in deprecation notice for FetchResponseBlock.Records
|
|
||||||
([1242](https://github.com/Shopify/sarama/pull/1242)).
|
|
||||||
- Fix typos in consumer metadata response file
|
|
||||||
([1244](https://github.com/Shopify/sarama/pull/1244)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Revert to individual msg retries for non-idempotent
|
|
||||||
([1203](https://github.com/Shopify/sarama/pull/1203)).
|
|
||||||
- Respect MaxMessageBytes limit for uncompressed messages
|
|
||||||
([1141](https://github.com/Shopify/sarama/pull/1141)).
|
|
||||||
|
|
||||||
#### Version 1.20.0 (2018-12-10)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Add support for zstd compression
|
|
||||||
([#1170](https://github.com/Shopify/sarama/pull/1170)).
|
|
||||||
- Add support for Idempotent Producer
|
|
||||||
([#1152](https://github.com/Shopify/sarama/pull/1152)).
|
|
||||||
- Add support support for Kafka 2.1.0
|
|
||||||
([#1229](https://github.com/Shopify/sarama/pull/1229)).
|
|
||||||
- Add support support for OffsetCommit request/response pairs versions v1 to v5
|
|
||||||
([#1201](https://github.com/Shopify/sarama/pull/1201)).
|
|
||||||
- Add support support for OffsetFetch request/response pair up to version v5
|
|
||||||
([#1198](https://github.com/Shopify/sarama/pull/1198)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Export broker's Rack setting
|
|
||||||
([#1173](https://github.com/Shopify/sarama/pull/1173)).
|
|
||||||
- Always use latest patch version of Go on CI
|
|
||||||
([#1202](https://github.com/Shopify/sarama/pull/1202)).
|
|
||||||
- Add error codes 61 to 72
|
|
||||||
([#1195](https://github.com/Shopify/sarama/pull/1195)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix build without cgo
|
|
||||||
([#1182](https://github.com/Shopify/sarama/pull/1182)).
|
|
||||||
- Fix go vet suggestion in consumer group file
|
|
||||||
([#1209](https://github.com/Shopify/sarama/pull/1209)).
|
|
||||||
- Fix typos in code and comments
|
|
||||||
([#1228](https://github.com/Shopify/sarama/pull/1228)).
|
|
||||||
|
|
||||||
#### Version 1.19.0 (2018-09-27)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Implement a higher-level consumer group
|
|
||||||
([#1099](https://github.com/Shopify/sarama/pull/1099)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Add support for Go 1.11
|
|
||||||
([#1176](https://github.com/Shopify/sarama/pull/1176)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix encoding of `MetadataResponse` with version 2 and higher
|
|
||||||
([#1174](https://github.com/Shopify/sarama/pull/1174)).
|
|
||||||
- Fix race condition in mock async producer
|
|
||||||
([#1174](https://github.com/Shopify/sarama/pull/1174)).
|
|
||||||
|
|
||||||
#### Version 1.18.0 (2018-09-07)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Make `Partitioner.RequiresConsistency` vary per-message
|
|
||||||
([#1112](https://github.com/Shopify/sarama/pull/1112)).
|
|
||||||
- Add customizable partitioner
|
|
||||||
([#1118](https://github.com/Shopify/sarama/pull/1118)).
|
|
||||||
- Add `ClusterAdmin` support for `CreateTopic`, `DeleteTopic`, `CreatePartitions`,
|
|
||||||
`DeleteRecords`, `DescribeConfig`, `AlterConfig`, `CreateACL`, `ListAcls`, `DeleteACL`
|
|
||||||
([#1055](https://github.com/Shopify/sarama/pull/1055)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Add support for Kafka 2.0.0
|
|
||||||
([#1149](https://github.com/Shopify/sarama/pull/1149)).
|
|
||||||
- Allow setting `LocalAddr` when dialing an address to support multi-homed hosts
|
|
||||||
([#1123](https://github.com/Shopify/sarama/pull/1123)).
|
|
||||||
- Simpler offset management
|
|
||||||
([#1127](https://github.com/Shopify/sarama/pull/1127)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix mutation of `ProducerMessage.MetaData` when producing to Kafka
|
|
||||||
([#1110](https://github.com/Shopify/sarama/pull/1110)).
|
|
||||||
- Fix consumer block when response did not contain all the
|
|
||||||
expected topic/partition blocks
|
|
||||||
([#1086](https://github.com/Shopify/sarama/pull/1086)).
|
|
||||||
- Fix consumer block when response contains only constrol messages
|
|
||||||
([#1115](https://github.com/Shopify/sarama/pull/1115)).
|
|
||||||
- Add timeout config for ClusterAdmin requests
|
|
||||||
([#1142](https://github.com/Shopify/sarama/pull/1142)).
|
|
||||||
- Add version check when producing message with headers
|
|
||||||
([#1117](https://github.com/Shopify/sarama/pull/1117)).
|
|
||||||
- Fix `MetadataRequest` for empty list of topics
|
|
||||||
([#1132](https://github.com/Shopify/sarama/pull/1132)).
|
|
||||||
- Fix producer topic metadata on-demand fetch when topic error happens in metadata response
|
|
||||||
([#1125](https://github.com/Shopify/sarama/pull/1125)).
|
|
||||||
|
|
||||||
#### Version 1.17.0 (2018-05-30)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Add support for gzip compression levels
|
|
||||||
([#1044](https://github.com/Shopify/sarama/pull/1044)).
|
|
||||||
- Add support for Metadata request/response pairs versions v1 to v5
|
|
||||||
([#1047](https://github.com/Shopify/sarama/pull/1047),
|
|
||||||
[#1069](https://github.com/Shopify/sarama/pull/1069)).
|
|
||||||
- Add versioning to JoinGroup request/response pairs
|
|
||||||
([#1098](https://github.com/Shopify/sarama/pull/1098))
|
|
||||||
- Add support for CreatePartitions, DeleteGroups, DeleteRecords request/response pairs
|
|
||||||
([#1065](https://github.com/Shopify/sarama/pull/1065),
|
|
||||||
[#1096](https://github.com/Shopify/sarama/pull/1096),
|
|
||||||
[#1027](https://github.com/Shopify/sarama/pull/1027)).
|
|
||||||
- Add `Controller()` method to Client interface
|
|
||||||
([#1063](https://github.com/Shopify/sarama/pull/1063)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- ConsumerMetadataReq/Resp has been migrated to FindCoordinatorReq/Resp
|
|
||||||
([#1010](https://github.com/Shopify/sarama/pull/1010)).
|
|
||||||
- Expose missing protocol parts: `msgSet` and `recordBatch`
|
|
||||||
([#1049](https://github.com/Shopify/sarama/pull/1049)).
|
|
||||||
- Add support for v1 DeleteTopics Request
|
|
||||||
([#1052](https://github.com/Shopify/sarama/pull/1052)).
|
|
||||||
- Add support for Go 1.10
|
|
||||||
([#1064](https://github.com/Shopify/sarama/pull/1064)).
|
|
||||||
- Claim support for Kafka 1.1.0
|
|
||||||
([#1073](https://github.com/Shopify/sarama/pull/1073)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix FindCoordinatorResponse.encode to allow nil Coordinator
|
|
||||||
([#1050](https://github.com/Shopify/sarama/pull/1050),
|
|
||||||
[#1051](https://github.com/Shopify/sarama/pull/1051)).
|
|
||||||
- Clear all metadata when we have the latest topic info
|
|
||||||
([#1033](https://github.com/Shopify/sarama/pull/1033)).
|
|
||||||
- Make `PartitionConsumer.Close` idempotent
|
|
||||||
([#1092](https://github.com/Shopify/sarama/pull/1092)).
|
|
||||||
|
|
||||||
#### Version 1.16.0 (2018-02-12)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Add support for the Create/Delete Topics request/response pairs
|
|
||||||
([#1007](https://github.com/Shopify/sarama/pull/1007),
|
|
||||||
[#1008](https://github.com/Shopify/sarama/pull/1008)).
|
|
||||||
- Add support for the Describe/Create/Delete ACL request/response pairs
|
|
||||||
([#1009](https://github.com/Shopify/sarama/pull/1009)).
|
|
||||||
- Add support for the five transaction-related request/response pairs
|
|
||||||
([#1016](https://github.com/Shopify/sarama/pull/1016)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Permit setting version on mock producer responses
|
|
||||||
([#999](https://github.com/Shopify/sarama/pull/999)).
|
|
||||||
- Add `NewMockBrokerListener` helper for testing TLS connections
|
|
||||||
([#1019](https://github.com/Shopify/sarama/pull/1019)).
|
|
||||||
- Changed the default value for `Consumer.Fetch.Default` from 32KiB to 1MiB
|
|
||||||
which results in much higher throughput in most cases
|
|
||||||
([#1024](https://github.com/Shopify/sarama/pull/1024)).
|
|
||||||
- Reuse the `time.Ticker` across fetch requests in the PartitionConsumer to
|
|
||||||
reduce CPU and memory usage when processing many partitions
|
|
||||||
([#1028](https://github.com/Shopify/sarama/pull/1028)).
|
|
||||||
- Assign relative offsets to messages in the producer to save the brokers a
|
|
||||||
recompression pass
|
|
||||||
([#1002](https://github.com/Shopify/sarama/pull/1002),
|
|
||||||
[#1015](https://github.com/Shopify/sarama/pull/1015)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix producing uncompressed batches with the new protocol format
|
|
||||||
([#1032](https://github.com/Shopify/sarama/issues/1032)).
|
|
||||||
- Fix consuming compacted topics with the new protocol format
|
|
||||||
([#1005](https://github.com/Shopify/sarama/issues/1005)).
|
|
||||||
- Fix consuming topics with a mix of protocol formats
|
|
||||||
([#1021](https://github.com/Shopify/sarama/issues/1021)).
|
|
||||||
- Fix consuming when the broker includes multiple batches in a single response
|
|
||||||
([#1022](https://github.com/Shopify/sarama/issues/1022)).
|
|
||||||
- Fix detection of `PartialTrailingMessage` when the partial message was
|
|
||||||
truncated before the magic value indicating its version
|
|
||||||
([#1030](https://github.com/Shopify/sarama/pull/1030)).
|
|
||||||
- Fix expectation-checking in the mock of `SyncProducer.SendMessages`
|
|
||||||
([#1035](https://github.com/Shopify/sarama/pull/1035)).
|
|
||||||
|
|
||||||
#### Version 1.15.0 (2017-12-08)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Claim official support for Kafka 1.0, though it did already work
|
|
||||||
([#984](https://github.com/Shopify/sarama/pull/984)).
|
|
||||||
- Helper methods for Kafka version numbers to/from strings
|
|
||||||
([#989](https://github.com/Shopify/sarama/pull/989)).
|
|
||||||
- Implement CreatePartitions request/response
|
|
||||||
([#985](https://github.com/Shopify/sarama/pull/985)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Add error codes 45-60
|
|
||||||
([#986](https://github.com/Shopify/sarama/issues/986)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix slow consuming for certain Kafka 0.11/1.0 configurations
|
|
||||||
([#982](https://github.com/Shopify/sarama/pull/982)).
|
|
||||||
- Correctly determine when a FetchResponse contains the new message format
|
|
||||||
([#990](https://github.com/Shopify/sarama/pull/990)).
|
|
||||||
- Fix producing with multiple headers
|
|
||||||
([#996](https://github.com/Shopify/sarama/pull/996)).
|
|
||||||
- Fix handling of truncated record batches
|
|
||||||
([#998](https://github.com/Shopify/sarama/pull/998)).
|
|
||||||
- Fix leaking metrics when closing brokers
|
|
||||||
([#991](https://github.com/Shopify/sarama/pull/991)).
|
|
||||||
|
|
||||||
#### Version 1.14.0 (2017-11-13)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Add support for the new Kafka 0.11 record-batch format, including the wire
|
|
||||||
protocol and the necessary behavioural changes in the producer and consumer.
|
|
||||||
Transactions and idempotency are not yet supported, but producing and
|
|
||||||
consuming should work with all the existing bells and whistles (batching,
|
|
||||||
compression, etc) as well as the new custom headers. Thanks to Vlad Hanciuta
|
|
||||||
of Arista Networks for this work. Part of
|
|
||||||
([#901](https://github.com/Shopify/sarama/issues/901)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix encoding of ProduceResponse versions in test
|
|
||||||
([#970](https://github.com/Shopify/sarama/pull/970)).
|
|
||||||
- Return partial replicas list when we have it
|
|
||||||
([#975](https://github.com/Shopify/sarama/pull/975)).
|
|
||||||
|
|
||||||
#### Version 1.13.0 (2017-10-04)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Support for FetchRequest version 3
|
|
||||||
([#905](https://github.com/Shopify/sarama/pull/905)).
|
|
||||||
- Permit setting version on mock FetchResponses
|
|
||||||
([#939](https://github.com/Shopify/sarama/pull/939)).
|
|
||||||
- Add a configuration option to support storing only minimal metadata for
|
|
||||||
extremely large clusters
|
|
||||||
([#937](https://github.com/Shopify/sarama/pull/937)).
|
|
||||||
- Add `PartitionOffsetManager.ResetOffset` for backtracking tracked offsets
|
|
||||||
([#932](https://github.com/Shopify/sarama/pull/932)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Provide the block-level timestamp when consuming compressed messages
|
|
||||||
([#885](https://github.com/Shopify/sarama/issues/885)).
|
|
||||||
- `Client.Replicas` and `Client.InSyncReplicas` now respect the order returned
|
|
||||||
by the broker, which can be meaningful
|
|
||||||
([#930](https://github.com/Shopify/sarama/pull/930)).
|
|
||||||
- Use a `Ticker` to reduce consumer timer overhead at the cost of higher
|
|
||||||
variance in the actual timeout
|
|
||||||
([#933](https://github.com/Shopify/sarama/pull/933)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Gracefully handle messages with negative timestamps
|
|
||||||
([#907](https://github.com/Shopify/sarama/pull/907)).
|
|
||||||
- Raise a proper error when encountering an unknown message version
|
|
||||||
([#940](https://github.com/Shopify/sarama/pull/940)).
|
|
||||||
|
|
||||||
#### Version 1.12.0 (2017-05-08)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Added support for the `ApiVersions` request and response pair, and Kafka
|
|
||||||
version 0.10.2 ([#867](https://github.com/Shopify/sarama/pull/867)). Note
|
|
||||||
that you still need to specify the Kafka version in the Sarama configuration
|
|
||||||
for the time being.
|
|
||||||
- Added a `Brokers` method to the Client which returns the complete set of
|
|
||||||
active brokers ([#813](https://github.com/Shopify/sarama/pull/813)).
|
|
||||||
- Added an `InSyncReplicas` method to the Client which returns the set of all
|
|
||||||
in-sync broker IDs for the given partition, now that the Kafka versions for
|
|
||||||
which this was misleading are no longer in our supported set
|
|
||||||
([#872](https://github.com/Shopify/sarama/pull/872)).
|
|
||||||
- Added a `NewCustomHashPartitioner` method which allows constructing a hash
|
|
||||||
partitioner with a custom hash method in case the default (FNV-1a) is not
|
|
||||||
suitable
|
|
||||||
([#837](https://github.com/Shopify/sarama/pull/837),
|
|
||||||
[#841](https://github.com/Shopify/sarama/pull/841)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Recognize more Kafka error codes
|
|
||||||
([#859](https://github.com/Shopify/sarama/pull/859)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix an issue where decoding a malformed FetchRequest would not return the
|
|
||||||
correct error ([#818](https://github.com/Shopify/sarama/pull/818)).
|
|
||||||
- Respect ordering of group protocols in JoinGroupRequests. This fix is
|
|
||||||
transparent if you're using the `AddGroupProtocol` or
|
|
||||||
`AddGroupProtocolMetadata` helpers; otherwise you will need to switch from
|
|
||||||
the `GroupProtocols` field (now deprecated) to use `OrderedGroupProtocols`
|
|
||||||
([#812](https://github.com/Shopify/sarama/issues/812)).
|
|
||||||
- Fix an alignment-related issue with atomics on 32-bit architectures
|
|
||||||
([#859](https://github.com/Shopify/sarama/pull/859)).
|
|
||||||
|
|
||||||
#### Version 1.11.0 (2016-12-20)
|
|
||||||
|
|
||||||
_Important:_ As of Sarama 1.11 it is necessary to set the config value of
|
|
||||||
`Producer.Return.Successes` to true in order to use the SyncProducer. Previous
|
|
||||||
versions would silently override this value when instantiating a SyncProducer
|
|
||||||
which led to unexpected values and data races.
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Metrics! Thanks to Sébastien Launay for all his work on this feature
|
|
||||||
([#701](https://github.com/Shopify/sarama/pull/701),
|
|
||||||
[#746](https://github.com/Shopify/sarama/pull/746),
|
|
||||||
[#766](https://github.com/Shopify/sarama/pull/766)).
|
|
||||||
- Add support for LZ4 compression
|
|
||||||
([#786](https://github.com/Shopify/sarama/pull/786)).
|
|
||||||
- Add support for ListOffsetRequest v1 and Kafka 0.10.1
|
|
||||||
([#775](https://github.com/Shopify/sarama/pull/775)).
|
|
||||||
- Added a `HighWaterMarks` method to the Consumer which aggregates the
|
|
||||||
`HighWaterMarkOffset` values of its child topic/partitions
|
|
||||||
([#769](https://github.com/Shopify/sarama/pull/769)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fixed producing when using timestamps, compression and Kafka 0.10
|
|
||||||
([#759](https://github.com/Shopify/sarama/pull/759)).
|
|
||||||
- Added missing decoder methods to DescribeGroups response
|
|
||||||
([#756](https://github.com/Shopify/sarama/pull/756)).
|
|
||||||
- Fix producer shutdown when `Return.Errors` is disabled
|
|
||||||
([#787](https://github.com/Shopify/sarama/pull/787)).
|
|
||||||
- Don't mutate configuration in SyncProducer
|
|
||||||
([#790](https://github.com/Shopify/sarama/pull/790)).
|
|
||||||
- Fix crash on SASL initialization failure
|
|
||||||
([#795](https://github.com/Shopify/sarama/pull/795)).
|
|
||||||
|
|
||||||
#### Version 1.10.1 (2016-08-30)
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix the documentation for `HashPartitioner` which was incorrect
|
|
||||||
([#717](https://github.com/Shopify/sarama/pull/717)).
|
|
||||||
- Permit client creation even when it is limited by ACLs
|
|
||||||
([#722](https://github.com/Shopify/sarama/pull/722)).
|
|
||||||
- Several fixes to the consumer timer optimization code, regressions introduced
|
|
||||||
in v1.10.0. Go's timers are finicky
|
|
||||||
([#730](https://github.com/Shopify/sarama/pull/730),
|
|
||||||
[#733](https://github.com/Shopify/sarama/pull/733),
|
|
||||||
[#734](https://github.com/Shopify/sarama/pull/734)).
|
|
||||||
- Handle consuming compressed relative offsets with Kafka 0.10
|
|
||||||
([#735](https://github.com/Shopify/sarama/pull/735)).
|
|
||||||
|
|
||||||
#### Version 1.10.0 (2016-08-02)
|
|
||||||
|
|
||||||
_Important:_ As of Sarama 1.10 it is necessary to tell Sarama the version of
|
|
||||||
Kafka you are running against (via the `config.Version` value) in order to use
|
|
||||||
features that may not be compatible with old Kafka versions. If you don't
|
|
||||||
specify this value it will default to 0.8.2 (the minimum supported), and trying
|
|
||||||
to use more recent features (like the offset manager) will fail with an error.
|
|
||||||
|
|
||||||
_Also:_ The offset-manager's behaviour has been changed to match the upstream
|
|
||||||
java consumer (see [#705](https://github.com/Shopify/sarama/pull/705) and
|
|
||||||
[#713](https://github.com/Shopify/sarama/pull/713)). If you use the
|
|
||||||
offset-manager, please ensure that you are committing one *greater* than the
|
|
||||||
last consumed message offset or else you may end up consuming duplicate
|
|
||||||
messages.
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Support for Kafka 0.10
|
|
||||||
([#672](https://github.com/Shopify/sarama/pull/672),
|
|
||||||
[#678](https://github.com/Shopify/sarama/pull/678),
|
|
||||||
[#681](https://github.com/Shopify/sarama/pull/681), and others).
|
|
||||||
- Support for configuring the target Kafka version
|
|
||||||
([#676](https://github.com/Shopify/sarama/pull/676)).
|
|
||||||
- Batch producing support in the SyncProducer
|
|
||||||
([#677](https://github.com/Shopify/sarama/pull/677)).
|
|
||||||
- Extend producer mock to allow setting expectations on message contents
|
|
||||||
([#667](https://github.com/Shopify/sarama/pull/667)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Support `nil` compressed messages for deleting in compacted topics
|
|
||||||
([#634](https://github.com/Shopify/sarama/pull/634)).
|
|
||||||
- Pre-allocate decoding errors, greatly reducing heap usage and GC time against
|
|
||||||
misbehaving brokers ([#690](https://github.com/Shopify/sarama/pull/690)).
|
|
||||||
- Re-use consumer expiry timers, removing one allocation per consumed message
|
|
||||||
([#707](https://github.com/Shopify/sarama/pull/707)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Actually default the client ID to "sarama" like we say we do
|
|
||||||
([#664](https://github.com/Shopify/sarama/pull/664)).
|
|
||||||
- Fix a rare issue where `Client.Leader` could return the wrong error
|
|
||||||
([#685](https://github.com/Shopify/sarama/pull/685)).
|
|
||||||
- Fix a possible tight loop in the consumer
|
|
||||||
([#693](https://github.com/Shopify/sarama/pull/693)).
|
|
||||||
- Match upstream's offset-tracking behaviour
|
|
||||||
([#705](https://github.com/Shopify/sarama/pull/705)).
|
|
||||||
- Report UnknownTopicOrPartition errors from the offset manager
|
|
||||||
([#706](https://github.com/Shopify/sarama/pull/706)).
|
|
||||||
- Fix possible negative partition value from the HashPartitioner
|
|
||||||
([#709](https://github.com/Shopify/sarama/pull/709)).
|
|
||||||
|
|
||||||
#### Version 1.9.0 (2016-05-16)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Add support for custom offset manager retention durations
|
|
||||||
([#602](https://github.com/Shopify/sarama/pull/602)).
|
|
||||||
- Publish low-level mocks to enable testing of third-party producer/consumer
|
|
||||||
implementations ([#570](https://github.com/Shopify/sarama/pull/570)).
|
|
||||||
- Declare support for Golang 1.6
|
|
||||||
([#611](https://github.com/Shopify/sarama/pull/611)).
|
|
||||||
- Support for SASL plain-text auth
|
|
||||||
([#648](https://github.com/Shopify/sarama/pull/648)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Simplified broker locking scheme slightly
|
|
||||||
([#604](https://github.com/Shopify/sarama/pull/604)).
|
|
||||||
- Documentation cleanup
|
|
||||||
([#605](https://github.com/Shopify/sarama/pull/605),
|
|
||||||
[#621](https://github.com/Shopify/sarama/pull/621),
|
|
||||||
[#654](https://github.com/Shopify/sarama/pull/654)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix race condition shutting down the OffsetManager
|
|
||||||
([#658](https://github.com/Shopify/sarama/pull/658)).
|
|
||||||
|
|
||||||
#### Version 1.8.0 (2016-02-01)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Full support for Kafka 0.9:
|
|
||||||
- All protocol messages and fields
|
|
||||||
([#586](https://github.com/Shopify/sarama/pull/586),
|
|
||||||
[#588](https://github.com/Shopify/sarama/pull/588),
|
|
||||||
[#590](https://github.com/Shopify/sarama/pull/590)).
|
|
||||||
- Verified that TLS support works
|
|
||||||
([#581](https://github.com/Shopify/sarama/pull/581)).
|
|
||||||
- Fixed the OffsetManager compatibility
|
|
||||||
([#585](https://github.com/Shopify/sarama/pull/585)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Optimize for fewer system calls when reading from the network
|
|
||||||
([#584](https://github.com/Shopify/sarama/pull/584)).
|
|
||||||
- Automatically retry `InvalidMessage` errors to match upstream behaviour
|
|
||||||
([#589](https://github.com/Shopify/sarama/pull/589)).
|
|
||||||
|
|
||||||
#### Version 1.7.0 (2015-12-11)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Preliminary support for Kafka 0.9
|
|
||||||
([#572](https://github.com/Shopify/sarama/pull/572)). This comes with several
|
|
||||||
caveats:
|
|
||||||
- Protocol-layer support is mostly in place
|
|
||||||
([#577](https://github.com/Shopify/sarama/pull/577)), however Kafka 0.9
|
|
||||||
renamed some messages and fields, which we did not in order to preserve API
|
|
||||||
compatibility.
|
|
||||||
- The producer and consumer work against 0.9, but the offset manager does
|
|
||||||
not ([#573](https://github.com/Shopify/sarama/pull/573)).
|
|
||||||
- TLS support may or may not work
|
|
||||||
([#581](https://github.com/Shopify/sarama/pull/581)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Don't wait for request timeouts on dead brokers, greatly speeding recovery
|
|
||||||
when the TCP connection is left hanging
|
|
||||||
([#548](https://github.com/Shopify/sarama/pull/548)).
|
|
||||||
- Refactored part of the producer. The new version provides a much more elegant
|
|
||||||
solution to [#449](https://github.com/Shopify/sarama/pull/449). It is also
|
|
||||||
slightly more efficient, and much more precise in calculating batch sizes
|
|
||||||
when compression is used
|
|
||||||
([#549](https://github.com/Shopify/sarama/pull/549),
|
|
||||||
[#550](https://github.com/Shopify/sarama/pull/550),
|
|
||||||
[#551](https://github.com/Shopify/sarama/pull/551)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix race condition in consumer test mock
|
|
||||||
([#553](https://github.com/Shopify/sarama/pull/553)).
|
|
||||||
|
|
||||||
#### Version 1.6.1 (2015-09-25)
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix panic that could occur if a user-supplied message value failed to encode
|
|
||||||
([#449](https://github.com/Shopify/sarama/pull/449)).
|
|
||||||
|
|
||||||
#### Version 1.6.0 (2015-09-04)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- Implementation of a consumer offset manager using the APIs introduced in
|
|
||||||
Kafka 0.8.2. The API is designed mainly for integration into a future
|
|
||||||
high-level consumer, not for direct use, although it is *possible* to use it
|
|
||||||
directly.
|
|
||||||
([#461](https://github.com/Shopify/sarama/pull/461)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- CRC32 calculation is much faster on machines with SSE4.2 instructions,
|
|
||||||
removing a major hotspot from most profiles
|
|
||||||
([#255](https://github.com/Shopify/sarama/pull/255)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Make protocol decoding more robust against some malformed packets generated
|
|
||||||
by go-fuzz ([#523](https://github.com/Shopify/sarama/pull/523),
|
|
||||||
[#525](https://github.com/Shopify/sarama/pull/525)) or found in other ways
|
|
||||||
([#528](https://github.com/Shopify/sarama/pull/528)).
|
|
||||||
- Fix a potential race condition panic in the consumer on shutdown
|
|
||||||
([#529](https://github.com/Shopify/sarama/pull/529)).
|
|
||||||
|
|
||||||
#### Version 1.5.0 (2015-08-17)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- TLS-encrypted network connections are now supported. This feature is subject
|
|
||||||
to change when Kafka releases built-in TLS support, but for now this is
|
|
||||||
enough to work with TLS-terminating proxies
|
|
||||||
([#154](https://github.com/Shopify/sarama/pull/154)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- The consumer will not block if a single partition is not drained by the user;
|
|
||||||
all other partitions will continue to consume normally
|
|
||||||
([#485](https://github.com/Shopify/sarama/pull/485)).
|
|
||||||
- Formatting of error strings has been much improved
|
|
||||||
([#495](https://github.com/Shopify/sarama/pull/495)).
|
|
||||||
- Internal refactoring of the producer for code cleanliness and to enable
|
|
||||||
future work ([#300](https://github.com/Shopify/sarama/pull/300)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix a potential deadlock in the consumer on shutdown
|
|
||||||
([#475](https://github.com/Shopify/sarama/pull/475)).
|
|
||||||
|
|
||||||
#### Version 1.4.3 (2015-07-21)
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Don't include the partitioner in the producer's "fetch partitions"
|
|
||||||
circuit-breaker ([#466](https://github.com/Shopify/sarama/pull/466)).
|
|
||||||
- Don't retry messages until the broker is closed when abandoning a broker in
|
|
||||||
the producer ([#468](https://github.com/Shopify/sarama/pull/468)).
|
|
||||||
- Update the import path for snappy-go, it has moved again and the API has
|
|
||||||
changed slightly ([#486](https://github.com/Shopify/sarama/pull/486)).
|
|
||||||
|
|
||||||
#### Version 1.4.2 (2015-05-27)
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Update the import path for snappy-go, it has moved from google code to github
|
|
||||||
([#456](https://github.com/Shopify/sarama/pull/456)).
|
|
||||||
|
|
||||||
#### Version 1.4.1 (2015-05-25)
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Optimizations when decoding snappy messages, thanks to John Potocny
|
|
||||||
([#446](https://github.com/Shopify/sarama/pull/446)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix hypothetical race conditions on producer shutdown
|
|
||||||
([#450](https://github.com/Shopify/sarama/pull/450),
|
|
||||||
[#451](https://github.com/Shopify/sarama/pull/451)).
|
|
||||||
|
|
||||||
#### Version 1.4.0 (2015-05-01)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- The consumer now implements `Topics()` and `Partitions()` methods to enable
|
|
||||||
users to dynamically choose what topics/partitions to consume without
|
|
||||||
instantiating a full client
|
|
||||||
([#431](https://github.com/Shopify/sarama/pull/431)).
|
|
||||||
- The partition-consumer now exposes the high water mark offset value returned
|
|
||||||
by the broker via the `HighWaterMarkOffset()` method ([#339](https://github.com/Shopify/sarama/pull/339)).
|
|
||||||
- Added a `kafka-console-consumer` tool capable of handling multiple
|
|
||||||
partitions, and deprecated the now-obsolete `kafka-console-partitionConsumer`
|
|
||||||
([#439](https://github.com/Shopify/sarama/pull/439),
|
|
||||||
[#442](https://github.com/Shopify/sarama/pull/442)).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- The producer's logging during retry scenarios is more consistent, more
|
|
||||||
useful, and slightly less verbose
|
|
||||||
([#429](https://github.com/Shopify/sarama/pull/429)).
|
|
||||||
- The client now shuffles its initial list of seed brokers in order to prevent
|
|
||||||
thundering herd on the first broker in the list
|
|
||||||
([#441](https://github.com/Shopify/sarama/pull/441)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- The producer now correctly manages its state if retries occur when it is
|
|
||||||
shutting down, fixing several instances of confusing behaviour and at least
|
|
||||||
one potential deadlock ([#419](https://github.com/Shopify/sarama/pull/419)).
|
|
||||||
- The consumer now handles messages for different partitions asynchronously,
|
|
||||||
making it much more resilient to specific user code ordering
|
|
||||||
([#325](https://github.com/Shopify/sarama/pull/325)).
|
|
||||||
|
|
||||||
#### Version 1.3.0 (2015-04-16)
|
|
||||||
|
|
||||||
New Features:
|
|
||||||
- The client now tracks consumer group coordinators using
|
|
||||||
ConsumerMetadataRequests similar to how it tracks partition leadership using
|
|
||||||
regular MetadataRequests ([#411](https://github.com/Shopify/sarama/pull/411)).
|
|
||||||
This adds two methods to the client API:
|
|
||||||
- `Coordinator(consumerGroup string) (*Broker, error)`
|
|
||||||
- `RefreshCoordinator(consumerGroup string) error`
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- ConsumerMetadataResponses now automatically create a Broker object out of the
|
|
||||||
ID/address/port combination for the Coordinator; accessing the fields
|
|
||||||
individually has been deprecated
|
|
||||||
([#413](https://github.com/Shopify/sarama/pull/413)).
|
|
||||||
- Much improved handling of `OffsetOutOfRange` errors in the consumer.
|
|
||||||
Consumers will fail to start if the provided offset is out of range
|
|
||||||
([#418](https://github.com/Shopify/sarama/pull/418))
|
|
||||||
and they will automatically shut down if the offset falls out of range
|
|
||||||
([#424](https://github.com/Shopify/sarama/pull/424)).
|
|
||||||
- Small performance improvement in encoding and decoding protocol messages
|
|
||||||
([#427](https://github.com/Shopify/sarama/pull/427)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix a rare race condition in the client's background metadata refresher if
|
|
||||||
it happens to be activated while the client is being closed
|
|
||||||
([#422](https://github.com/Shopify/sarama/pull/422)).
|
|
||||||
|
|
||||||
#### Version 1.2.0 (2015-04-07)
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- The producer's behaviour when `Flush.Frequency` is set is now more intuitive
|
|
||||||
([#389](https://github.com/Shopify/sarama/pull/389)).
|
|
||||||
- The producer is now somewhat more memory-efficient during and after retrying
|
|
||||||
messages due to an improved queue implementation
|
|
||||||
([#396](https://github.com/Shopify/sarama/pull/396)).
|
|
||||||
- The consumer produces much more useful logging output when leadership
|
|
||||||
changes ([#385](https://github.com/Shopify/sarama/pull/385)).
|
|
||||||
- The client's `GetOffset` method will now automatically refresh metadata and
|
|
||||||
retry once in the event of stale information or similar
|
|
||||||
([#394](https://github.com/Shopify/sarama/pull/394)).
|
|
||||||
- Broker connections now have support for using TCP keepalives
|
|
||||||
([#407](https://github.com/Shopify/sarama/issues/407)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- The OffsetCommitRequest message now correctly implements all three possible
|
|
||||||
API versions ([#390](https://github.com/Shopify/sarama/pull/390),
|
|
||||||
[#400](https://github.com/Shopify/sarama/pull/400)).
|
|
||||||
|
|
||||||
#### Version 1.1.0 (2015-03-20)
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Wrap the producer's partitioner call in a circuit-breaker so that repeatedly
|
|
||||||
broken topics don't choke throughput
|
|
||||||
([#373](https://github.com/Shopify/sarama/pull/373)).
|
|
||||||
|
|
||||||
Bug Fixes:
|
|
||||||
- Fix the producer's internal reference counting in certain unusual scenarios
|
|
||||||
([#367](https://github.com/Shopify/sarama/pull/367)).
|
|
||||||
- Fix the consumer's internal reference counting in certain unusual scenarios
|
|
||||||
([#369](https://github.com/Shopify/sarama/pull/369)).
|
|
||||||
- Fix a condition where the producer's internal control messages could have
|
|
||||||
gotten stuck ([#368](https://github.com/Shopify/sarama/pull/368)).
|
|
||||||
- Fix an issue where invalid partition lists would be cached when asking for
|
|
||||||
metadata for a non-existant topic ([#372](https://github.com/Shopify/sarama/pull/372)).
|
|
||||||
|
|
||||||
|
|
||||||
#### Version 1.0.0 (2015-03-17)
|
|
||||||
|
|
||||||
Version 1.0.0 is the first tagged version, and is almost a complete rewrite. The primary differences with previous untagged versions are:
|
|
||||||
|
|
||||||
- The producer has been rewritten; there is now a `SyncProducer` with a blocking API, and an `AsyncProducer` that is non-blocking.
|
|
||||||
- The consumer has been rewritten to only open one connection per broker instead of one connection per partition.
|
|
||||||
- The main types of Sarama are now interfaces to make depedency injection easy; mock implementations for `Consumer`, `SyncProducer` and `AsyncProducer` are provided in the `github.com/Shopify/sarama/mocks` package.
|
|
||||||
- For most uses cases, it is no longer necessary to open a `Client`; this will be done for you.
|
|
||||||
- All the configuration values have been unified in the `Config` struct.
|
|
||||||
- Much improved test suite.
|
|
||||||
-20
@@ -1,20 +0,0 @@
|
|||||||
Copyright (c) 2013 Shopify
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
-27
@@ -1,27 +0,0 @@
|
|||||||
default: fmt get update test lint
|
|
||||||
|
|
||||||
GO := GO111MODULE=on GOPRIVATE=github.com/linkedin GOSUMDB=off go
|
|
||||||
GOBUILD := CGO_ENABLED=0 $(GO) build $(BUILD_FLAG)
|
|
||||||
GOTEST := $(GO) test -gcflags='-l' -p 3 -v -race -timeout 6m -coverprofile=profile.out -covermode=atomic
|
|
||||||
|
|
||||||
FILES := $(shell find . -name '*.go' -type f -not -name '*.pb.go' -not -name '*_generated.go' -not -name '*_test.go')
|
|
||||||
TESTS := $(shell find . -name '*.go' -type f -not -name '*.pb.go' -not -name '*_generated.go' -name '*_test.go')
|
|
||||||
|
|
||||||
get:
|
|
||||||
$(GO) get ./...
|
|
||||||
$(GO) mod verify
|
|
||||||
$(GO) mod tidy
|
|
||||||
|
|
||||||
update:
|
|
||||||
$(GO) get -u -v all
|
|
||||||
$(GO) mod verify
|
|
||||||
$(GO) mod tidy
|
|
||||||
|
|
||||||
fmt:
|
|
||||||
gofmt -s -l -w $(FILES) $(TESTS)
|
|
||||||
|
|
||||||
lint:
|
|
||||||
golangci-lint run
|
|
||||||
|
|
||||||
test:
|
|
||||||
$(GOTEST) ./...
|
|
||||||
-36
@@ -1,36 +0,0 @@
|
|||||||
# sarama
|
|
||||||
|
|
||||||
[](https://godoc.org/github.com/Shopify/sarama)
|
|
||||||
[](https://travis-ci.org/Shopify/sarama)
|
|
||||||
[](https://codecov.io/gh/Shopify/sarama)
|
|
||||||
|
|
||||||
Sarama is an MIT-licensed Go client library for [Apache Kafka](https://kafka.apache.org/) version 0.8 (and later).
|
|
||||||
|
|
||||||
## Getting started
|
|
||||||
|
|
||||||
- API documentation and examples are available via [godoc](https://godoc.org/github.com/Shopify/sarama).
|
|
||||||
- Mocks for testing are available in the [mocks](./mocks) subpackage.
|
|
||||||
- The [examples](./examples) directory contains more elaborate example applications.
|
|
||||||
- The [tools](./tools) directory contains command line tools that can be useful for testing, diagnostics, and instrumentation.
|
|
||||||
|
|
||||||
You might also want to look at the [Frequently Asked Questions](https://github.com/Shopify/sarama/wiki/Frequently-Asked-Questions).
|
|
||||||
|
|
||||||
## Compatibility and API stability
|
|
||||||
|
|
||||||
Sarama provides a "2 releases + 2 months" compatibility guarantee: we support
|
|
||||||
the two latest stable releases of Kafka and Go, and we provide a two month
|
|
||||||
grace period for older releases. This means we currently officially support
|
|
||||||
Go 1.12 through 1.14, and Kafka 2.1 through 2.4, although older releases are
|
|
||||||
still likely to work.
|
|
||||||
|
|
||||||
Sarama follows semantic versioning and provides API stability via the gopkg.in service.
|
|
||||||
You can import a version with a guaranteed stable API via http://gopkg.in/Shopify/sarama.v1.
|
|
||||||
A changelog is available [here](CHANGELOG.md).
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
- Get started by checking our [contribution guidelines](https://github.com/Shopify/sarama/blob/master/.github/CONTRIBUTING.md).
|
|
||||||
- Read the [Sarama wiki](https://github.com/Shopify/sarama/wiki) for more technical and design details.
|
|
||||||
- The [Kafka Protocol Specification](https://cwiki.apache.org/confluence/display/KAFKA/A+Guide+To+The+Kafka+Protocol) contains a wealth of useful information.
|
|
||||||
- For more general issues, there is [a google group](https://groups.google.com/forum/#!forum/kafka-clients) for Kafka client developers.
|
|
||||||
- If you have any questions, just ask!
|
|
||||||
-14
@@ -1,14 +0,0 @@
|
|||||||
# We have 5 * 192MB ZK processes and 5 * 320MB Kafka processes => 2560MB
|
|
||||||
MEMORY = 3072
|
|
||||||
|
|
||||||
Vagrant.configure("2") do |config|
|
|
||||||
config.vm.box = "ubuntu/bionic64"
|
|
||||||
|
|
||||||
config.vm.provision :shell, path: "vagrant/provision.sh"
|
|
||||||
|
|
||||||
config.vm.network "private_network", ip: "192.168.100.67"
|
|
||||||
|
|
||||||
config.vm.provider "virtualbox" do |v|
|
|
||||||
v.memory = MEMORY
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-138
@@ -1,138 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
//Resource holds information about acl resource type
|
|
||||||
type Resource struct {
|
|
||||||
ResourceType AclResourceType
|
|
||||||
ResourceName string
|
|
||||||
ResourcePatternType AclResourcePatternType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resource) encode(pe packetEncoder, version int16) error {
|
|
||||||
pe.putInt8(int8(r.ResourceType))
|
|
||||||
|
|
||||||
if err := pe.putString(r.ResourceName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if version == 1 {
|
|
||||||
if r.ResourcePatternType == AclPatternUnknown {
|
|
||||||
Logger.Print("Cannot encode an unknown resource pattern type, using Literal instead")
|
|
||||||
r.ResourcePatternType = AclPatternLiteral
|
|
||||||
}
|
|
||||||
pe.putInt8(int8(r.ResourcePatternType))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resource) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
resourceType, err := pd.getInt8()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.ResourceType = AclResourceType(resourceType)
|
|
||||||
|
|
||||||
if r.ResourceName, err = pd.getString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if version == 1 {
|
|
||||||
pattern, err := pd.getInt8()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.ResourcePatternType = AclResourcePatternType(pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//Acl holds information about acl type
|
|
||||||
type Acl struct {
|
|
||||||
Principal string
|
|
||||||
Host string
|
|
||||||
Operation AclOperation
|
|
||||||
PermissionType AclPermissionType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Acl) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putString(a.Principal); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pe.putString(a.Host); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pe.putInt8(int8(a.Operation))
|
|
||||||
pe.putInt8(int8(a.PermissionType))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Acl) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
if a.Principal, err = pd.getString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Host, err = pd.getString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
operation, err := pd.getInt8()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.Operation = AclOperation(operation)
|
|
||||||
|
|
||||||
permissionType, err := pd.getInt8()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.PermissionType = AclPermissionType(permissionType)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//ResourceAcls is an acl resource type
|
|
||||||
type ResourceAcls struct {
|
|
||||||
Resource
|
|
||||||
Acls []*Acl
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ResourceAcls) encode(pe packetEncoder, version int16) error {
|
|
||||||
if err := r.Resource.encode(pe, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(r.Acls)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, acl := range r.Acls {
|
|
||||||
if err := acl.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ResourceAcls) decode(pd packetDecoder, version int16) error {
|
|
||||||
if err := r.Resource.decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Acls = make([]*Acl, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
r.Acls[i] = new(Acl)
|
|
||||||
if err := r.Acls[i].decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-89
@@ -1,89 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
//CreateAclsRequest is an acl creation request
|
|
||||||
type CreateAclsRequest struct {
|
|
||||||
Version int16
|
|
||||||
AclCreations []*AclCreation
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateAclsRequest) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putArrayLength(len(c.AclCreations)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, aclCreation := range c.AclCreations {
|
|
||||||
if err := aclCreation.encode(pe, c.Version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateAclsRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
c.Version = version
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.AclCreations = make([]*AclCreation, n)
|
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
c.AclCreations[i] = new(AclCreation)
|
|
||||||
if err := c.AclCreations[i].decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateAclsRequest) key() int16 {
|
|
||||||
return 30
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateAclsRequest) version() int16 {
|
|
||||||
return c.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateAclsRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateAclsRequest) requiredVersion() KafkaVersion {
|
|
||||||
switch c.Version {
|
|
||||||
case 1:
|
|
||||||
return V2_0_0_0
|
|
||||||
default:
|
|
||||||
return V0_11_0_0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//AclCreation is a wrapper around Resource and Acl type
|
|
||||||
type AclCreation struct {
|
|
||||||
Resource
|
|
||||||
Acl
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AclCreation) encode(pe packetEncoder, version int16) error {
|
|
||||||
if err := a.Resource.encode(pe, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := a.Acl.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AclCreation) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
if err := a.Resource.decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := a.Acl.decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-94
@@ -1,94 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
//CreateAclsResponse is a an acl response creation type
|
|
||||||
type CreateAclsResponse struct {
|
|
||||||
ThrottleTime time.Duration
|
|
||||||
AclCreationResponses []*AclCreationResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateAclsResponse) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt32(int32(c.ThrottleTime / time.Millisecond))
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(c.AclCreationResponses)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, aclCreationResponse := range c.AclCreationResponses {
|
|
||||||
if err := aclCreationResponse.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateAclsResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
throttleTime, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.ThrottleTime = time.Duration(throttleTime) * time.Millisecond
|
|
||||||
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.AclCreationResponses = make([]*AclCreationResponse, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
c.AclCreationResponses[i] = new(AclCreationResponse)
|
|
||||||
if err := c.AclCreationResponses[i].decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateAclsResponse) key() int16 {
|
|
||||||
return 30
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateAclsResponse) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateAclsResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateAclsResponse) requiredVersion() KafkaVersion {
|
|
||||||
return V0_11_0_0
|
|
||||||
}
|
|
||||||
|
|
||||||
//AclCreationResponse is an acl creation response type
|
|
||||||
type AclCreationResponse struct {
|
|
||||||
Err KError
|
|
||||||
ErrMsg *string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AclCreationResponse) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt16(int16(a.Err))
|
|
||||||
|
|
||||||
if err := pe.putNullableString(a.ErrMsg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AclCreationResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
kerr, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.Err = KError(kerr)
|
|
||||||
|
|
||||||
if a.ErrMsg, err = pd.getNullableString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-62
@@ -1,62 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
//DeleteAclsRequest is a delete acl request
|
|
||||||
type DeleteAclsRequest struct {
|
|
||||||
Version int
|
|
||||||
Filters []*AclFilter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteAclsRequest) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putArrayLength(len(d.Filters)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, filter := range d.Filters {
|
|
||||||
filter.Version = d.Version
|
|
||||||
if err := filter.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteAclsRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
d.Version = int(version)
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.Filters = make([]*AclFilter, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
d.Filters[i] = new(AclFilter)
|
|
||||||
d.Filters[i].Version = int(version)
|
|
||||||
if err := d.Filters[i].decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteAclsRequest) key() int16 {
|
|
||||||
return 31
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteAclsRequest) version() int16 {
|
|
||||||
return int16(d.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *DeleteAclsRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteAclsRequest) requiredVersion() KafkaVersion {
|
|
||||||
switch d.Version {
|
|
||||||
case 1:
|
|
||||||
return V2_0_0_0
|
|
||||||
default:
|
|
||||||
return V0_11_0_0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-163
@@ -1,163 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
//DeleteAclsResponse is a delete acl response
|
|
||||||
type DeleteAclsResponse struct {
|
|
||||||
Version int16
|
|
||||||
ThrottleTime time.Duration
|
|
||||||
FilterResponses []*FilterResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteAclsResponse) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt32(int32(d.ThrottleTime / time.Millisecond))
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(d.FilterResponses)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, filterResponse := range d.FilterResponses {
|
|
||||||
if err := filterResponse.encode(pe, d.Version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteAclsResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
throttleTime, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.ThrottleTime = time.Duration(throttleTime) * time.Millisecond
|
|
||||||
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.FilterResponses = make([]*FilterResponse, n)
|
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
d.FilterResponses[i] = new(FilterResponse)
|
|
||||||
if err := d.FilterResponses[i].decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteAclsResponse) key() int16 {
|
|
||||||
return 31
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteAclsResponse) version() int16 {
|
|
||||||
return d.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteAclsResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteAclsResponse) requiredVersion() KafkaVersion {
|
|
||||||
return V0_11_0_0
|
|
||||||
}
|
|
||||||
|
|
||||||
//FilterResponse is a filter response type
|
|
||||||
type FilterResponse struct {
|
|
||||||
Err KError
|
|
||||||
ErrMsg *string
|
|
||||||
MatchingAcls []*MatchingAcl
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FilterResponse) encode(pe packetEncoder, version int16) error {
|
|
||||||
pe.putInt16(int16(f.Err))
|
|
||||||
if err := pe.putNullableString(f.ErrMsg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(f.MatchingAcls)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, matchingAcl := range f.MatchingAcls {
|
|
||||||
if err := matchingAcl.encode(pe, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FilterResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
kerr, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.Err = KError(kerr)
|
|
||||||
|
|
||||||
if f.ErrMsg, err = pd.getNullableString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.MatchingAcls = make([]*MatchingAcl, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
f.MatchingAcls[i] = new(MatchingAcl)
|
|
||||||
if err := f.MatchingAcls[i].decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//MatchingAcl is a matching acl type
|
|
||||||
type MatchingAcl struct {
|
|
||||||
Err KError
|
|
||||||
ErrMsg *string
|
|
||||||
Resource
|
|
||||||
Acl
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MatchingAcl) encode(pe packetEncoder, version int16) error {
|
|
||||||
pe.putInt16(int16(m.Err))
|
|
||||||
if err := pe.putNullableString(m.ErrMsg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.Resource.encode(pe, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.Acl.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MatchingAcl) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
kerr, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.Err = KError(kerr)
|
|
||||||
|
|
||||||
if m.ErrMsg, err = pd.getNullableString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.Resource.decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.Acl.decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-39
@@ -1,39 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
//DescribeAclsRequest is a secribe acl request type
|
|
||||||
type DescribeAclsRequest struct {
|
|
||||||
Version int
|
|
||||||
AclFilter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DescribeAclsRequest) encode(pe packetEncoder) error {
|
|
||||||
d.AclFilter.Version = d.Version
|
|
||||||
return d.AclFilter.encode(pe)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DescribeAclsRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
d.Version = int(version)
|
|
||||||
d.AclFilter.Version = int(version)
|
|
||||||
return d.AclFilter.decode(pd, version)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DescribeAclsRequest) key() int16 {
|
|
||||||
return 29
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DescribeAclsRequest) version() int16 {
|
|
||||||
return int16(d.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DescribeAclsRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DescribeAclsRequest) requiredVersion() KafkaVersion {
|
|
||||||
switch d.Version {
|
|
||||||
case 1:
|
|
||||||
return V2_0_0_0
|
|
||||||
default:
|
|
||||||
return V0_11_0_0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-91
@@ -1,91 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
//DescribeAclsResponse is a describe acl response type
|
|
||||||
type DescribeAclsResponse struct {
|
|
||||||
Version int16
|
|
||||||
ThrottleTime time.Duration
|
|
||||||
Err KError
|
|
||||||
ErrMsg *string
|
|
||||||
ResourceAcls []*ResourceAcls
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DescribeAclsResponse) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt32(int32(d.ThrottleTime / time.Millisecond))
|
|
||||||
pe.putInt16(int16(d.Err))
|
|
||||||
|
|
||||||
if err := pe.putNullableString(d.ErrMsg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(d.ResourceAcls)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, resourceAcl := range d.ResourceAcls {
|
|
||||||
if err := resourceAcl.encode(pe, d.Version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DescribeAclsResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
throttleTime, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.ThrottleTime = time.Duration(throttleTime) * time.Millisecond
|
|
||||||
|
|
||||||
kerr, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.Err = KError(kerr)
|
|
||||||
|
|
||||||
errmsg, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if errmsg != "" {
|
|
||||||
d.ErrMsg = &errmsg
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.ResourceAcls = make([]*ResourceAcls, n)
|
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
d.ResourceAcls[i] = new(ResourceAcls)
|
|
||||||
if err := d.ResourceAcls[i].decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DescribeAclsResponse) key() int16 {
|
|
||||||
return 29
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DescribeAclsResponse) version() int16 {
|
|
||||||
return d.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DescribeAclsResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DescribeAclsResponse) requiredVersion() KafkaVersion {
|
|
||||||
switch d.Version {
|
|
||||||
case 1:
|
|
||||||
return V2_0_0_0
|
|
||||||
default:
|
|
||||||
return V0_11_0_0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-78
@@ -1,78 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
type AclFilter struct {
|
|
||||||
Version int
|
|
||||||
ResourceType AclResourceType
|
|
||||||
ResourceName *string
|
|
||||||
ResourcePatternTypeFilter AclResourcePatternType
|
|
||||||
Principal *string
|
|
||||||
Host *string
|
|
||||||
Operation AclOperation
|
|
||||||
PermissionType AclPermissionType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AclFilter) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt8(int8(a.ResourceType))
|
|
||||||
if err := pe.putNullableString(a.ResourceName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Version == 1 {
|
|
||||||
pe.putInt8(int8(a.ResourcePatternTypeFilter))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pe.putNullableString(a.Principal); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := pe.putNullableString(a.Host); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pe.putInt8(int8(a.Operation))
|
|
||||||
pe.putInt8(int8(a.PermissionType))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AclFilter) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
resourceType, err := pd.getInt8()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.ResourceType = AclResourceType(resourceType)
|
|
||||||
|
|
||||||
if a.ResourceName, err = pd.getNullableString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Version == 1 {
|
|
||||||
pattern, err := pd.getInt8()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
a.ResourcePatternTypeFilter = AclResourcePatternType(pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Principal, err = pd.getNullableString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Host, err = pd.getNullableString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
operation, err := pd.getInt8()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.Operation = AclOperation(operation)
|
|
||||||
|
|
||||||
permissionType, err := pd.getInt8()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.PermissionType = AclPermissionType(permissionType)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-55
@@ -1,55 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
type (
|
|
||||||
AclOperation int
|
|
||||||
|
|
||||||
AclPermissionType int
|
|
||||||
|
|
||||||
AclResourceType int
|
|
||||||
|
|
||||||
AclResourcePatternType int
|
|
||||||
)
|
|
||||||
|
|
||||||
// ref: https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/acl/AclOperation.java
|
|
||||||
const (
|
|
||||||
AclOperationUnknown AclOperation = iota
|
|
||||||
AclOperationAny
|
|
||||||
AclOperationAll
|
|
||||||
AclOperationRead
|
|
||||||
AclOperationWrite
|
|
||||||
AclOperationCreate
|
|
||||||
AclOperationDelete
|
|
||||||
AclOperationAlter
|
|
||||||
AclOperationDescribe
|
|
||||||
AclOperationClusterAction
|
|
||||||
AclOperationDescribeConfigs
|
|
||||||
AclOperationAlterConfigs
|
|
||||||
AclOperationIdempotentWrite
|
|
||||||
)
|
|
||||||
|
|
||||||
// ref: https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/acl/AclPermissionType.java
|
|
||||||
const (
|
|
||||||
AclPermissionUnknown AclPermissionType = iota
|
|
||||||
AclPermissionAny
|
|
||||||
AclPermissionDeny
|
|
||||||
AclPermissionAllow
|
|
||||||
)
|
|
||||||
|
|
||||||
// ref: https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/resource/ResourceType.java
|
|
||||||
const (
|
|
||||||
AclResourceUnknown AclResourceType = iota
|
|
||||||
AclResourceAny
|
|
||||||
AclResourceTopic
|
|
||||||
AclResourceGroup
|
|
||||||
AclResourceCluster
|
|
||||||
AclResourceTransactionalID
|
|
||||||
)
|
|
||||||
|
|
||||||
// ref: https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/resource/PatternType.java
|
|
||||||
const (
|
|
||||||
AclPatternUnknown AclResourcePatternType = iota
|
|
||||||
AclPatternAny
|
|
||||||
AclPatternMatch
|
|
||||||
AclPatternLiteral
|
|
||||||
AclPatternPrefixed
|
|
||||||
)
|
|
||||||
-57
@@ -1,57 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
//AddOffsetsToTxnRequest adds offsets to a transaction request
|
|
||||||
type AddOffsetsToTxnRequest struct {
|
|
||||||
TransactionalID string
|
|
||||||
ProducerID int64
|
|
||||||
ProducerEpoch int16
|
|
||||||
GroupID string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddOffsetsToTxnRequest) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putString(a.TransactionalID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pe.putInt64(a.ProducerID)
|
|
||||||
|
|
||||||
pe.putInt16(a.ProducerEpoch)
|
|
||||||
|
|
||||||
if err := pe.putString(a.GroupID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddOffsetsToTxnRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
if a.TransactionalID, err = pd.getString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if a.ProducerID, err = pd.getInt64(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if a.ProducerEpoch, err = pd.getInt16(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if a.GroupID, err = pd.getString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddOffsetsToTxnRequest) key() int16 {
|
|
||||||
return 25
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddOffsetsToTxnRequest) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddOffsetsToTxnRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddOffsetsToTxnRequest) requiredVersion() KafkaVersion {
|
|
||||||
return V0_11_0_0
|
|
||||||
}
|
|
||||||
-49
@@ -1,49 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
//AddOffsetsToTxnResponse is a response type for adding offsets to txns
|
|
||||||
type AddOffsetsToTxnResponse struct {
|
|
||||||
ThrottleTime time.Duration
|
|
||||||
Err KError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddOffsetsToTxnResponse) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt32(int32(a.ThrottleTime / time.Millisecond))
|
|
||||||
pe.putInt16(int16(a.Err))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddOffsetsToTxnResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
throttleTime, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.ThrottleTime = time.Duration(throttleTime) * time.Millisecond
|
|
||||||
|
|
||||||
kerr, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.Err = KError(kerr)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddOffsetsToTxnResponse) key() int16 {
|
|
||||||
return 25
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddOffsetsToTxnResponse) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddOffsetsToTxnResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddOffsetsToTxnResponse) requiredVersion() KafkaVersion {
|
|
||||||
return V0_11_0_0
|
|
||||||
}
|
|
||||||
-81
@@ -1,81 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
//AddPartitionsToTxnRequest is a add paartition request
|
|
||||||
type AddPartitionsToTxnRequest struct {
|
|
||||||
TransactionalID string
|
|
||||||
ProducerID int64
|
|
||||||
ProducerEpoch int16
|
|
||||||
TopicPartitions map[string][]int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddPartitionsToTxnRequest) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putString(a.TransactionalID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pe.putInt64(a.ProducerID)
|
|
||||||
pe.putInt16(a.ProducerEpoch)
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(a.TopicPartitions)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for topic, partitions := range a.TopicPartitions {
|
|
||||||
if err := pe.putString(topic); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := pe.putInt32Array(partitions); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddPartitionsToTxnRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
if a.TransactionalID, err = pd.getString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if a.ProducerID, err = pd.getInt64(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if a.ProducerEpoch, err = pd.getInt16(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
a.TopicPartitions = make(map[string][]int32)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
topic, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
partitions, err := pd.getInt32Array()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
a.TopicPartitions[topic] = partitions
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddPartitionsToTxnRequest) key() int16 {
|
|
||||||
return 24
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddPartitionsToTxnRequest) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddPartitionsToTxnRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddPartitionsToTxnRequest) requiredVersion() KafkaVersion {
|
|
||||||
return V0_11_0_0
|
|
||||||
}
|
|
||||||
-114
@@ -1,114 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
//AddPartitionsToTxnResponse is a partition errors to transaction type
|
|
||||||
type AddPartitionsToTxnResponse struct {
|
|
||||||
ThrottleTime time.Duration
|
|
||||||
Errors map[string][]*PartitionError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddPartitionsToTxnResponse) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt32(int32(a.ThrottleTime / time.Millisecond))
|
|
||||||
if err := pe.putArrayLength(len(a.Errors)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for topic, e := range a.Errors {
|
|
||||||
if err := pe.putString(topic); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := pe.putArrayLength(len(e)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, partitionError := range e {
|
|
||||||
if err := partitionError.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddPartitionsToTxnResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
throttleTime, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.ThrottleTime = time.Duration(throttleTime) * time.Millisecond
|
|
||||||
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
a.Errors = make(map[string][]*PartitionError)
|
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
topic, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
m, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
a.Errors[topic] = make([]*PartitionError, m)
|
|
||||||
|
|
||||||
for j := 0; j < m; j++ {
|
|
||||||
a.Errors[topic][j] = new(PartitionError)
|
|
||||||
if err := a.Errors[topic][j].decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddPartitionsToTxnResponse) key() int16 {
|
|
||||||
return 24
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddPartitionsToTxnResponse) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddPartitionsToTxnResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AddPartitionsToTxnResponse) requiredVersion() KafkaVersion {
|
|
||||||
return V0_11_0_0
|
|
||||||
}
|
|
||||||
|
|
||||||
//PartitionError is a partition error type
|
|
||||||
type PartitionError struct {
|
|
||||||
Partition int32
|
|
||||||
Err KError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PartitionError) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt32(p.Partition)
|
|
||||||
pe.putInt16(int16(p.Err))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PartitionError) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
if p.Partition, err = pd.getInt32(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
kerr, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.Err = KError(kerr)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-934
@@ -1,934 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ClusterAdmin is the administrative client for Kafka, which supports managing and inspecting topics,
|
|
||||||
// brokers, configurations and ACLs. The minimum broker version required is 0.10.0.0.
|
|
||||||
// Methods with stricter requirements will specify the minimum broker version required.
|
|
||||||
// You MUST call Close() on a client to avoid leaks
|
|
||||||
type ClusterAdmin interface {
|
|
||||||
// Creates a new topic. This operation is supported by brokers with version 0.10.1.0 or higher.
|
|
||||||
// It may take several seconds after CreateTopic returns success for all the brokers
|
|
||||||
// to become aware that the topic has been created. During this time, listTopics
|
|
||||||
// may not return information about the new topic.The validateOnly option is supported from version 0.10.2.0.
|
|
||||||
CreateTopic(topic string, detail *TopicDetail, validateOnly bool) error
|
|
||||||
|
|
||||||
// List the topics available in the cluster with the default options.
|
|
||||||
ListTopics() (map[string]TopicDetail, error)
|
|
||||||
|
|
||||||
// Describe some topics in the cluster.
|
|
||||||
DescribeTopics(topics []string) (metadata []*TopicMetadata, err error)
|
|
||||||
|
|
||||||
// Delete a topic. It may take several seconds after the DeleteTopic to returns success
|
|
||||||
// and for all the brokers to become aware that the topics are gone.
|
|
||||||
// During this time, listTopics may continue to return information about the deleted topic.
|
|
||||||
// If delete.topic.enable is false on the brokers, deleteTopic will mark
|
|
||||||
// the topic for deletion, but not actually delete them.
|
|
||||||
// This operation is supported by brokers with version 0.10.1.0 or higher.
|
|
||||||
DeleteTopic(topic string) error
|
|
||||||
|
|
||||||
// Increase the number of partitions of the topics according to the corresponding values.
|
|
||||||
// If partitions are increased for a topic that has a key, the partition logic or ordering of
|
|
||||||
// the messages will be affected. It may take several seconds after this method returns
|
|
||||||
// success for all the brokers to become aware that the partitions have been created.
|
|
||||||
// During this time, ClusterAdmin#describeTopics may not return information about the
|
|
||||||
// new partitions. This operation is supported by brokers with version 1.0.0 or higher.
|
|
||||||
CreatePartitions(topic string, count int32, assignment [][]int32, validateOnly bool) error
|
|
||||||
|
|
||||||
// Alter the replica assignment for partitions.
|
|
||||||
// This operation is supported by brokers with version 2.4.0.0 or higher.
|
|
||||||
AlterPartitionReassignments(topic string, assignment [][]int32) error
|
|
||||||
|
|
||||||
// Provides info on ongoing partitions replica reassignments.
|
|
||||||
// This operation is supported by brokers with version 2.4.0.0 or higher.
|
|
||||||
ListPartitionReassignments(topics string, partitions []int32) (topicStatus map[string]map[int32]*PartitionReplicaReassignmentsStatus, err error)
|
|
||||||
|
|
||||||
// Delete records whose offset is smaller than the given offset of the corresponding partition.
|
|
||||||
// This operation is supported by brokers with version 0.11.0.0 or higher.
|
|
||||||
DeleteRecords(topic string, partitionOffsets map[int32]int64) error
|
|
||||||
|
|
||||||
// Get the configuration for the specified resources.
|
|
||||||
// The returned configuration includes default values and the Default is true
|
|
||||||
// can be used to distinguish them from user supplied values.
|
|
||||||
// Config entries where ReadOnly is true cannot be updated.
|
|
||||||
// The value of config entries where Sensitive is true is always nil so
|
|
||||||
// sensitive information is not disclosed.
|
|
||||||
// This operation is supported by brokers with version 0.11.0.0 or higher.
|
|
||||||
DescribeConfig(resource ConfigResource) ([]ConfigEntry, error)
|
|
||||||
|
|
||||||
// Update the configuration for the specified resources with the default options.
|
|
||||||
// This operation is supported by brokers with version 0.11.0.0 or higher.
|
|
||||||
// The resources with their configs (topic is the only resource type with configs
|
|
||||||
// that can be updated currently Updates are not transactional so they may succeed
|
|
||||||
// for some resources while fail for others. The configs for a particular resource are updated automatically.
|
|
||||||
AlterConfig(resourceType ConfigResourceType, name string, entries map[string]*string, validateOnly bool) error
|
|
||||||
|
|
||||||
// Creates access control lists (ACLs) which are bound to specific resources.
|
|
||||||
// This operation is not transactional so it may succeed for some ACLs while fail for others.
|
|
||||||
// If you attempt to add an ACL that duplicates an existing ACL, no error will be raised, but
|
|
||||||
// no changes will be made. This operation is supported by brokers with version 0.11.0.0 or higher.
|
|
||||||
CreateACL(resource Resource, acl Acl) error
|
|
||||||
|
|
||||||
// Lists access control lists (ACLs) according to the supplied filter.
|
|
||||||
// it may take some time for changes made by createAcls or deleteAcls to be reflected in the output of ListAcls
|
|
||||||
// This operation is supported by brokers with version 0.11.0.0 or higher.
|
|
||||||
ListAcls(filter AclFilter) ([]ResourceAcls, error)
|
|
||||||
|
|
||||||
// Deletes access control lists (ACLs) according to the supplied filters.
|
|
||||||
// This operation is not transactional so it may succeed for some ACLs while fail for others.
|
|
||||||
// This operation is supported by brokers with version 0.11.0.0 or higher.
|
|
||||||
DeleteACL(filter AclFilter, validateOnly bool) ([]MatchingAcl, error)
|
|
||||||
|
|
||||||
// List the consumer groups available in the cluster.
|
|
||||||
ListConsumerGroups() (map[string]string, error)
|
|
||||||
|
|
||||||
// Describe the given consumer groups.
|
|
||||||
DescribeConsumerGroups(groups []string) ([]*GroupDescription, error)
|
|
||||||
|
|
||||||
// List the consumer group offsets available in the cluster.
|
|
||||||
ListConsumerGroupOffsets(group string, topicPartitions map[string][]int32) (*OffsetFetchResponse, error)
|
|
||||||
|
|
||||||
// Delete a consumer group.
|
|
||||||
DeleteConsumerGroup(group string) error
|
|
||||||
|
|
||||||
// Get information about the nodes in the cluster
|
|
||||||
DescribeCluster() (brokers []*Broker, controllerID int32, err error)
|
|
||||||
|
|
||||||
// Get information about all log directories on the given set of brokers
|
|
||||||
DescribeLogDirs(brokers []int32) (map[int32][]DescribeLogDirsResponseDirMetadata, error)
|
|
||||||
|
|
||||||
// Close shuts down the admin and closes underlying client.
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type clusterAdmin struct {
|
|
||||||
client Client
|
|
||||||
conf *Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClusterAdmin creates a new ClusterAdmin using the given broker addresses and configuration.
|
|
||||||
func NewClusterAdmin(addrs []string, conf *Config) (ClusterAdmin, error) {
|
|
||||||
client, err := NewClient(addrs, conf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return NewClusterAdminFromClient(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClusterAdminFromClient creates a new ClusterAdmin using the given client.
|
|
||||||
// Note that underlying client will also be closed on admin's Close() call.
|
|
||||||
func NewClusterAdminFromClient(client Client) (ClusterAdmin, error) {
|
|
||||||
//make sure we can retrieve the controller
|
|
||||||
_, err := client.Controller()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ca := &clusterAdmin{
|
|
||||||
client: client,
|
|
||||||
conf: client.Config(),
|
|
||||||
}
|
|
||||||
return ca, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) Close() error {
|
|
||||||
return ca.client.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) Controller() (*Broker, error) {
|
|
||||||
return ca.client.Controller()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) refreshController() (*Broker, error) {
|
|
||||||
return ca.client.RefreshController()
|
|
||||||
}
|
|
||||||
|
|
||||||
// isErrNoController returns `true` if the given error type unwraps to an
|
|
||||||
// `ErrNotController` response from Kafka
|
|
||||||
func isErrNoController(err error) bool {
|
|
||||||
switch e := err.(type) {
|
|
||||||
case *TopicError:
|
|
||||||
return e.Err == ErrNotController
|
|
||||||
case *TopicPartitionError:
|
|
||||||
return e.Err == ErrNotController
|
|
||||||
case KError:
|
|
||||||
return e == ErrNotController
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// retryOnError will repeatedly call the given (error-returning) func in the
|
|
||||||
// case that its response is non-nil and retriable (as determined by the
|
|
||||||
// provided retriable func) up to the maximum number of tries permitted by
|
|
||||||
// the admin client configuration
|
|
||||||
func (ca *clusterAdmin) retryOnError(retriable func(error) bool, fn func() error) error {
|
|
||||||
var err error
|
|
||||||
for attempt := 0; attempt < ca.conf.Admin.Retry.Max; attempt++ {
|
|
||||||
err = fn()
|
|
||||||
if err == nil || !retriable(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
Logger.Printf(
|
|
||||||
"admin/request retrying after %dms... (%d attempts remaining)\n",
|
|
||||||
ca.conf.Admin.Retry.Backoff/time.Millisecond, ca.conf.Admin.Retry.Max-attempt)
|
|
||||||
time.Sleep(ca.conf.Admin.Retry.Backoff)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) CreateTopic(topic string, detail *TopicDetail, validateOnly bool) error {
|
|
||||||
if topic == "" {
|
|
||||||
return ErrInvalidTopic
|
|
||||||
}
|
|
||||||
|
|
||||||
if detail == nil {
|
|
||||||
return errors.New("you must specify topic details")
|
|
||||||
}
|
|
||||||
|
|
||||||
topicDetails := make(map[string]*TopicDetail)
|
|
||||||
topicDetails[topic] = detail
|
|
||||||
|
|
||||||
request := &CreateTopicsRequest{
|
|
||||||
TopicDetails: topicDetails,
|
|
||||||
ValidateOnly: validateOnly,
|
|
||||||
Timeout: ca.conf.Admin.Timeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
if ca.conf.Version.IsAtLeast(V0_11_0_0) {
|
|
||||||
request.Version = 1
|
|
||||||
}
|
|
||||||
if ca.conf.Version.IsAtLeast(V1_0_0_0) {
|
|
||||||
request.Version = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
return ca.retryOnError(isErrNoController, func() error {
|
|
||||||
b, err := ca.Controller()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rsp, err := b.CreateTopics(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
topicErr, ok := rsp.TopicErrors[topic]
|
|
||||||
if !ok {
|
|
||||||
return ErrIncompleteResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
if topicErr.Err != ErrNoError {
|
|
||||||
if topicErr.Err == ErrNotController {
|
|
||||||
_, _ = ca.refreshController()
|
|
||||||
}
|
|
||||||
return topicErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) DescribeTopics(topics []string) (metadata []*TopicMetadata, err error) {
|
|
||||||
controller, err := ca.Controller()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &MetadataRequest{
|
|
||||||
Topics: topics,
|
|
||||||
AllowAutoTopicCreation: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
if ca.conf.Version.IsAtLeast(V1_0_0_0) {
|
|
||||||
request.Version = 5
|
|
||||||
} else if ca.conf.Version.IsAtLeast(V0_11_0_0) {
|
|
||||||
request.Version = 4
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := controller.GetMetadata(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return response.Topics, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) DescribeCluster() (brokers []*Broker, controllerID int32, err error) {
|
|
||||||
controller, err := ca.Controller()
|
|
||||||
if err != nil {
|
|
||||||
return nil, int32(0), err
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &MetadataRequest{
|
|
||||||
Topics: []string{},
|
|
||||||
}
|
|
||||||
|
|
||||||
if ca.conf.Version.IsAtLeast(V0_10_0_0) {
|
|
||||||
request.Version = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := controller.GetMetadata(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, int32(0), err
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.Brokers, response.ControllerID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) findBroker(id int32) (*Broker, error) {
|
|
||||||
brokers := ca.client.Brokers()
|
|
||||||
for _, b := range brokers {
|
|
||||||
if b.ID() == id {
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("could not find broker id %d", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) findAnyBroker() (*Broker, error) {
|
|
||||||
brokers := ca.client.Brokers()
|
|
||||||
if len(brokers) > 0 {
|
|
||||||
index := rand.Intn(len(brokers))
|
|
||||||
return brokers[index], nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("no available broker")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) ListTopics() (map[string]TopicDetail, error) {
|
|
||||||
// In order to build TopicDetails we need to first get the list of all
|
|
||||||
// topics using a MetadataRequest and then get their configs using a
|
|
||||||
// DescribeConfigsRequest request. To avoid sending many requests to the
|
|
||||||
// broker, we use a single DescribeConfigsRequest.
|
|
||||||
|
|
||||||
// Send the all-topic MetadataRequest
|
|
||||||
b, err := ca.findAnyBroker()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_ = b.Open(ca.client.Config())
|
|
||||||
|
|
||||||
metadataReq := &MetadataRequest{}
|
|
||||||
metadataResp, err := b.GetMetadata(metadataReq)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
topicsDetailsMap := make(map[string]TopicDetail)
|
|
||||||
|
|
||||||
var describeConfigsResources []*ConfigResource
|
|
||||||
|
|
||||||
for _, topic := range metadataResp.Topics {
|
|
||||||
topicDetails := TopicDetail{
|
|
||||||
NumPartitions: int32(len(topic.Partitions)),
|
|
||||||
}
|
|
||||||
if len(topic.Partitions) > 0 {
|
|
||||||
topicDetails.ReplicaAssignment = map[int32][]int32{}
|
|
||||||
for _, partition := range topic.Partitions {
|
|
||||||
topicDetails.ReplicaAssignment[partition.ID] = partition.Replicas
|
|
||||||
}
|
|
||||||
topicDetails.ReplicationFactor = int16(len(topic.Partitions[0].Replicas))
|
|
||||||
}
|
|
||||||
topicsDetailsMap[topic.Name] = topicDetails
|
|
||||||
|
|
||||||
// we populate the resources we want to describe from the MetadataResponse
|
|
||||||
topicResource := ConfigResource{
|
|
||||||
Type: TopicResource,
|
|
||||||
Name: topic.Name,
|
|
||||||
}
|
|
||||||
describeConfigsResources = append(describeConfigsResources, &topicResource)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the DescribeConfigsRequest
|
|
||||||
describeConfigsReq := &DescribeConfigsRequest{
|
|
||||||
Resources: describeConfigsResources,
|
|
||||||
}
|
|
||||||
|
|
||||||
if ca.conf.Version.IsAtLeast(V1_1_0_0) {
|
|
||||||
describeConfigsReq.Version = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if ca.conf.Version.IsAtLeast(V2_0_0_0) {
|
|
||||||
describeConfigsReq.Version = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
describeConfigsResp, err := b.DescribeConfigs(describeConfigsReq)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, resource := range describeConfigsResp.Resources {
|
|
||||||
topicDetails := topicsDetailsMap[resource.Name]
|
|
||||||
topicDetails.ConfigEntries = make(map[string]*string)
|
|
||||||
|
|
||||||
for _, entry := range resource.Configs {
|
|
||||||
// only include non-default non-sensitive config
|
|
||||||
// (don't actually think topic config will ever be sensitive)
|
|
||||||
if entry.Default || entry.Sensitive {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
topicDetails.ConfigEntries[entry.Name] = &entry.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
topicsDetailsMap[resource.Name] = topicDetails
|
|
||||||
}
|
|
||||||
|
|
||||||
return topicsDetailsMap, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) DeleteTopic(topic string) error {
|
|
||||||
if topic == "" {
|
|
||||||
return ErrInvalidTopic
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &DeleteTopicsRequest{
|
|
||||||
Topics: []string{topic},
|
|
||||||
Timeout: ca.conf.Admin.Timeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
if ca.conf.Version.IsAtLeast(V0_11_0_0) {
|
|
||||||
request.Version = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return ca.retryOnError(isErrNoController, func() error {
|
|
||||||
b, err := ca.Controller()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rsp, err := b.DeleteTopics(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
topicErr, ok := rsp.TopicErrorCodes[topic]
|
|
||||||
if !ok {
|
|
||||||
return ErrIncompleteResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
if topicErr != ErrNoError {
|
|
||||||
if topicErr == ErrNotController {
|
|
||||||
_, _ = ca.refreshController()
|
|
||||||
}
|
|
||||||
return topicErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) CreatePartitions(topic string, count int32, assignment [][]int32, validateOnly bool) error {
|
|
||||||
if topic == "" {
|
|
||||||
return ErrInvalidTopic
|
|
||||||
}
|
|
||||||
|
|
||||||
topicPartitions := make(map[string]*TopicPartition)
|
|
||||||
topicPartitions[topic] = &TopicPartition{Count: count, Assignment: assignment}
|
|
||||||
|
|
||||||
request := &CreatePartitionsRequest{
|
|
||||||
TopicPartitions: topicPartitions,
|
|
||||||
Timeout: ca.conf.Admin.Timeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
return ca.retryOnError(isErrNoController, func() error {
|
|
||||||
b, err := ca.Controller()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rsp, err := b.CreatePartitions(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
topicErr, ok := rsp.TopicPartitionErrors[topic]
|
|
||||||
if !ok {
|
|
||||||
return ErrIncompleteResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
if topicErr.Err != ErrNoError {
|
|
||||||
if topicErr.Err == ErrNotController {
|
|
||||||
_, _ = ca.refreshController()
|
|
||||||
}
|
|
||||||
return topicErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) AlterPartitionReassignments(topic string, assignment [][]int32) error {
|
|
||||||
if topic == "" {
|
|
||||||
return ErrInvalidTopic
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &AlterPartitionReassignmentsRequest{
|
|
||||||
TimeoutMs: int32(60000),
|
|
||||||
Version: int16(0),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(assignment); i++ {
|
|
||||||
request.AddBlock(topic, int32(i), assignment[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
return ca.retryOnError(isErrNoController, func() error {
|
|
||||||
b, err := ca.Controller()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
errs := make([]error, 0)
|
|
||||||
|
|
||||||
rsp, err := b.AlterPartitionReassignments(request)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
} else {
|
|
||||||
if rsp.ErrorCode > 0 {
|
|
||||||
errs = append(errs, errors.New(rsp.ErrorCode.Error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
for topic, topicErrors := range rsp.Errors {
|
|
||||||
for partition, partitionError := range topicErrors {
|
|
||||||
if partitionError.errorCode != ErrNoError {
|
|
||||||
errStr := fmt.Sprintf("[%s-%d]: %s", topic, partition, partitionError.errorCode.Error())
|
|
||||||
errs = append(errs, errors.New(errStr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errs) > 0 {
|
|
||||||
return ErrReassignPartitions{MultiError{&errs}}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) ListPartitionReassignments(topic string, partitions []int32) (topicStatus map[string]map[int32]*PartitionReplicaReassignmentsStatus, err error) {
|
|
||||||
if topic == "" {
|
|
||||||
return nil, ErrInvalidTopic
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &ListPartitionReassignmentsRequest{
|
|
||||||
TimeoutMs: int32(60000),
|
|
||||||
Version: int16(0),
|
|
||||||
}
|
|
||||||
|
|
||||||
request.AddBlock(topic, partitions)
|
|
||||||
|
|
||||||
b, err := ca.Controller()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_ = b.Open(ca.client.Config())
|
|
||||||
|
|
||||||
rsp, err := b.ListPartitionReassignments(request)
|
|
||||||
|
|
||||||
if err == nil && rsp != nil {
|
|
||||||
return rsp.TopicStatus, nil
|
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) DeleteRecords(topic string, partitionOffsets map[int32]int64) error {
|
|
||||||
if topic == "" {
|
|
||||||
return ErrInvalidTopic
|
|
||||||
}
|
|
||||||
partitionPerBroker := make(map[*Broker][]int32)
|
|
||||||
for partition := range partitionOffsets {
|
|
||||||
broker, err := ca.client.Leader(topic, partition)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, ok := partitionPerBroker[broker]; ok {
|
|
||||||
partitionPerBroker[broker] = append(partitionPerBroker[broker], partition)
|
|
||||||
} else {
|
|
||||||
partitionPerBroker[broker] = []int32{partition}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
errs := make([]error, 0)
|
|
||||||
for broker, partitions := range partitionPerBroker {
|
|
||||||
topics := make(map[string]*DeleteRecordsRequestTopic)
|
|
||||||
recordsToDelete := make(map[int32]int64)
|
|
||||||
for _, p := range partitions {
|
|
||||||
recordsToDelete[p] = partitionOffsets[p]
|
|
||||||
}
|
|
||||||
topics[topic] = &DeleteRecordsRequestTopic{PartitionOffsets: recordsToDelete}
|
|
||||||
request := &DeleteRecordsRequest{
|
|
||||||
Topics: topics,
|
|
||||||
Timeout: ca.conf.Admin.Timeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
rsp, err := broker.DeleteRecords(request)
|
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
} else {
|
|
||||||
deleteRecordsResponseTopic, ok := rsp.Topics[topic]
|
|
||||||
if !ok {
|
|
||||||
errs = append(errs, ErrIncompleteResponse)
|
|
||||||
} else {
|
|
||||||
for _, deleteRecordsResponsePartition := range deleteRecordsResponseTopic.Partitions {
|
|
||||||
if deleteRecordsResponsePartition.Err != ErrNoError {
|
|
||||||
errs = append(errs, errors.New(deleteRecordsResponsePartition.Err.Error()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
|
||||||
return ErrDeleteRecords{MultiError{&errs}}
|
|
||||||
}
|
|
||||||
//todo since we are dealing with couple of partitions it would be good if we return slice of errors
|
|
||||||
//for each partition instead of one error
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a bool indicating whether the resource request needs to go to a
|
|
||||||
// specific broker
|
|
||||||
func dependsOnSpecificNode(resource ConfigResource) bool {
|
|
||||||
return (resource.Type == BrokerResource && resource.Name != "") ||
|
|
||||||
resource.Type == BrokerLoggerResource
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) DescribeConfig(resource ConfigResource) ([]ConfigEntry, error) {
|
|
||||||
var entries []ConfigEntry
|
|
||||||
var resources []*ConfigResource
|
|
||||||
resources = append(resources, &resource)
|
|
||||||
|
|
||||||
request := &DescribeConfigsRequest{
|
|
||||||
Resources: resources,
|
|
||||||
}
|
|
||||||
|
|
||||||
if ca.conf.Version.IsAtLeast(V1_1_0_0) {
|
|
||||||
request.Version = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if ca.conf.Version.IsAtLeast(V2_0_0_0) {
|
|
||||||
request.Version = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
b *Broker
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
// DescribeConfig of broker/broker logger must be sent to the broker in question
|
|
||||||
if dependsOnSpecificNode(resource) {
|
|
||||||
id, _ := strconv.Atoi(resource.Name)
|
|
||||||
b, err = ca.findBroker(int32(id))
|
|
||||||
} else {
|
|
||||||
b, err = ca.findAnyBroker()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = b.Open(ca.client.Config())
|
|
||||||
rsp, err := b.DescribeConfigs(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rspResource := range rsp.Resources {
|
|
||||||
if rspResource.Name == resource.Name {
|
|
||||||
if rspResource.ErrorMsg != "" {
|
|
||||||
return nil, errors.New(rspResource.ErrorMsg)
|
|
||||||
}
|
|
||||||
if rspResource.ErrorCode != 0 {
|
|
||||||
return nil, KError(rspResource.ErrorCode)
|
|
||||||
}
|
|
||||||
for _, cfgEntry := range rspResource.Configs {
|
|
||||||
entries = append(entries, *cfgEntry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return entries, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) AlterConfig(resourceType ConfigResourceType, name string, entries map[string]*string, validateOnly bool) error {
|
|
||||||
var resources []*AlterConfigsResource
|
|
||||||
resources = append(resources, &AlterConfigsResource{
|
|
||||||
Type: resourceType,
|
|
||||||
Name: name,
|
|
||||||
ConfigEntries: entries,
|
|
||||||
})
|
|
||||||
|
|
||||||
request := &AlterConfigsRequest{
|
|
||||||
Resources: resources,
|
|
||||||
ValidateOnly: validateOnly,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
b *Broker
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
// AlterConfig of broker/broker logger must be sent to the broker in question
|
|
||||||
if dependsOnSpecificNode(ConfigResource{Name: name, Type: resourceType}) {
|
|
||||||
id, _ := strconv.Atoi(name)
|
|
||||||
b, err = ca.findBroker(int32(id))
|
|
||||||
} else {
|
|
||||||
b, err = ca.findAnyBroker()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = b.Open(ca.client.Config())
|
|
||||||
rsp, err := b.AlterConfigs(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rspResource := range rsp.Resources {
|
|
||||||
if rspResource.Name == name {
|
|
||||||
if rspResource.ErrorMsg != "" {
|
|
||||||
return errors.New(rspResource.ErrorMsg)
|
|
||||||
}
|
|
||||||
if rspResource.ErrorCode != 0 {
|
|
||||||
return KError(rspResource.ErrorCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) CreateACL(resource Resource, acl Acl) error {
|
|
||||||
var acls []*AclCreation
|
|
||||||
acls = append(acls, &AclCreation{resource, acl})
|
|
||||||
request := &CreateAclsRequest{AclCreations: acls}
|
|
||||||
|
|
||||||
if ca.conf.Version.IsAtLeast(V2_0_0_0) {
|
|
||||||
request.Version = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := ca.Controller()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = b.CreateAcls(request)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) ListAcls(filter AclFilter) ([]ResourceAcls, error) {
|
|
||||||
request := &DescribeAclsRequest{AclFilter: filter}
|
|
||||||
|
|
||||||
if ca.conf.Version.IsAtLeast(V2_0_0_0) {
|
|
||||||
request.Version = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := ca.Controller()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rsp, err := b.DescribeAcls(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var lAcls []ResourceAcls
|
|
||||||
for _, rAcl := range rsp.ResourceAcls {
|
|
||||||
lAcls = append(lAcls, *rAcl)
|
|
||||||
}
|
|
||||||
return lAcls, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) DeleteACL(filter AclFilter, validateOnly bool) ([]MatchingAcl, error) {
|
|
||||||
var filters []*AclFilter
|
|
||||||
filters = append(filters, &filter)
|
|
||||||
request := &DeleteAclsRequest{Filters: filters}
|
|
||||||
|
|
||||||
if ca.conf.Version.IsAtLeast(V2_0_0_0) {
|
|
||||||
request.Version = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := ca.Controller()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rsp, err := b.DeleteAcls(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var mAcls []MatchingAcl
|
|
||||||
for _, fr := range rsp.FilterResponses {
|
|
||||||
for _, mACL := range fr.MatchingAcls {
|
|
||||||
mAcls = append(mAcls, *mACL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mAcls, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) DescribeConsumerGroups(groups []string) (result []*GroupDescription, err error) {
|
|
||||||
groupsPerBroker := make(map[*Broker][]string)
|
|
||||||
|
|
||||||
for _, group := range groups {
|
|
||||||
controller, err := ca.client.Coordinator(group)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
groupsPerBroker[controller] = append(groupsPerBroker[controller], group)
|
|
||||||
}
|
|
||||||
|
|
||||||
for broker, brokerGroups := range groupsPerBroker {
|
|
||||||
response, err := broker.DescribeGroups(&DescribeGroupsRequest{
|
|
||||||
Groups: brokerGroups,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result = append(result, response.Groups...)
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) ListConsumerGroups() (allGroups map[string]string, err error) {
|
|
||||||
allGroups = make(map[string]string)
|
|
||||||
|
|
||||||
// Query brokers in parallel, since we have to query *all* brokers
|
|
||||||
brokers := ca.client.Brokers()
|
|
||||||
groupMaps := make(chan map[string]string, len(brokers))
|
|
||||||
errors := make(chan error, len(brokers))
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
|
|
||||||
for _, b := range brokers {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(b *Broker, conf *Config) {
|
|
||||||
defer wg.Done()
|
|
||||||
_ = b.Open(conf) // Ensure that broker is opened
|
|
||||||
|
|
||||||
response, err := b.ListGroups(&ListGroupsRequest{})
|
|
||||||
if err != nil {
|
|
||||||
errors <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
groups := make(map[string]string)
|
|
||||||
for group, typ := range response.Groups {
|
|
||||||
groups[group] = typ
|
|
||||||
}
|
|
||||||
|
|
||||||
groupMaps <- groups
|
|
||||||
}(b, ca.conf)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
close(groupMaps)
|
|
||||||
close(errors)
|
|
||||||
|
|
||||||
for groupMap := range groupMaps {
|
|
||||||
for group, protocolType := range groupMap {
|
|
||||||
allGroups[group] = protocolType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intentionally return only the first error for simplicity
|
|
||||||
err = <-errors
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) ListConsumerGroupOffsets(group string, topicPartitions map[string][]int32) (*OffsetFetchResponse, error) {
|
|
||||||
coordinator, err := ca.client.Coordinator(group)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &OffsetFetchRequest{
|
|
||||||
ConsumerGroup: group,
|
|
||||||
partitions: topicPartitions,
|
|
||||||
}
|
|
||||||
|
|
||||||
if ca.conf.Version.IsAtLeast(V0_10_2_0) {
|
|
||||||
request.Version = 2
|
|
||||||
} else if ca.conf.Version.IsAtLeast(V0_8_2_2) {
|
|
||||||
request.Version = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return coordinator.FetchOffset(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) DeleteConsumerGroup(group string) error {
|
|
||||||
coordinator, err := ca.client.Coordinator(group)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &DeleteGroupsRequest{
|
|
||||||
Groups: []string{group},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := coordinator.DeleteGroups(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
groupErr, ok := resp.GroupErrorCodes[group]
|
|
||||||
if !ok {
|
|
||||||
return ErrIncompleteResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
if groupErr != ErrNoError {
|
|
||||||
return groupErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *clusterAdmin) DescribeLogDirs(brokerIds []int32) (allLogDirs map[int32][]DescribeLogDirsResponseDirMetadata, err error) {
|
|
||||||
allLogDirs = make(map[int32][]DescribeLogDirsResponseDirMetadata)
|
|
||||||
|
|
||||||
// Query brokers in parallel, since we may have to query multiple brokers
|
|
||||||
logDirsMaps := make(chan map[int32][]DescribeLogDirsResponseDirMetadata, len(brokerIds))
|
|
||||||
errors := make(chan error, len(brokerIds))
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
|
|
||||||
for _, b := range brokerIds {
|
|
||||||
wg.Add(1)
|
|
||||||
broker, err := ca.findBroker(b)
|
|
||||||
if err != nil {
|
|
||||||
Logger.Printf("Unable to find broker with ID = %v\n", b)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
go func(b *Broker, conf *Config) {
|
|
||||||
defer wg.Done()
|
|
||||||
_ = b.Open(conf) // Ensure that broker is opened
|
|
||||||
|
|
||||||
response, err := b.DescribeLogDirs(&DescribeLogDirsRequest{})
|
|
||||||
if err != nil {
|
|
||||||
errors <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logDirs := make(map[int32][]DescribeLogDirsResponseDirMetadata)
|
|
||||||
logDirs[b.ID()] = response.LogDirs
|
|
||||||
logDirsMaps <- logDirs
|
|
||||||
}(broker, ca.conf)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
close(logDirsMaps)
|
|
||||||
close(errors)
|
|
||||||
|
|
||||||
for logDirsMap := range logDirsMaps {
|
|
||||||
for id, logDirs := range logDirsMap {
|
|
||||||
allLogDirs[id] = logDirs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intentionally return only the first error for simplicity
|
|
||||||
err = <-errors
|
|
||||||
return
|
|
||||||
}
|
|
||||||
-126
@@ -1,126 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
//AlterConfigsRequest is an alter config request type
|
|
||||||
type AlterConfigsRequest struct {
|
|
||||||
Resources []*AlterConfigsResource
|
|
||||||
ValidateOnly bool
|
|
||||||
}
|
|
||||||
|
|
||||||
//AlterConfigsResource is an alter config resource type
|
|
||||||
type AlterConfigsResource struct {
|
|
||||||
Type ConfigResourceType
|
|
||||||
Name string
|
|
||||||
ConfigEntries map[string]*string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlterConfigsRequest) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putArrayLength(len(a.Resources)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, r := range a.Resources {
|
|
||||||
if err := r.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pe.putBool(a.ValidateOnly)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlterConfigsRequest) decode(pd packetDecoder, version int16) error {
|
|
||||||
resourceCount, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
a.Resources = make([]*AlterConfigsResource, resourceCount)
|
|
||||||
for i := range a.Resources {
|
|
||||||
r := &AlterConfigsResource{}
|
|
||||||
err = r.decode(pd, version)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.Resources[i] = r
|
|
||||||
}
|
|
||||||
|
|
||||||
validateOnly, err := pd.getBool()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
a.ValidateOnly = validateOnly
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlterConfigsResource) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt8(int8(a.Type))
|
|
||||||
|
|
||||||
if err := pe.putString(a.Name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(a.ConfigEntries)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for configKey, configValue := range a.ConfigEntries {
|
|
||||||
if err := pe.putString(configKey); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := pe.putNullableString(configValue); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlterConfigsResource) decode(pd packetDecoder, version int16) error {
|
|
||||||
t, err := pd.getInt8()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.Type = ConfigResourceType(t)
|
|
||||||
|
|
||||||
name, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.Name = name
|
|
||||||
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if n > 0 {
|
|
||||||
a.ConfigEntries = make(map[string]*string, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
configKey, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if a.ConfigEntries[configKey], err = pd.getNullableString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlterConfigsRequest) key() int16 {
|
|
||||||
return 33
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlterConfigsRequest) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlterConfigsRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlterConfigsRequest) requiredVersion() KafkaVersion {
|
|
||||||
return V0_11_0_0
|
|
||||||
}
|
|
||||||
-101
@@ -1,101 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
//AlterConfigsResponse is a response type for alter config
|
|
||||||
type AlterConfigsResponse struct {
|
|
||||||
ThrottleTime time.Duration
|
|
||||||
Resources []*AlterConfigsResourceResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
//AlterConfigsResourceResponse is a response type for alter config resource
|
|
||||||
type AlterConfigsResourceResponse struct {
|
|
||||||
ErrorCode int16
|
|
||||||
ErrorMsg string
|
|
||||||
Type ConfigResourceType
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlterConfigsResponse) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt32(int32(a.ThrottleTime / time.Millisecond))
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(a.Resources)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range a.Resources {
|
|
||||||
pe.putInt16(a.Resources[i].ErrorCode)
|
|
||||||
err := pe.putString(a.Resources[i].ErrorMsg)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
pe.putInt8(int8(a.Resources[i].Type))
|
|
||||||
err = pe.putString(a.Resources[i].Name)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlterConfigsResponse) decode(pd packetDecoder, version int16) error {
|
|
||||||
throttleTime, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.ThrottleTime = time.Duration(throttleTime) * time.Millisecond
|
|
||||||
|
|
||||||
responseCount, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
a.Resources = make([]*AlterConfigsResourceResponse, responseCount)
|
|
||||||
|
|
||||||
for i := range a.Resources {
|
|
||||||
a.Resources[i] = new(AlterConfigsResourceResponse)
|
|
||||||
|
|
||||||
errCode, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.Resources[i].ErrorCode = errCode
|
|
||||||
|
|
||||||
e, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.Resources[i].ErrorMsg = e
|
|
||||||
|
|
||||||
t, err := pd.getInt8()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.Resources[i].Type = ConfigResourceType(t)
|
|
||||||
|
|
||||||
name, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.Resources[i].Name = name
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlterConfigsResponse) key() int16 {
|
|
||||||
return 32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlterConfigsResponse) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlterConfigsResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AlterConfigsResponse) requiredVersion() KafkaVersion {
|
|
||||||
return V0_11_0_0
|
|
||||||
}
|
|
||||||
-130
@@ -1,130 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
type alterPartitionReassignmentsBlock struct {
|
|
||||||
replicas []int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *alterPartitionReassignmentsBlock) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putNullableCompactInt32Array(b.replicas); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pe.putEmptyTaggedFieldArray()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *alterPartitionReassignmentsBlock) decode(pd packetDecoder) (err error) {
|
|
||||||
if b.replicas, err = pd.getCompactInt32Array(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlterPartitionReassignmentsRequest struct {
|
|
||||||
TimeoutMs int32
|
|
||||||
blocks map[string]map[int32]*alterPartitionReassignmentsBlock
|
|
||||||
Version int16
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *AlterPartitionReassignmentsRequest) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt32(r.TimeoutMs)
|
|
||||||
|
|
||||||
pe.putCompactArrayLength(len(r.blocks))
|
|
||||||
|
|
||||||
for topic, partitions := range r.blocks {
|
|
||||||
if err := pe.putCompactString(topic); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pe.putCompactArrayLength(len(partitions))
|
|
||||||
for partition, block := range partitions {
|
|
||||||
pe.putInt32(partition)
|
|
||||||
if err := block.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pe.putEmptyTaggedFieldArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
pe.putEmptyTaggedFieldArray()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *AlterPartitionReassignmentsRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
r.Version = version
|
|
||||||
|
|
||||||
if r.TimeoutMs, err = pd.getInt32(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
topicCount, err := pd.getCompactArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if topicCount > 0 {
|
|
||||||
r.blocks = make(map[string]map[int32]*alterPartitionReassignmentsBlock)
|
|
||||||
for i := 0; i < topicCount; i++ {
|
|
||||||
topic, err := pd.getCompactString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
partitionCount, err := pd.getCompactArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.blocks[topic] = make(map[int32]*alterPartitionReassignmentsBlock)
|
|
||||||
for j := 0; j < partitionCount; j++ {
|
|
||||||
partition, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
block := &alterPartitionReassignmentsBlock{}
|
|
||||||
if err := block.decode(pd); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.blocks[topic][partition] = block
|
|
||||||
|
|
||||||
if _, err := pd.getEmptyTaggedFieldArray(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := pd.getEmptyTaggedFieldArray(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := pd.getEmptyTaggedFieldArray(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *AlterPartitionReassignmentsRequest) key() int16 {
|
|
||||||
return 45
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *AlterPartitionReassignmentsRequest) version() int16 {
|
|
||||||
return r.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *AlterPartitionReassignmentsRequest) headerVersion() int16 {
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *AlterPartitionReassignmentsRequest) requiredVersion() KafkaVersion {
|
|
||||||
return V2_4_0_0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *AlterPartitionReassignmentsRequest) AddBlock(topic string, partitionID int32, replicas []int32) {
|
|
||||||
if r.blocks == nil {
|
|
||||||
r.blocks = make(map[string]map[int32]*alterPartitionReassignmentsBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.blocks[topic] == nil {
|
|
||||||
r.blocks[topic] = make(map[int32]*alterPartitionReassignmentsBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.blocks[topic][partitionID] = &alterPartitionReassignmentsBlock{replicas}
|
|
||||||
}
|
|
||||||
-157
@@ -1,157 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
type alterPartitionReassignmentsErrorBlock struct {
|
|
||||||
errorCode KError
|
|
||||||
errorMessage *string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *alterPartitionReassignmentsErrorBlock) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt16(int16(b.errorCode))
|
|
||||||
if err := pe.putNullableCompactString(b.errorMessage); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pe.putEmptyTaggedFieldArray()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *alterPartitionReassignmentsErrorBlock) decode(pd packetDecoder) (err error) {
|
|
||||||
errorCode, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.errorCode = KError(errorCode)
|
|
||||||
b.errorMessage, err = pd.getCompactNullableString()
|
|
||||||
|
|
||||||
if _, err := pd.getEmptyTaggedFieldArray(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlterPartitionReassignmentsResponse struct {
|
|
||||||
Version int16
|
|
||||||
ThrottleTimeMs int32
|
|
||||||
ErrorCode KError
|
|
||||||
ErrorMessage *string
|
|
||||||
Errors map[string]map[int32]*alterPartitionReassignmentsErrorBlock
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *AlterPartitionReassignmentsResponse) AddError(topic string, partition int32, kerror KError, message *string) {
|
|
||||||
if r.Errors == nil {
|
|
||||||
r.Errors = make(map[string]map[int32]*alterPartitionReassignmentsErrorBlock)
|
|
||||||
}
|
|
||||||
partitions := r.Errors[topic]
|
|
||||||
if partitions == nil {
|
|
||||||
partitions = make(map[int32]*alterPartitionReassignmentsErrorBlock)
|
|
||||||
r.Errors[topic] = partitions
|
|
||||||
}
|
|
||||||
|
|
||||||
partitions[partition] = &alterPartitionReassignmentsErrorBlock{errorCode: kerror, errorMessage: message}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *AlterPartitionReassignmentsResponse) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt32(r.ThrottleTimeMs)
|
|
||||||
pe.putInt16(int16(r.ErrorCode))
|
|
||||||
if err := pe.putNullableCompactString(r.ErrorMessage); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pe.putCompactArrayLength(len(r.Errors))
|
|
||||||
for topic, partitions := range r.Errors {
|
|
||||||
if err := pe.putCompactString(topic); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pe.putCompactArrayLength(len(partitions))
|
|
||||||
for partition, block := range partitions {
|
|
||||||
pe.putInt32(partition)
|
|
||||||
|
|
||||||
if err := block.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pe.putEmptyTaggedFieldArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
pe.putEmptyTaggedFieldArray()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *AlterPartitionReassignmentsResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
r.Version = version
|
|
||||||
|
|
||||||
if r.ThrottleTimeMs, err = pd.getInt32(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
kerr, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.ErrorCode = KError(kerr)
|
|
||||||
|
|
||||||
if r.ErrorMessage, err = pd.getCompactNullableString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
numTopics, err := pd.getCompactArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if numTopics > 0 {
|
|
||||||
r.Errors = make(map[string]map[int32]*alterPartitionReassignmentsErrorBlock, numTopics)
|
|
||||||
for i := 0; i < numTopics; i++ {
|
|
||||||
topic, err := pd.getCompactString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ongoingPartitionReassignments, err := pd.getCompactArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Errors[topic] = make(map[int32]*alterPartitionReassignmentsErrorBlock, ongoingPartitionReassignments)
|
|
||||||
|
|
||||||
for j := 0; j < ongoingPartitionReassignments; j++ {
|
|
||||||
partition, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
block := &alterPartitionReassignmentsErrorBlock{}
|
|
||||||
if err := block.decode(pd); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Errors[topic][partition] = block
|
|
||||||
}
|
|
||||||
if _, err = pd.getEmptyTaggedFieldArray(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = pd.getEmptyTaggedFieldArray(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *AlterPartitionReassignmentsResponse) key() int16 {
|
|
||||||
return 45
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *AlterPartitionReassignmentsResponse) version() int16 {
|
|
||||||
return r.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *AlterPartitionReassignmentsResponse) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *AlterPartitionReassignmentsResponse) requiredVersion() KafkaVersion {
|
|
||||||
return V2_4_0_0
|
|
||||||
}
|
|
||||||
-29
@@ -1,29 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
//ApiVersionsRequest ...
|
|
||||||
type ApiVersionsRequest struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *ApiVersionsRequest) encode(pe packetEncoder) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *ApiVersionsRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *ApiVersionsRequest) key() int16 {
|
|
||||||
return 18
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *ApiVersionsRequest) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *ApiVersionsRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *ApiVersionsRequest) requiredVersion() KafkaVersion {
|
|
||||||
return V0_10_0_0
|
|
||||||
}
|
|
||||||
-93
@@ -1,93 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
//ApiVersionsResponseBlock is an api version response block type
|
|
||||||
type ApiVersionsResponseBlock struct {
|
|
||||||
ApiKey int16
|
|
||||||
MinVersion int16
|
|
||||||
MaxVersion int16
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *ApiVersionsResponseBlock) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt16(b.ApiKey)
|
|
||||||
pe.putInt16(b.MinVersion)
|
|
||||||
pe.putInt16(b.MaxVersion)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *ApiVersionsResponseBlock) decode(pd packetDecoder) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if b.ApiKey, err = pd.getInt16(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.MinVersion, err = pd.getInt16(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.MaxVersion, err = pd.getInt16(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//ApiVersionsResponse is an api version response type
|
|
||||||
type ApiVersionsResponse struct {
|
|
||||||
Err KError
|
|
||||||
ApiVersions []*ApiVersionsResponseBlock
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ApiVersionsResponse) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt16(int16(r.Err))
|
|
||||||
if err := pe.putArrayLength(len(r.ApiVersions)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, apiVersion := range r.ApiVersions {
|
|
||||||
if err := apiVersion.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ApiVersionsResponse) decode(pd packetDecoder, version int16) error {
|
|
||||||
kerr, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Err = KError(kerr)
|
|
||||||
|
|
||||||
numBlocks, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.ApiVersions = make([]*ApiVersionsResponseBlock, numBlocks)
|
|
||||||
for i := 0; i < numBlocks; i++ {
|
|
||||||
block := new(ApiVersionsResponseBlock)
|
|
||||||
if err := block.decode(pd); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.ApiVersions[i] = block
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ApiVersionsResponse) key() int16 {
|
|
||||||
return 18
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ApiVersionsResponse) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *ApiVersionsResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ApiVersionsResponse) requiredVersion() KafkaVersion {
|
|
||||||
return V0_10_0_0
|
|
||||||
}
|
|
||||||
-1154
File diff suppressed because it is too large
Load Diff
-1061
File diff suppressed because it is too large
Load Diff
-1441
File diff suppressed because it is too large
Load Diff
-1070
File diff suppressed because it is too large
Load Diff
-194
@@ -1,194 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
snappy "github.com/eapache/go-xerial-snappy"
|
|
||||||
"github.com/pierrec/lz4"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
lz4WriterPool = sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return lz4.NewWriter(nil)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
gzipWriterPool = sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return gzip.NewWriter(nil)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
gzipWriterPoolForCompressionLevel1 = sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
gz, err := gzip.NewWriterLevel(nil, 1)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return gz
|
|
||||||
},
|
|
||||||
}
|
|
||||||
gzipWriterPoolForCompressionLevel2 = sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
gz, err := gzip.NewWriterLevel(nil, 2)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return gz
|
|
||||||
},
|
|
||||||
}
|
|
||||||
gzipWriterPoolForCompressionLevel3 = sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
gz, err := gzip.NewWriterLevel(nil, 3)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return gz
|
|
||||||
},
|
|
||||||
}
|
|
||||||
gzipWriterPoolForCompressionLevel4 = sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
gz, err := gzip.NewWriterLevel(nil, 4)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return gz
|
|
||||||
},
|
|
||||||
}
|
|
||||||
gzipWriterPoolForCompressionLevel5 = sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
gz, err := gzip.NewWriterLevel(nil, 5)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return gz
|
|
||||||
},
|
|
||||||
}
|
|
||||||
gzipWriterPoolForCompressionLevel6 = sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
gz, err := gzip.NewWriterLevel(nil, 6)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return gz
|
|
||||||
},
|
|
||||||
}
|
|
||||||
gzipWriterPoolForCompressionLevel7 = sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
gz, err := gzip.NewWriterLevel(nil, 7)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return gz
|
|
||||||
},
|
|
||||||
}
|
|
||||||
gzipWriterPoolForCompressionLevel8 = sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
gz, err := gzip.NewWriterLevel(nil, 8)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return gz
|
|
||||||
},
|
|
||||||
}
|
|
||||||
gzipWriterPoolForCompressionLevel9 = sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
gz, err := gzip.NewWriterLevel(nil, 9)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return gz
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func compress(cc CompressionCodec, level int, data []byte) ([]byte, error) {
|
|
||||||
switch cc {
|
|
||||||
case CompressionNone:
|
|
||||||
return data, nil
|
|
||||||
case CompressionGZIP:
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
buf bytes.Buffer
|
|
||||||
writer *gzip.Writer
|
|
||||||
)
|
|
||||||
|
|
||||||
switch level {
|
|
||||||
case CompressionLevelDefault:
|
|
||||||
writer = gzipWriterPool.Get().(*gzip.Writer)
|
|
||||||
defer gzipWriterPool.Put(writer)
|
|
||||||
writer.Reset(&buf)
|
|
||||||
case 1:
|
|
||||||
writer = gzipWriterPoolForCompressionLevel1.Get().(*gzip.Writer)
|
|
||||||
defer gzipWriterPoolForCompressionLevel1.Put(writer)
|
|
||||||
writer.Reset(&buf)
|
|
||||||
case 2:
|
|
||||||
writer = gzipWriterPoolForCompressionLevel2.Get().(*gzip.Writer)
|
|
||||||
defer gzipWriterPoolForCompressionLevel2.Put(writer)
|
|
||||||
writer.Reset(&buf)
|
|
||||||
case 3:
|
|
||||||
writer = gzipWriterPoolForCompressionLevel3.Get().(*gzip.Writer)
|
|
||||||
defer gzipWriterPoolForCompressionLevel3.Put(writer)
|
|
||||||
writer.Reset(&buf)
|
|
||||||
case 4:
|
|
||||||
writer = gzipWriterPoolForCompressionLevel4.Get().(*gzip.Writer)
|
|
||||||
defer gzipWriterPoolForCompressionLevel4.Put(writer)
|
|
||||||
writer.Reset(&buf)
|
|
||||||
case 5:
|
|
||||||
writer = gzipWriterPoolForCompressionLevel5.Get().(*gzip.Writer)
|
|
||||||
defer gzipWriterPoolForCompressionLevel5.Put(writer)
|
|
||||||
writer.Reset(&buf)
|
|
||||||
case 6:
|
|
||||||
writer = gzipWriterPoolForCompressionLevel6.Get().(*gzip.Writer)
|
|
||||||
defer gzipWriterPoolForCompressionLevel6.Put(writer)
|
|
||||||
writer.Reset(&buf)
|
|
||||||
case 7:
|
|
||||||
writer = gzipWriterPoolForCompressionLevel7.Get().(*gzip.Writer)
|
|
||||||
defer gzipWriterPoolForCompressionLevel7.Put(writer)
|
|
||||||
writer.Reset(&buf)
|
|
||||||
case 8:
|
|
||||||
writer = gzipWriterPoolForCompressionLevel8.Get().(*gzip.Writer)
|
|
||||||
defer gzipWriterPoolForCompressionLevel8.Put(writer)
|
|
||||||
writer.Reset(&buf)
|
|
||||||
case 9:
|
|
||||||
writer = gzipWriterPoolForCompressionLevel9.Get().(*gzip.Writer)
|
|
||||||
defer gzipWriterPoolForCompressionLevel9.Put(writer)
|
|
||||||
writer.Reset(&buf)
|
|
||||||
default:
|
|
||||||
writer, err = gzip.NewWriterLevel(&buf, level)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := writer.Write(data); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := writer.Close(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
case CompressionSnappy:
|
|
||||||
return snappy.Encode(data), nil
|
|
||||||
case CompressionLZ4:
|
|
||||||
writer := lz4WriterPool.Get().(*lz4.Writer)
|
|
||||||
defer lz4WriterPool.Put(writer)
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
writer.Reset(&buf)
|
|
||||||
|
|
||||||
if _, err := writer.Write(data); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := writer.Close(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
case CompressionZSTD:
|
|
||||||
return zstdCompress(nil, data)
|
|
||||||
default:
|
|
||||||
return nil, PacketEncodingError{fmt.Sprintf("unsupported compression codec (%d)", cc)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-749
@@ -1,749 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"compress/gzip"
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"regexp"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/rcrowley/go-metrics"
|
|
||||||
"golang.org/x/net/proxy"
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultClientID = "sarama"
|
|
||||||
|
|
||||||
var validID = regexp.MustCompile(`\A[A-Za-z0-9._-]+\z`)
|
|
||||||
|
|
||||||
// Config is used to pass multiple configuration options to Sarama's constructors.
|
|
||||||
type Config struct {
|
|
||||||
// Admin is the namespace for ClusterAdmin properties used by the administrative Kafka client.
|
|
||||||
Admin struct {
|
|
||||||
Retry struct {
|
|
||||||
// The total number of times to retry sending (retriable) admin requests (default 5).
|
|
||||||
// Similar to the `retries` setting of the JVM AdminClientConfig.
|
|
||||||
Max int
|
|
||||||
// Backoff time between retries of a failed request (default 100ms)
|
|
||||||
Backoff time.Duration
|
|
||||||
}
|
|
||||||
// The maximum duration the administrative Kafka client will wait for ClusterAdmin operations,
|
|
||||||
// including topics, brokers, configurations and ACLs (defaults to 3 seconds).
|
|
||||||
Timeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// Net is the namespace for network-level properties used by the Broker, and
|
|
||||||
// shared by the Client/Producer/Consumer.
|
|
||||||
Net struct {
|
|
||||||
// How many outstanding requests a connection is allowed to have before
|
|
||||||
// sending on it blocks (default 5).
|
|
||||||
MaxOpenRequests int
|
|
||||||
|
|
||||||
// All three of the below configurations are similar to the
|
|
||||||
// `socket.timeout.ms` setting in JVM kafka. All of them default
|
|
||||||
// to 30 seconds.
|
|
||||||
DialTimeout time.Duration // How long to wait for the initial connection.
|
|
||||||
ReadTimeout time.Duration // How long to wait for a response.
|
|
||||||
WriteTimeout time.Duration // How long to wait for a transmit.
|
|
||||||
|
|
||||||
TLS struct {
|
|
||||||
// Whether or not to use TLS when connecting to the broker
|
|
||||||
// (defaults to false).
|
|
||||||
Enable bool
|
|
||||||
// The TLS configuration to use for secure connections if
|
|
||||||
// enabled (defaults to nil).
|
|
||||||
Config *tls.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// SASL based authentication with broker. While there are multiple SASL authentication methods
|
|
||||||
// the current implementation is limited to plaintext (SASL/PLAIN) authentication
|
|
||||||
SASL struct {
|
|
||||||
// Whether or not to use SASL authentication when connecting to the broker
|
|
||||||
// (defaults to false).
|
|
||||||
Enable bool
|
|
||||||
// SASLMechanism is the name of the enabled SASL mechanism.
|
|
||||||
// Possible values: OAUTHBEARER, PLAIN (defaults to PLAIN).
|
|
||||||
Mechanism SASLMechanism
|
|
||||||
// Version is the SASL Protocol Version to use
|
|
||||||
// Kafka > 1.x should use V1, except on Azure EventHub which use V0
|
|
||||||
Version int16
|
|
||||||
// Whether or not to send the Kafka SASL handshake first if enabled
|
|
||||||
// (defaults to true). You should only set this to false if you're using
|
|
||||||
// a non-Kafka SASL proxy.
|
|
||||||
Handshake bool
|
|
||||||
// AuthIdentity is an (optional) authorization identity (authzid) to
|
|
||||||
// use for SASL/PLAIN authentication (if different from User) when
|
|
||||||
// an authenticated user is permitted to act as the presented
|
|
||||||
// alternative user. See RFC4616 for details.
|
|
||||||
AuthIdentity string
|
|
||||||
// User is the authentication identity (authcid) to present for
|
|
||||||
// SASL/PLAIN or SASL/SCRAM authentication
|
|
||||||
User string
|
|
||||||
// Password for SASL/PLAIN authentication
|
|
||||||
Password string
|
|
||||||
// authz id used for SASL/SCRAM authentication
|
|
||||||
SCRAMAuthzID string
|
|
||||||
// SCRAMClientGeneratorFunc is a generator of a user provided implementation of a SCRAM
|
|
||||||
// client used to perform the SCRAM exchange with the server.
|
|
||||||
SCRAMClientGeneratorFunc func() SCRAMClient
|
|
||||||
// TokenProvider is a user-defined callback for generating
|
|
||||||
// access tokens for SASL/OAUTHBEARER auth. See the
|
|
||||||
// AccessTokenProvider interface docs for proper implementation
|
|
||||||
// guidelines.
|
|
||||||
TokenProvider AccessTokenProvider
|
|
||||||
|
|
||||||
GSSAPI GSSAPIConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeepAlive specifies the keep-alive period for an active network connection (defaults to 0).
|
|
||||||
// If zero or positive, keep-alives are enabled.
|
|
||||||
// If negative, keep-alives are disabled.
|
|
||||||
KeepAlive time.Duration
|
|
||||||
|
|
||||||
// LocalAddr is the local address to use when dialing an
|
|
||||||
// address. The address must be of a compatible type for the
|
|
||||||
// network being dialed.
|
|
||||||
// If nil, a local address is automatically chosen.
|
|
||||||
LocalAddr net.Addr
|
|
||||||
|
|
||||||
Proxy struct {
|
|
||||||
// Whether or not to use proxy when connecting to the broker
|
|
||||||
// (defaults to false).
|
|
||||||
Enable bool
|
|
||||||
// The proxy dialer to use enabled (defaults to nil).
|
|
||||||
Dialer proxy.Dialer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metadata is the namespace for metadata management properties used by the
|
|
||||||
// Client, and shared by the Producer/Consumer.
|
|
||||||
Metadata struct {
|
|
||||||
Retry struct {
|
|
||||||
// The total number of times to retry a metadata request when the
|
|
||||||
// cluster is in the middle of a leader election (default 3).
|
|
||||||
Max int
|
|
||||||
// How long to wait for leader election to occur before retrying
|
|
||||||
// (default 250ms). Similar to the JVM's `retry.backoff.ms`.
|
|
||||||
Backoff time.Duration
|
|
||||||
// Called to compute backoff time dynamically. Useful for implementing
|
|
||||||
// more sophisticated backoff strategies. This takes precedence over
|
|
||||||
// `Backoff` if set.
|
|
||||||
BackoffFunc func(retries, maxRetries int) time.Duration
|
|
||||||
}
|
|
||||||
// How frequently to refresh the cluster metadata in the background.
|
|
||||||
// Defaults to 10 minutes. Set to 0 to disable. Similar to
|
|
||||||
// `topic.metadata.refresh.interval.ms` in the JVM version.
|
|
||||||
RefreshFrequency time.Duration
|
|
||||||
|
|
||||||
// Whether to maintain a full set of metadata for all topics, or just
|
|
||||||
// the minimal set that has been necessary so far. The full set is simpler
|
|
||||||
// and usually more convenient, but can take up a substantial amount of
|
|
||||||
// memory if you have many topics and partitions. Defaults to true.
|
|
||||||
Full bool
|
|
||||||
|
|
||||||
// How long to wait for a successful metadata response.
|
|
||||||
// Disabled by default which means a metadata request against an unreachable
|
|
||||||
// cluster (all brokers are unreachable or unresponsive) can take up to
|
|
||||||
// `Net.[Dial|Read]Timeout * BrokerCount * (Metadata.Retry.Max + 1) + Metadata.Retry.Backoff * Metadata.Retry.Max`
|
|
||||||
// to fail.
|
|
||||||
Timeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// Producer is the namespace for configuration related to producing messages,
|
|
||||||
// used by the Producer.
|
|
||||||
Producer struct {
|
|
||||||
// The maximum permitted size of a message (defaults to 1000000). Should be
|
|
||||||
// set equal to or smaller than the broker's `message.max.bytes`.
|
|
||||||
MaxMessageBytes int
|
|
||||||
// The level of acknowledgement reliability needed from the broker (defaults
|
|
||||||
// to WaitForLocal). Equivalent to the `request.required.acks` setting of the
|
|
||||||
// JVM producer.
|
|
||||||
RequiredAcks RequiredAcks
|
|
||||||
// The maximum duration the broker will wait the receipt of the number of
|
|
||||||
// RequiredAcks (defaults to 10 seconds). This is only relevant when
|
|
||||||
// RequiredAcks is set to WaitForAll or a number > 1. Only supports
|
|
||||||
// millisecond resolution, nanoseconds will be truncated. Equivalent to
|
|
||||||
// the JVM producer's `request.timeout.ms` setting.
|
|
||||||
Timeout time.Duration
|
|
||||||
// The type of compression to use on messages (defaults to no compression).
|
|
||||||
// Similar to `compression.codec` setting of the JVM producer.
|
|
||||||
Compression CompressionCodec
|
|
||||||
// The level of compression to use on messages. The meaning depends
|
|
||||||
// on the actual compression type used and defaults to default compression
|
|
||||||
// level for the codec.
|
|
||||||
CompressionLevel int
|
|
||||||
// Generates partitioners for choosing the partition to send messages to
|
|
||||||
// (defaults to hashing the message key). Similar to the `partitioner.class`
|
|
||||||
// setting for the JVM producer.
|
|
||||||
Partitioner PartitionerConstructor
|
|
||||||
// If enabled, the producer will ensure that exactly one copy of each message is
|
|
||||||
// written.
|
|
||||||
Idempotent bool
|
|
||||||
|
|
||||||
// Return specifies what channels will be populated. If they are set to true,
|
|
||||||
// you must read from the respective channels to prevent deadlock. If,
|
|
||||||
// however, this config is used to create a `SyncProducer`, both must be set
|
|
||||||
// to true and you shall not read from the channels since the producer does
|
|
||||||
// this internally.
|
|
||||||
Return struct {
|
|
||||||
// If enabled, successfully delivered messages will be returned on the
|
|
||||||
// Successes channel (default disabled).
|
|
||||||
Successes bool
|
|
||||||
|
|
||||||
// If enabled, messages that failed to deliver will be returned on the
|
|
||||||
// Errors channel, including error (default enabled).
|
|
||||||
Errors bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// The following config options control how often messages are batched up and
|
|
||||||
// sent to the broker. By default, messages are sent as fast as possible, and
|
|
||||||
// all messages received while the current batch is in-flight are placed
|
|
||||||
// into the subsequent batch.
|
|
||||||
Flush struct {
|
|
||||||
// The best-effort number of bytes needed to trigger a flush. Use the
|
|
||||||
// global sarama.MaxRequestSize to set a hard upper limit.
|
|
||||||
Bytes int
|
|
||||||
// The best-effort number of messages needed to trigger a flush. Use
|
|
||||||
// `MaxMessages` to set a hard upper limit.
|
|
||||||
Messages int
|
|
||||||
// The best-effort frequency of flushes. Equivalent to
|
|
||||||
// `queue.buffering.max.ms` setting of JVM producer.
|
|
||||||
Frequency time.Duration
|
|
||||||
// The maximum number of messages the producer will send in a single
|
|
||||||
// broker request. Defaults to 0 for unlimited. Similar to
|
|
||||||
// `queue.buffering.max.messages` in the JVM producer.
|
|
||||||
MaxMessages int
|
|
||||||
}
|
|
||||||
|
|
||||||
Retry struct {
|
|
||||||
// The total number of times to retry sending a message (default 3).
|
|
||||||
// Similar to the `message.send.max.retries` setting of the JVM producer.
|
|
||||||
Max int
|
|
||||||
// How long to wait for the cluster to settle between retries
|
|
||||||
// (default 100ms). Similar to the `retry.backoff.ms` setting of the
|
|
||||||
// JVM producer.
|
|
||||||
Backoff time.Duration
|
|
||||||
// Called to compute backoff time dynamically. Useful for implementing
|
|
||||||
// more sophisticated backoff strategies. This takes precedence over
|
|
||||||
// `Backoff` if set.
|
|
||||||
BackoffFunc func(retries, maxRetries int) time.Duration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consumer is the namespace for configuration related to consuming messages,
|
|
||||||
// used by the Consumer.
|
|
||||||
Consumer struct {
|
|
||||||
|
|
||||||
// Group is the namespace for configuring consumer group.
|
|
||||||
Group struct {
|
|
||||||
Session struct {
|
|
||||||
// The timeout used to detect consumer failures when using Kafka's group management facility.
|
|
||||||
// The consumer sends periodic heartbeats to indicate its liveness to the broker.
|
|
||||||
// If no heartbeats are received by the broker before the expiration of this session timeout,
|
|
||||||
// then the broker will remove this consumer from the group and initiate a rebalance.
|
|
||||||
// Note that the value must be in the allowable range as configured in the broker configuration
|
|
||||||
// by `group.min.session.timeout.ms` and `group.max.session.timeout.ms` (default 10s)
|
|
||||||
Timeout time.Duration
|
|
||||||
}
|
|
||||||
Heartbeat struct {
|
|
||||||
// The expected time between heartbeats to the consumer coordinator when using Kafka's group
|
|
||||||
// management facilities. Heartbeats are used to ensure that the consumer's session stays active and
|
|
||||||
// to facilitate rebalancing when new consumers join or leave the group.
|
|
||||||
// The value must be set lower than Consumer.Group.Session.Timeout, but typically should be set no
|
|
||||||
// higher than 1/3 of that value.
|
|
||||||
// It can be adjusted even lower to control the expected time for normal rebalances (default 3s)
|
|
||||||
Interval time.Duration
|
|
||||||
}
|
|
||||||
Rebalance struct {
|
|
||||||
// Strategy for allocating topic partitions to members (default BalanceStrategyRange)
|
|
||||||
Strategy BalanceStrategy
|
|
||||||
// The maximum allowed time for each worker to join the group once a rebalance has begun.
|
|
||||||
// This is basically a limit on the amount of time needed for all tasks to flush any pending
|
|
||||||
// data and commit offsets. If the timeout is exceeded, then the worker will be removed from
|
|
||||||
// the group, which will cause offset commit failures (default 60s).
|
|
||||||
Timeout time.Duration
|
|
||||||
|
|
||||||
Retry struct {
|
|
||||||
// When a new consumer joins a consumer group the set of consumers attempt to "rebalance"
|
|
||||||
// the load to assign partitions to each consumer. If the set of consumers changes while
|
|
||||||
// this assignment is taking place the rebalance will fail and retry. This setting controls
|
|
||||||
// the maximum number of attempts before giving up (default 4).
|
|
||||||
Max int
|
|
||||||
// Backoff time between retries during rebalance (default 2s)
|
|
||||||
Backoff time.Duration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Member struct {
|
|
||||||
// Custom metadata to include when joining the group. The user data for all joined members
|
|
||||||
// can be retrieved by sending a DescribeGroupRequest to the broker that is the
|
|
||||||
// coordinator for the group.
|
|
||||||
UserData []byte
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Retry struct {
|
|
||||||
// How long to wait after a failing to read from a partition before
|
|
||||||
// trying again (default 2s).
|
|
||||||
Backoff time.Duration
|
|
||||||
// Called to compute backoff time dynamically. Useful for implementing
|
|
||||||
// more sophisticated backoff strategies. This takes precedence over
|
|
||||||
// `Backoff` if set.
|
|
||||||
BackoffFunc func(retries int) time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch is the namespace for controlling how many bytes are retrieved by any
|
|
||||||
// given request.
|
|
||||||
Fetch struct {
|
|
||||||
// The minimum number of message bytes to fetch in a request - the broker
|
|
||||||
// will wait until at least this many are available. The default is 1,
|
|
||||||
// as 0 causes the consumer to spin when no messages are available.
|
|
||||||
// Equivalent to the JVM's `fetch.min.bytes`.
|
|
||||||
Min int32
|
|
||||||
// The default number of message bytes to fetch from the broker in each
|
|
||||||
// request (default 1MB). This should be larger than the majority of
|
|
||||||
// your messages, or else the consumer will spend a lot of time
|
|
||||||
// negotiating sizes and not actually consuming. Similar to the JVM's
|
|
||||||
// `fetch.message.max.bytes`.
|
|
||||||
Default int32
|
|
||||||
// The maximum number of message bytes to fetch from the broker in a
|
|
||||||
// single request. Messages larger than this will return
|
|
||||||
// ErrMessageTooLarge and will not be consumable, so you must be sure
|
|
||||||
// this is at least as large as your largest message. Defaults to 0
|
|
||||||
// (no limit). Similar to the JVM's `fetch.message.max.bytes`. The
|
|
||||||
// global `sarama.MaxResponseSize` still applies.
|
|
||||||
Max int32
|
|
||||||
}
|
|
||||||
// The maximum amount of time the broker will wait for Consumer.Fetch.Min
|
|
||||||
// bytes to become available before it returns fewer than that anyways. The
|
|
||||||
// default is 250ms, since 0 causes the consumer to spin when no events are
|
|
||||||
// available. 100-500ms is a reasonable range for most cases. Kafka only
|
|
||||||
// supports precision up to milliseconds; nanoseconds will be truncated.
|
|
||||||
// Equivalent to the JVM's `fetch.wait.max.ms`.
|
|
||||||
MaxWaitTime time.Duration
|
|
||||||
|
|
||||||
// The maximum amount of time the consumer expects a message takes to
|
|
||||||
// process for the user. If writing to the Messages channel takes longer
|
|
||||||
// than this, that partition will stop fetching more messages until it
|
|
||||||
// can proceed again.
|
|
||||||
// Note that, since the Messages channel is buffered, the actual grace time is
|
|
||||||
// (MaxProcessingTime * ChannelBufferSize). Defaults to 100ms.
|
|
||||||
// If a message is not written to the Messages channel between two ticks
|
|
||||||
// of the expiryTicker then a timeout is detected.
|
|
||||||
// Using a ticker instead of a timer to detect timeouts should typically
|
|
||||||
// result in many fewer calls to Timer functions which may result in a
|
|
||||||
// significant performance improvement if many messages are being sent
|
|
||||||
// and timeouts are infrequent.
|
|
||||||
// The disadvantage of using a ticker instead of a timer is that
|
|
||||||
// timeouts will be less accurate. That is, the effective timeout could
|
|
||||||
// be between `MaxProcessingTime` and `2 * MaxProcessingTime`. For
|
|
||||||
// example, if `MaxProcessingTime` is 100ms then a delay of 180ms
|
|
||||||
// between two messages being sent may not be recognized as a timeout.
|
|
||||||
MaxProcessingTime time.Duration
|
|
||||||
|
|
||||||
// Return specifies what channels will be populated. If they are set to true,
|
|
||||||
// you must read from them to prevent deadlock.
|
|
||||||
Return struct {
|
|
||||||
// If enabled, any errors that occurred while consuming are returned on
|
|
||||||
// the Errors channel (default disabled).
|
|
||||||
Errors bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Offsets specifies configuration for how and when to commit consumed
|
|
||||||
// offsets. This currently requires the manual use of an OffsetManager
|
|
||||||
// but will eventually be automated.
|
|
||||||
Offsets struct {
|
|
||||||
// Deprecated: CommitInterval exists for historical compatibility
|
|
||||||
// and should not be used. Please use Consumer.Offsets.AutoCommit
|
|
||||||
CommitInterval time.Duration
|
|
||||||
|
|
||||||
// AutoCommit specifies configuration for commit messages automatically.
|
|
||||||
AutoCommit struct {
|
|
||||||
// Whether or not to auto-commit updated offsets back to the broker.
|
|
||||||
// (default enabled).
|
|
||||||
Enable bool
|
|
||||||
|
|
||||||
// How frequently to commit updated offsets. Ineffective unless
|
|
||||||
// auto-commit is enabled (default 1s)
|
|
||||||
Interval time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// The initial offset to use if no offset was previously committed.
|
|
||||||
// Should be OffsetNewest or OffsetOldest. Defaults to OffsetNewest.
|
|
||||||
Initial int64
|
|
||||||
|
|
||||||
// The retention duration for committed offsets. If zero, disabled
|
|
||||||
// (in which case the `offsets.retention.minutes` option on the
|
|
||||||
// broker will be used). Kafka only supports precision up to
|
|
||||||
// milliseconds; nanoseconds will be truncated. Requires Kafka
|
|
||||||
// broker version 0.9.0 or later.
|
|
||||||
// (default is 0: disabled).
|
|
||||||
Retention time.Duration
|
|
||||||
|
|
||||||
Retry struct {
|
|
||||||
// The total number of times to retry failing commit
|
|
||||||
// requests during OffsetManager shutdown (default 3).
|
|
||||||
Max int
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsolationLevel support 2 mode:
|
|
||||||
// - use `ReadUncommitted` (default) to consume and return all messages in message channel
|
|
||||||
// - use `ReadCommitted` to hide messages that are part of an aborted transaction
|
|
||||||
IsolationLevel IsolationLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
// A user-provided string sent with every request to the brokers for logging,
|
|
||||||
// debugging, and auditing purposes. Defaults to "sarama", but you should
|
|
||||||
// probably set it to something specific to your application.
|
|
||||||
ClientID string
|
|
||||||
// A rack identifier for this client. This can be any string value which
|
|
||||||
// indicates where this client is physically located.
|
|
||||||
// It corresponds with the broker config 'broker.rack'
|
|
||||||
RackID string
|
|
||||||
// The number of events to buffer in internal and external channels. This
|
|
||||||
// permits the producer and consumer to continue processing some messages
|
|
||||||
// in the background while user code is working, greatly improving throughput.
|
|
||||||
// Defaults to 256.
|
|
||||||
ChannelBufferSize int
|
|
||||||
// The version of Kafka that Sarama will assume it is running against.
|
|
||||||
// Defaults to the oldest supported stable version. Since Kafka provides
|
|
||||||
// backwards-compatibility, setting it to a version older than you have
|
|
||||||
// will not break anything, although it may prevent you from using the
|
|
||||||
// latest features. Setting it to a version greater than you are actually
|
|
||||||
// running may lead to random breakage.
|
|
||||||
Version KafkaVersion
|
|
||||||
// The registry to define metrics into.
|
|
||||||
// Defaults to a local registry.
|
|
||||||
// If you want to disable metrics gathering, set "metrics.UseNilMetrics" to "true"
|
|
||||||
// prior to starting Sarama.
|
|
||||||
// See Examples on how to use the metrics registry
|
|
||||||
MetricRegistry metrics.Registry
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConfig returns a new configuration instance with sane defaults.
|
|
||||||
func NewConfig() *Config {
|
|
||||||
c := &Config{}
|
|
||||||
|
|
||||||
c.Admin.Retry.Max = 5
|
|
||||||
c.Admin.Retry.Backoff = 100 * time.Millisecond
|
|
||||||
c.Admin.Timeout = 3 * time.Second
|
|
||||||
|
|
||||||
c.Net.MaxOpenRequests = 5
|
|
||||||
c.Net.DialTimeout = 30 * time.Second
|
|
||||||
c.Net.ReadTimeout = 30 * time.Second
|
|
||||||
c.Net.WriteTimeout = 30 * time.Second
|
|
||||||
c.Net.SASL.Handshake = true
|
|
||||||
c.Net.SASL.Version = SASLHandshakeV0
|
|
||||||
|
|
||||||
c.Metadata.Retry.Max = 3
|
|
||||||
c.Metadata.Retry.Backoff = 250 * time.Millisecond
|
|
||||||
c.Metadata.RefreshFrequency = 10 * time.Minute
|
|
||||||
c.Metadata.Full = true
|
|
||||||
|
|
||||||
c.Producer.MaxMessageBytes = 1000000
|
|
||||||
c.Producer.RequiredAcks = WaitForLocal
|
|
||||||
c.Producer.Timeout = 10 * time.Second
|
|
||||||
c.Producer.Partitioner = NewHashPartitioner
|
|
||||||
c.Producer.Retry.Max = 3
|
|
||||||
c.Producer.Retry.Backoff = 100 * time.Millisecond
|
|
||||||
c.Producer.Return.Errors = true
|
|
||||||
c.Producer.CompressionLevel = CompressionLevelDefault
|
|
||||||
|
|
||||||
c.Consumer.Fetch.Min = 1
|
|
||||||
c.Consumer.Fetch.Default = 1024 * 1024
|
|
||||||
c.Consumer.Retry.Backoff = 2 * time.Second
|
|
||||||
c.Consumer.MaxWaitTime = 250 * time.Millisecond
|
|
||||||
c.Consumer.MaxProcessingTime = 100 * time.Millisecond
|
|
||||||
c.Consumer.Return.Errors = false
|
|
||||||
c.Consumer.Offsets.AutoCommit.Enable = true
|
|
||||||
c.Consumer.Offsets.AutoCommit.Interval = 1 * time.Second
|
|
||||||
c.Consumer.Offsets.Initial = OffsetNewest
|
|
||||||
c.Consumer.Offsets.Retry.Max = 3
|
|
||||||
|
|
||||||
c.Consumer.Group.Session.Timeout = 10 * time.Second
|
|
||||||
c.Consumer.Group.Heartbeat.Interval = 3 * time.Second
|
|
||||||
c.Consumer.Group.Rebalance.Strategy = BalanceStrategyRange
|
|
||||||
c.Consumer.Group.Rebalance.Timeout = 60 * time.Second
|
|
||||||
c.Consumer.Group.Rebalance.Retry.Max = 4
|
|
||||||
c.Consumer.Group.Rebalance.Retry.Backoff = 2 * time.Second
|
|
||||||
|
|
||||||
c.ClientID = defaultClientID
|
|
||||||
c.ChannelBufferSize = 256
|
|
||||||
c.Version = MinVersion
|
|
||||||
c.MetricRegistry = metrics.NewRegistry()
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate checks a Config instance. It will return a
|
|
||||||
// ConfigurationError if the specified values don't make sense.
|
|
||||||
func (c *Config) Validate() error {
|
|
||||||
// some configuration values should be warned on but not fail completely, do those first
|
|
||||||
if !c.Net.TLS.Enable && c.Net.TLS.Config != nil {
|
|
||||||
Logger.Println("Net.TLS is disabled but a non-nil configuration was provided.")
|
|
||||||
}
|
|
||||||
if !c.Net.SASL.Enable {
|
|
||||||
if c.Net.SASL.User != "" {
|
|
||||||
Logger.Println("Net.SASL is disabled but a non-empty username was provided.")
|
|
||||||
}
|
|
||||||
if c.Net.SASL.Password != "" {
|
|
||||||
Logger.Println("Net.SASL is disabled but a non-empty password was provided.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.Producer.RequiredAcks > 1 {
|
|
||||||
Logger.Println("Producer.RequiredAcks > 1 is deprecated and will raise an exception with kafka >= 0.8.2.0.")
|
|
||||||
}
|
|
||||||
if c.Producer.MaxMessageBytes >= int(MaxRequestSize) {
|
|
||||||
Logger.Println("Producer.MaxMessageBytes must be smaller than MaxRequestSize; it will be ignored.")
|
|
||||||
}
|
|
||||||
if c.Producer.Flush.Bytes >= int(MaxRequestSize) {
|
|
||||||
Logger.Println("Producer.Flush.Bytes must be smaller than MaxRequestSize; it will be ignored.")
|
|
||||||
}
|
|
||||||
if (c.Producer.Flush.Bytes > 0 || c.Producer.Flush.Messages > 0) && c.Producer.Flush.Frequency == 0 {
|
|
||||||
Logger.Println("Producer.Flush: Bytes or Messages are set, but Frequency is not; messages may not get flushed.")
|
|
||||||
}
|
|
||||||
if c.Producer.Timeout%time.Millisecond != 0 {
|
|
||||||
Logger.Println("Producer.Timeout only supports millisecond resolution; nanoseconds will be truncated.")
|
|
||||||
}
|
|
||||||
if c.Consumer.MaxWaitTime < 100*time.Millisecond {
|
|
||||||
Logger.Println("Consumer.MaxWaitTime is very low, which can cause high CPU and network usage. See documentation for details.")
|
|
||||||
}
|
|
||||||
if c.Consumer.MaxWaitTime%time.Millisecond != 0 {
|
|
||||||
Logger.Println("Consumer.MaxWaitTime only supports millisecond precision; nanoseconds will be truncated.")
|
|
||||||
}
|
|
||||||
if c.Consumer.Offsets.Retention%time.Millisecond != 0 {
|
|
||||||
Logger.Println("Consumer.Offsets.Retention only supports millisecond precision; nanoseconds will be truncated.")
|
|
||||||
}
|
|
||||||
if c.Consumer.Group.Session.Timeout%time.Millisecond != 0 {
|
|
||||||
Logger.Println("Consumer.Group.Session.Timeout only supports millisecond precision; nanoseconds will be truncated.")
|
|
||||||
}
|
|
||||||
if c.Consumer.Group.Heartbeat.Interval%time.Millisecond != 0 {
|
|
||||||
Logger.Println("Consumer.Group.Heartbeat.Interval only supports millisecond precision; nanoseconds will be truncated.")
|
|
||||||
}
|
|
||||||
if c.Consumer.Group.Rebalance.Timeout%time.Millisecond != 0 {
|
|
||||||
Logger.Println("Consumer.Group.Rebalance.Timeout only supports millisecond precision; nanoseconds will be truncated.")
|
|
||||||
}
|
|
||||||
if c.ClientID == defaultClientID {
|
|
||||||
Logger.Println("ClientID is the default of 'sarama', you should consider setting it to something application-specific.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate Net values
|
|
||||||
switch {
|
|
||||||
case c.Net.MaxOpenRequests <= 0:
|
|
||||||
return ConfigurationError("Net.MaxOpenRequests must be > 0")
|
|
||||||
case c.Net.DialTimeout <= 0:
|
|
||||||
return ConfigurationError("Net.DialTimeout must be > 0")
|
|
||||||
case c.Net.ReadTimeout <= 0:
|
|
||||||
return ConfigurationError("Net.ReadTimeout must be > 0")
|
|
||||||
case c.Net.WriteTimeout <= 0:
|
|
||||||
return ConfigurationError("Net.WriteTimeout must be > 0")
|
|
||||||
case c.Net.SASL.Enable:
|
|
||||||
if c.Net.SASL.Mechanism == "" {
|
|
||||||
c.Net.SASL.Mechanism = SASLTypePlaintext
|
|
||||||
}
|
|
||||||
|
|
||||||
switch c.Net.SASL.Mechanism {
|
|
||||||
case SASLTypePlaintext:
|
|
||||||
if c.Net.SASL.User == "" {
|
|
||||||
return ConfigurationError("Net.SASL.User must not be empty when SASL is enabled")
|
|
||||||
}
|
|
||||||
if c.Net.SASL.Password == "" {
|
|
||||||
return ConfigurationError("Net.SASL.Password must not be empty when SASL is enabled")
|
|
||||||
}
|
|
||||||
case SASLTypeOAuth:
|
|
||||||
if c.Net.SASL.TokenProvider == nil {
|
|
||||||
return ConfigurationError("An AccessTokenProvider instance must be provided to Net.SASL.TokenProvider")
|
|
||||||
}
|
|
||||||
case SASLTypeSCRAMSHA256, SASLTypeSCRAMSHA512:
|
|
||||||
if c.Net.SASL.User == "" {
|
|
||||||
return ConfigurationError("Net.SASL.User must not be empty when SASL is enabled")
|
|
||||||
}
|
|
||||||
if c.Net.SASL.Password == "" {
|
|
||||||
return ConfigurationError("Net.SASL.Password must not be empty when SASL is enabled")
|
|
||||||
}
|
|
||||||
if c.Net.SASL.SCRAMClientGeneratorFunc == nil {
|
|
||||||
return ConfigurationError("A SCRAMClientGeneratorFunc function must be provided to Net.SASL.SCRAMClientGeneratorFunc")
|
|
||||||
}
|
|
||||||
case SASLTypeGSSAPI:
|
|
||||||
if c.Net.SASL.GSSAPI.ServiceName == "" {
|
|
||||||
return ConfigurationError("Net.SASL.GSSAPI.ServiceName must not be empty when GSS-API mechanism is used")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Net.SASL.GSSAPI.AuthType == KRB5_USER_AUTH {
|
|
||||||
if c.Net.SASL.GSSAPI.Password == "" {
|
|
||||||
return ConfigurationError("Net.SASL.GSSAPI.Password must not be empty when GSS-API " +
|
|
||||||
"mechanism is used and Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH")
|
|
||||||
}
|
|
||||||
} else if c.Net.SASL.GSSAPI.AuthType == KRB5_KEYTAB_AUTH {
|
|
||||||
if c.Net.SASL.GSSAPI.KeyTabPath == "" {
|
|
||||||
return ConfigurationError("Net.SASL.GSSAPI.KeyTabPath must not be empty when GSS-API mechanism is used" +
|
|
||||||
" and Net.SASL.GSSAPI.AuthType = KRB5_KEYTAB_AUTH")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return ConfigurationError("Net.SASL.GSSAPI.AuthType is invalid. Possible values are KRB5_USER_AUTH and KRB5_KEYTAB_AUTH")
|
|
||||||
}
|
|
||||||
if c.Net.SASL.GSSAPI.KerberosConfigPath == "" {
|
|
||||||
return ConfigurationError("Net.SASL.GSSAPI.KerberosConfigPath must not be empty when GSS-API mechanism is used")
|
|
||||||
}
|
|
||||||
if c.Net.SASL.GSSAPI.Username == "" {
|
|
||||||
return ConfigurationError("Net.SASL.GSSAPI.Username must not be empty when GSS-API mechanism is used")
|
|
||||||
}
|
|
||||||
if c.Net.SASL.GSSAPI.Realm == "" {
|
|
||||||
return ConfigurationError("Net.SASL.GSSAPI.Realm must not be empty when GSS-API mechanism is used")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
msg := fmt.Sprintf("The SASL mechanism configuration is invalid. Possible values are `%s`, `%s`, `%s`, `%s` and `%s`",
|
|
||||||
SASLTypeOAuth, SASLTypePlaintext, SASLTypeSCRAMSHA256, SASLTypeSCRAMSHA512, SASLTypeGSSAPI)
|
|
||||||
return ConfigurationError(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate the Admin values
|
|
||||||
switch {
|
|
||||||
case c.Admin.Timeout <= 0:
|
|
||||||
return ConfigurationError("Admin.Timeout must be > 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate the Metadata values
|
|
||||||
switch {
|
|
||||||
case c.Metadata.Retry.Max < 0:
|
|
||||||
return ConfigurationError("Metadata.Retry.Max must be >= 0")
|
|
||||||
case c.Metadata.Retry.Backoff < 0:
|
|
||||||
return ConfigurationError("Metadata.Retry.Backoff must be >= 0")
|
|
||||||
case c.Metadata.RefreshFrequency < 0:
|
|
||||||
return ConfigurationError("Metadata.RefreshFrequency must be >= 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate the Producer values
|
|
||||||
switch {
|
|
||||||
case c.Producer.MaxMessageBytes <= 0:
|
|
||||||
return ConfigurationError("Producer.MaxMessageBytes must be > 0")
|
|
||||||
case c.Producer.RequiredAcks < -1:
|
|
||||||
return ConfigurationError("Producer.RequiredAcks must be >= -1")
|
|
||||||
case c.Producer.Timeout <= 0:
|
|
||||||
return ConfigurationError("Producer.Timeout must be > 0")
|
|
||||||
case c.Producer.Partitioner == nil:
|
|
||||||
return ConfigurationError("Producer.Partitioner must not be nil")
|
|
||||||
case c.Producer.Flush.Bytes < 0:
|
|
||||||
return ConfigurationError("Producer.Flush.Bytes must be >= 0")
|
|
||||||
case c.Producer.Flush.Messages < 0:
|
|
||||||
return ConfigurationError("Producer.Flush.Messages must be >= 0")
|
|
||||||
case c.Producer.Flush.Frequency < 0:
|
|
||||||
return ConfigurationError("Producer.Flush.Frequency must be >= 0")
|
|
||||||
case c.Producer.Flush.MaxMessages < 0:
|
|
||||||
return ConfigurationError("Producer.Flush.MaxMessages must be >= 0")
|
|
||||||
case c.Producer.Flush.MaxMessages > 0 && c.Producer.Flush.MaxMessages < c.Producer.Flush.Messages:
|
|
||||||
return ConfigurationError("Producer.Flush.MaxMessages must be >= Producer.Flush.Messages when set")
|
|
||||||
case c.Producer.Retry.Max < 0:
|
|
||||||
return ConfigurationError("Producer.Retry.Max must be >= 0")
|
|
||||||
case c.Producer.Retry.Backoff < 0:
|
|
||||||
return ConfigurationError("Producer.Retry.Backoff must be >= 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Producer.Compression == CompressionLZ4 && !c.Version.IsAtLeast(V0_10_0_0) {
|
|
||||||
return ConfigurationError("lz4 compression requires Version >= V0_10_0_0")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Producer.Compression == CompressionGZIP {
|
|
||||||
if c.Producer.CompressionLevel != CompressionLevelDefault {
|
|
||||||
if _, err := gzip.NewWriterLevel(ioutil.Discard, c.Producer.CompressionLevel); err != nil {
|
|
||||||
return ConfigurationError(fmt.Sprintf("gzip compression does not work with level %d: %v", c.Producer.CompressionLevel, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Producer.Compression == CompressionZSTD && !c.Version.IsAtLeast(V2_1_0_0) {
|
|
||||||
return ConfigurationError("zstd compression requires Version >= V2_1_0_0")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Producer.Idempotent {
|
|
||||||
if !c.Version.IsAtLeast(V0_11_0_0) {
|
|
||||||
return ConfigurationError("Idempotent producer requires Version >= V0_11_0_0")
|
|
||||||
}
|
|
||||||
if c.Producer.Retry.Max == 0 {
|
|
||||||
return ConfigurationError("Idempotent producer requires Producer.Retry.Max >= 1")
|
|
||||||
}
|
|
||||||
if c.Producer.RequiredAcks != WaitForAll {
|
|
||||||
return ConfigurationError("Idempotent producer requires Producer.RequiredAcks to be WaitForAll")
|
|
||||||
}
|
|
||||||
if c.Net.MaxOpenRequests > 1 {
|
|
||||||
return ConfigurationError("Idempotent producer requires Net.MaxOpenRequests to be 1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate the Consumer values
|
|
||||||
switch {
|
|
||||||
case c.Consumer.Fetch.Min <= 0:
|
|
||||||
return ConfigurationError("Consumer.Fetch.Min must be > 0")
|
|
||||||
case c.Consumer.Fetch.Default <= 0:
|
|
||||||
return ConfigurationError("Consumer.Fetch.Default must be > 0")
|
|
||||||
case c.Consumer.Fetch.Max < 0:
|
|
||||||
return ConfigurationError("Consumer.Fetch.Max must be >= 0")
|
|
||||||
case c.Consumer.MaxWaitTime < 1*time.Millisecond:
|
|
||||||
return ConfigurationError("Consumer.MaxWaitTime must be >= 1ms")
|
|
||||||
case c.Consumer.MaxProcessingTime <= 0:
|
|
||||||
return ConfigurationError("Consumer.MaxProcessingTime must be > 0")
|
|
||||||
case c.Consumer.Retry.Backoff < 0:
|
|
||||||
return ConfigurationError("Consumer.Retry.Backoff must be >= 0")
|
|
||||||
case c.Consumer.Offsets.AutoCommit.Interval <= 0:
|
|
||||||
return ConfigurationError("Consumer.Offsets.AutoCommit.Interval must be > 0")
|
|
||||||
case c.Consumer.Offsets.Initial != OffsetOldest && c.Consumer.Offsets.Initial != OffsetNewest:
|
|
||||||
return ConfigurationError("Consumer.Offsets.Initial must be OffsetOldest or OffsetNewest")
|
|
||||||
case c.Consumer.Offsets.Retry.Max < 0:
|
|
||||||
return ConfigurationError("Consumer.Offsets.Retry.Max must be >= 0")
|
|
||||||
case c.Consumer.IsolationLevel != ReadUncommitted && c.Consumer.IsolationLevel != ReadCommitted:
|
|
||||||
return ConfigurationError("Consumer.IsolationLevel must be ReadUncommitted or ReadCommitted")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Consumer.Offsets.CommitInterval != 0 {
|
|
||||||
Logger.Println("Deprecation warning: Consumer.Offsets.CommitInterval exists for historical compatibility" +
|
|
||||||
" and should not be used. Please use Consumer.Offsets.AutoCommit, the current value will be ignored")
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate IsolationLevel
|
|
||||||
if c.Consumer.IsolationLevel == ReadCommitted && !c.Version.IsAtLeast(V0_11_0_0) {
|
|
||||||
return ConfigurationError("ReadCommitted requires Version >= V0_11_0_0")
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate the Consumer Group values
|
|
||||||
switch {
|
|
||||||
case c.Consumer.Group.Session.Timeout <= 2*time.Millisecond:
|
|
||||||
return ConfigurationError("Consumer.Group.Session.Timeout must be >= 2ms")
|
|
||||||
case c.Consumer.Group.Heartbeat.Interval < 1*time.Millisecond:
|
|
||||||
return ConfigurationError("Consumer.Group.Heartbeat.Interval must be >= 1ms")
|
|
||||||
case c.Consumer.Group.Heartbeat.Interval >= c.Consumer.Group.Session.Timeout:
|
|
||||||
return ConfigurationError("Consumer.Group.Heartbeat.Interval must be < Consumer.Group.Session.Timeout")
|
|
||||||
case c.Consumer.Group.Rebalance.Strategy == nil:
|
|
||||||
return ConfigurationError("Consumer.Group.Rebalance.Strategy must not be empty")
|
|
||||||
case c.Consumer.Group.Rebalance.Timeout <= time.Millisecond:
|
|
||||||
return ConfigurationError("Consumer.Group.Rebalance.Timeout must be >= 1ms")
|
|
||||||
case c.Consumer.Group.Rebalance.Retry.Max < 0:
|
|
||||||
return ConfigurationError("Consumer.Group.Rebalance.Retry.Max must be >= 0")
|
|
||||||
case c.Consumer.Group.Rebalance.Retry.Backoff < 0:
|
|
||||||
return ConfigurationError("Consumer.Group.Rebalance.Retry.Backoff must be >= 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate misc shared values
|
|
||||||
switch {
|
|
||||||
case c.ChannelBufferSize < 0:
|
|
||||||
return ConfigurationError("ChannelBufferSize must be >= 0")
|
|
||||||
case !validID.MatchString(c.ClientID):
|
|
||||||
return ConfigurationError("ClientID is invalid")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) getDialer() proxy.Dialer {
|
|
||||||
if c.Net.Proxy.Enable {
|
|
||||||
Logger.Printf("using proxy %s", c.Net.Proxy.Dialer)
|
|
||||||
return c.Net.Proxy.Dialer
|
|
||||||
} else {
|
|
||||||
return &net.Dialer{
|
|
||||||
Timeout: c.Net.DialTimeout,
|
|
||||||
KeepAlive: c.Net.KeepAlive,
|
|
||||||
LocalAddr: c.Net.LocalAddr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-18
@@ -1,18 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
// ConfigResourceType is a type for resources that have configs.
|
|
||||||
type ConfigResourceType int8
|
|
||||||
|
|
||||||
// Taken from:
|
|
||||||
// https://github.com/apache/kafka/blob/ed7c071e07f1f90e4c2895582f61ca090ced3c42/clients/src/main/java/org/apache/kafka/common/config/ConfigResource.java#L32-L55
|
|
||||||
|
|
||||||
const (
|
|
||||||
// UnknownResource constant type
|
|
||||||
UnknownResource ConfigResourceType = 0
|
|
||||||
// TopicResource constant type
|
|
||||||
TopicResource ConfigResourceType = 2
|
|
||||||
// BrokerResource constant type
|
|
||||||
BrokerResource ConfigResourceType = 4
|
|
||||||
// BrokerLoggerResource constant type
|
|
||||||
BrokerLoggerResource ConfigResourceType = 8
|
|
||||||
)
|
|
||||||
-911
@@ -1,911 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/rcrowley/go-metrics"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ConsumerMessage encapsulates a Kafka message returned by the consumer.
|
|
||||||
type ConsumerMessage struct {
|
|
||||||
Headers []*RecordHeader // only set if kafka is version 0.11+
|
|
||||||
Timestamp time.Time // only set if kafka is version 0.10+, inner message timestamp
|
|
||||||
BlockTimestamp time.Time // only set if kafka is version 0.10+, outer (compressed) block timestamp
|
|
||||||
|
|
||||||
Key, Value []byte
|
|
||||||
Topic string
|
|
||||||
Partition int32
|
|
||||||
Offset int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConsumerError is what is provided to the user when an error occurs.
|
|
||||||
// It wraps an error and includes the topic and partition.
|
|
||||||
type ConsumerError struct {
|
|
||||||
Topic string
|
|
||||||
Partition int32
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ce ConsumerError) Error() string {
|
|
||||||
return fmt.Sprintf("kafka: error while consuming %s/%d: %s", ce.Topic, ce.Partition, ce.Err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConsumerErrors is a type that wraps a batch of errors and implements the Error interface.
|
|
||||||
// It can be returned from the PartitionConsumer's Close methods to avoid the need to manually drain errors
|
|
||||||
// when stopping.
|
|
||||||
type ConsumerErrors []*ConsumerError
|
|
||||||
|
|
||||||
func (ce ConsumerErrors) Error() string {
|
|
||||||
return fmt.Sprintf("kafka: %d errors while consuming", len(ce))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consumer manages PartitionConsumers which process Kafka messages from brokers. You MUST call Close()
|
|
||||||
// on a consumer to avoid leaks, it will not be garbage-collected automatically when it passes out of
|
|
||||||
// scope.
|
|
||||||
type Consumer interface {
|
|
||||||
// Topics returns the set of available topics as retrieved from the cluster
|
|
||||||
// metadata. This method is the same as Client.Topics(), and is provided for
|
|
||||||
// convenience.
|
|
||||||
Topics() ([]string, error)
|
|
||||||
|
|
||||||
// Partitions returns the sorted list of all partition IDs for the given topic.
|
|
||||||
// This method is the same as Client.Partitions(), and is provided for convenience.
|
|
||||||
Partitions(topic string) ([]int32, error)
|
|
||||||
|
|
||||||
// ConsumePartition creates a PartitionConsumer on the given topic/partition with
|
|
||||||
// the given offset. It will return an error if this Consumer is already consuming
|
|
||||||
// on the given topic/partition. Offset can be a literal offset, or OffsetNewest
|
|
||||||
// or OffsetOldest
|
|
||||||
ConsumePartition(topic string, partition int32, offset int64) (PartitionConsumer, error)
|
|
||||||
|
|
||||||
// HighWaterMarks returns the current high water marks for each topic and partition.
|
|
||||||
// Consistency between partitions is not guaranteed since high water marks are updated separately.
|
|
||||||
HighWaterMarks() map[string]map[int32]int64
|
|
||||||
|
|
||||||
// Close shuts down the consumer. It must be called after all child
|
|
||||||
// PartitionConsumers have already been closed.
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type consumer struct {
|
|
||||||
conf *Config
|
|
||||||
children map[string]map[int32]*partitionConsumer
|
|
||||||
brokerConsumers map[*Broker]*brokerConsumer
|
|
||||||
client Client
|
|
||||||
lock sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConsumer creates a new consumer using the given broker addresses and configuration.
|
|
||||||
func NewConsumer(addrs []string, config *Config) (Consumer, error) {
|
|
||||||
client, err := NewClient(addrs, config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return newConsumer(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConsumerFromClient creates a new consumer using the given client. It is still
|
|
||||||
// necessary to call Close() on the underlying client when shutting down this consumer.
|
|
||||||
func NewConsumerFromClient(client Client) (Consumer, error) {
|
|
||||||
// For clients passed in by the client, ensure we don't
|
|
||||||
// call Close() on it.
|
|
||||||
cli := &nopCloserClient{client}
|
|
||||||
return newConsumer(cli)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConsumer(client Client) (Consumer, error) {
|
|
||||||
// Check that we are not dealing with a closed Client before processing any other arguments
|
|
||||||
if client.Closed() {
|
|
||||||
return nil, ErrClosedClient
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &consumer{
|
|
||||||
client: client,
|
|
||||||
conf: client.Config(),
|
|
||||||
children: make(map[string]map[int32]*partitionConsumer),
|
|
||||||
brokerConsumers: make(map[*Broker]*brokerConsumer),
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumer) Close() error {
|
|
||||||
return c.client.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumer) Topics() ([]string, error) {
|
|
||||||
return c.client.Topics()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumer) Partitions(topic string) ([]int32, error) {
|
|
||||||
return c.client.Partitions(topic)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumer) ConsumePartition(topic string, partition int32, offset int64) (PartitionConsumer, error) {
|
|
||||||
child := &partitionConsumer{
|
|
||||||
consumer: c,
|
|
||||||
conf: c.conf,
|
|
||||||
topic: topic,
|
|
||||||
partition: partition,
|
|
||||||
messages: make(chan *ConsumerMessage, c.conf.ChannelBufferSize),
|
|
||||||
errors: make(chan *ConsumerError, c.conf.ChannelBufferSize),
|
|
||||||
feeder: make(chan *FetchResponse, 1),
|
|
||||||
trigger: make(chan none, 1),
|
|
||||||
dying: make(chan none),
|
|
||||||
fetchSize: c.conf.Consumer.Fetch.Default,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := child.chooseStartingOffset(offset); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var leader *Broker
|
|
||||||
var err error
|
|
||||||
if leader, err = c.client.Leader(child.topic, child.partition); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.addChild(child); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
go withRecover(child.dispatcher)
|
|
||||||
go withRecover(child.responseFeeder)
|
|
||||||
|
|
||||||
child.broker = c.refBrokerConsumer(leader)
|
|
||||||
child.broker.input <- child
|
|
||||||
|
|
||||||
return child, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumer) HighWaterMarks() map[string]map[int32]int64 {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
|
|
||||||
hwms := make(map[string]map[int32]int64)
|
|
||||||
for topic, p := range c.children {
|
|
||||||
hwm := make(map[int32]int64, len(p))
|
|
||||||
for partition, pc := range p {
|
|
||||||
hwm[partition] = pc.HighWaterMarkOffset()
|
|
||||||
}
|
|
||||||
hwms[topic] = hwm
|
|
||||||
}
|
|
||||||
|
|
||||||
return hwms
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumer) addChild(child *partitionConsumer) error {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
|
|
||||||
topicChildren := c.children[child.topic]
|
|
||||||
if topicChildren == nil {
|
|
||||||
topicChildren = make(map[int32]*partitionConsumer)
|
|
||||||
c.children[child.topic] = topicChildren
|
|
||||||
}
|
|
||||||
|
|
||||||
if topicChildren[child.partition] != nil {
|
|
||||||
return ConfigurationError("That topic/partition is already being consumed")
|
|
||||||
}
|
|
||||||
|
|
||||||
topicChildren[child.partition] = child
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumer) removeChild(child *partitionConsumer) {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
|
|
||||||
delete(c.children[child.topic], child.partition)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumer) refBrokerConsumer(broker *Broker) *brokerConsumer {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
|
|
||||||
bc := c.brokerConsumers[broker]
|
|
||||||
if bc == nil {
|
|
||||||
bc = c.newBrokerConsumer(broker)
|
|
||||||
c.brokerConsumers[broker] = bc
|
|
||||||
}
|
|
||||||
|
|
||||||
bc.refs++
|
|
||||||
|
|
||||||
return bc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumer) unrefBrokerConsumer(brokerWorker *brokerConsumer) {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
|
|
||||||
brokerWorker.refs--
|
|
||||||
|
|
||||||
if brokerWorker.refs == 0 {
|
|
||||||
close(brokerWorker.input)
|
|
||||||
if c.brokerConsumers[brokerWorker.broker] == brokerWorker {
|
|
||||||
delete(c.brokerConsumers, brokerWorker.broker)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumer) abandonBrokerConsumer(brokerWorker *brokerConsumer) {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
|
|
||||||
delete(c.brokerConsumers, brokerWorker.broker)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PartitionConsumer
|
|
||||||
|
|
||||||
// PartitionConsumer processes Kafka messages from a given topic and partition. You MUST call one of Close() or
|
|
||||||
// AsyncClose() on a PartitionConsumer to avoid leaks; it will not be garbage-collected automatically when it passes out
|
|
||||||
// of scope.
|
|
||||||
//
|
|
||||||
// The simplest way of using a PartitionConsumer is to loop over its Messages channel using a for/range
|
|
||||||
// loop. The PartitionConsumer will only stop itself in one case: when the offset being consumed is reported
|
|
||||||
// as out of range by the brokers. In this case you should decide what you want to do (try a different offset,
|
|
||||||
// notify a human, etc) and handle it appropriately. For all other error cases, it will just keep retrying.
|
|
||||||
// By default, it logs these errors to sarama.Logger; if you want to be notified directly of all errors, set
|
|
||||||
// your config's Consumer.Return.Errors to true and read from the Errors channel, using a select statement
|
|
||||||
// or a separate goroutine. Check out the Consumer examples to see implementations of these different approaches.
|
|
||||||
//
|
|
||||||
// To terminate such a for/range loop while the loop is executing, call AsyncClose. This will kick off the process of
|
|
||||||
// consumer tear-down & return immediately. Continue to loop, servicing the Messages channel until the teardown process
|
|
||||||
// AsyncClose initiated closes it (thus terminating the for/range loop). If you've already ceased reading Messages, call
|
|
||||||
// Close; this will signal the PartitionConsumer's goroutines to begin shutting down (just like AsyncClose), but will
|
|
||||||
// also drain the Messages channel, harvest all errors & return them once cleanup has completed.
|
|
||||||
type PartitionConsumer interface {
|
|
||||||
// AsyncClose initiates a shutdown of the PartitionConsumer. This method will return immediately, after which you
|
|
||||||
// should continue to service the 'Messages' and 'Errors' channels until they are empty. It is required to call this
|
|
||||||
// function, or Close before a consumer object passes out of scope, as it will otherwise leak memory. You must call
|
|
||||||
// this before calling Close on the underlying client.
|
|
||||||
AsyncClose()
|
|
||||||
|
|
||||||
// Close stops the PartitionConsumer from fetching messages. It will initiate a shutdown just like AsyncClose, drain
|
|
||||||
// the Messages channel, harvest any errors & return them to the caller. Note that if you are continuing to service
|
|
||||||
// the Messages channel when this function is called, you will be competing with Close for messages; consider
|
|
||||||
// calling AsyncClose, instead. It is required to call this function (or AsyncClose) before a consumer object passes
|
|
||||||
// out of scope, as it will otherwise leak memory. You must call this before calling Close on the underlying client.
|
|
||||||
Close() error
|
|
||||||
|
|
||||||
// Messages returns the read channel for the messages that are returned by
|
|
||||||
// the broker.
|
|
||||||
Messages() <-chan *ConsumerMessage
|
|
||||||
|
|
||||||
// Errors returns a read channel of errors that occurred during consuming, if
|
|
||||||
// enabled. By default, errors are logged and not returned over this channel.
|
|
||||||
// If you want to implement any custom error handling, set your config's
|
|
||||||
// Consumer.Return.Errors setting to true, and read from this channel.
|
|
||||||
Errors() <-chan *ConsumerError
|
|
||||||
|
|
||||||
// HighWaterMarkOffset returns the high water mark offset of the partition,
|
|
||||||
// i.e. the offset that will be used for the next message that will be produced.
|
|
||||||
// You can use this to determine how far behind the processing is.
|
|
||||||
HighWaterMarkOffset() int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type partitionConsumer struct {
|
|
||||||
highWaterMarkOffset int64 // must be at the top of the struct because https://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
|
||||||
|
|
||||||
consumer *consumer
|
|
||||||
conf *Config
|
|
||||||
broker *brokerConsumer
|
|
||||||
messages chan *ConsumerMessage
|
|
||||||
errors chan *ConsumerError
|
|
||||||
feeder chan *FetchResponse
|
|
||||||
|
|
||||||
trigger, dying chan none
|
|
||||||
closeOnce sync.Once
|
|
||||||
topic string
|
|
||||||
partition int32
|
|
||||||
responseResult error
|
|
||||||
fetchSize int32
|
|
||||||
offset int64
|
|
||||||
retries int32
|
|
||||||
}
|
|
||||||
|
|
||||||
var errTimedOut = errors.New("timed out feeding messages to the user") // not user-facing
|
|
||||||
|
|
||||||
func (child *partitionConsumer) sendError(err error) {
|
|
||||||
cErr := &ConsumerError{
|
|
||||||
Topic: child.topic,
|
|
||||||
Partition: child.partition,
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
|
|
||||||
if child.conf.Consumer.Return.Errors {
|
|
||||||
child.errors <- cErr
|
|
||||||
} else {
|
|
||||||
Logger.Println(cErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (child *partitionConsumer) computeBackoff() time.Duration {
|
|
||||||
if child.conf.Consumer.Retry.BackoffFunc != nil {
|
|
||||||
retries := atomic.AddInt32(&child.retries, 1)
|
|
||||||
return child.conf.Consumer.Retry.BackoffFunc(int(retries))
|
|
||||||
}
|
|
||||||
return child.conf.Consumer.Retry.Backoff
|
|
||||||
}
|
|
||||||
|
|
||||||
func (child *partitionConsumer) dispatcher() {
|
|
||||||
for range child.trigger {
|
|
||||||
select {
|
|
||||||
case <-child.dying:
|
|
||||||
close(child.trigger)
|
|
||||||
case <-time.After(child.computeBackoff()):
|
|
||||||
if child.broker != nil {
|
|
||||||
child.consumer.unrefBrokerConsumer(child.broker)
|
|
||||||
child.broker = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Printf("consumer/%s/%d finding new broker\n", child.topic, child.partition)
|
|
||||||
if err := child.dispatch(); err != nil {
|
|
||||||
child.sendError(err)
|
|
||||||
child.trigger <- none{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if child.broker != nil {
|
|
||||||
child.consumer.unrefBrokerConsumer(child.broker)
|
|
||||||
}
|
|
||||||
child.consumer.removeChild(child)
|
|
||||||
close(child.feeder)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (child *partitionConsumer) dispatch() error {
|
|
||||||
if err := child.consumer.client.RefreshMetadata(child.topic); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var leader *Broker
|
|
||||||
var err error
|
|
||||||
if leader, err = child.consumer.client.Leader(child.topic, child.partition); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
child.broker = child.consumer.refBrokerConsumer(leader)
|
|
||||||
|
|
||||||
child.broker.input <- child
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (child *partitionConsumer) chooseStartingOffset(offset int64) error {
|
|
||||||
newestOffset, err := child.consumer.client.GetOffset(child.topic, child.partition, OffsetNewest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
oldestOffset, err := child.consumer.client.GetOffset(child.topic, child.partition, OffsetOldest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case offset == OffsetNewest:
|
|
||||||
child.offset = newestOffset
|
|
||||||
case offset == OffsetOldest:
|
|
||||||
child.offset = oldestOffset
|
|
||||||
case offset >= oldestOffset && offset <= newestOffset:
|
|
||||||
child.offset = offset
|
|
||||||
default:
|
|
||||||
return ErrOffsetOutOfRange
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (child *partitionConsumer) Messages() <-chan *ConsumerMessage {
|
|
||||||
return child.messages
|
|
||||||
}
|
|
||||||
|
|
||||||
func (child *partitionConsumer) Errors() <-chan *ConsumerError {
|
|
||||||
return child.errors
|
|
||||||
}
|
|
||||||
|
|
||||||
func (child *partitionConsumer) AsyncClose() {
|
|
||||||
// this triggers whatever broker owns this child to abandon it and close its trigger channel, which causes
|
|
||||||
// the dispatcher to exit its loop, which removes it from the consumer then closes its 'messages' and
|
|
||||||
// 'errors' channel (alternatively, if the child is already at the dispatcher for some reason, that will
|
|
||||||
// also just close itself)
|
|
||||||
child.closeOnce.Do(func() {
|
|
||||||
close(child.dying)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (child *partitionConsumer) Close() error {
|
|
||||||
child.AsyncClose()
|
|
||||||
|
|
||||||
var errors ConsumerErrors
|
|
||||||
for err := range child.errors {
|
|
||||||
errors = append(errors, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errors) > 0 {
|
|
||||||
return errors
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (child *partitionConsumer) HighWaterMarkOffset() int64 {
|
|
||||||
return atomic.LoadInt64(&child.highWaterMarkOffset)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (child *partitionConsumer) responseFeeder() {
|
|
||||||
var msgs []*ConsumerMessage
|
|
||||||
expiryTicker := time.NewTicker(child.conf.Consumer.MaxProcessingTime)
|
|
||||||
firstAttempt := true
|
|
||||||
|
|
||||||
feederLoop:
|
|
||||||
for response := range child.feeder {
|
|
||||||
msgs, child.responseResult = child.parseResponse(response)
|
|
||||||
|
|
||||||
if child.responseResult == nil {
|
|
||||||
atomic.StoreInt32(&child.retries, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, msg := range msgs {
|
|
||||||
messageSelect:
|
|
||||||
select {
|
|
||||||
case <-child.dying:
|
|
||||||
child.broker.acks.Done()
|
|
||||||
continue feederLoop
|
|
||||||
case child.messages <- msg:
|
|
||||||
firstAttempt = true
|
|
||||||
case <-expiryTicker.C:
|
|
||||||
if !firstAttempt {
|
|
||||||
child.responseResult = errTimedOut
|
|
||||||
child.broker.acks.Done()
|
|
||||||
remainingLoop:
|
|
||||||
for _, msg = range msgs[i:] {
|
|
||||||
select {
|
|
||||||
case child.messages <- msg:
|
|
||||||
case <-child.dying:
|
|
||||||
break remainingLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
child.broker.input <- child
|
|
||||||
continue feederLoop
|
|
||||||
} else {
|
|
||||||
// current message has not been sent, return to select
|
|
||||||
// statement
|
|
||||||
firstAttempt = false
|
|
||||||
goto messageSelect
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
child.broker.acks.Done()
|
|
||||||
}
|
|
||||||
|
|
||||||
expiryTicker.Stop()
|
|
||||||
close(child.messages)
|
|
||||||
close(child.errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (child *partitionConsumer) parseMessages(msgSet *MessageSet) ([]*ConsumerMessage, error) {
|
|
||||||
var messages []*ConsumerMessage
|
|
||||||
for _, msgBlock := range msgSet.Messages {
|
|
||||||
for _, msg := range msgBlock.Messages() {
|
|
||||||
offset := msg.Offset
|
|
||||||
timestamp := msg.Msg.Timestamp
|
|
||||||
if msg.Msg.Version >= 1 {
|
|
||||||
baseOffset := msgBlock.Offset - msgBlock.Messages()[len(msgBlock.Messages())-1].Offset
|
|
||||||
offset += baseOffset
|
|
||||||
if msg.Msg.LogAppendTime {
|
|
||||||
timestamp = msgBlock.Msg.Timestamp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if offset < child.offset {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
messages = append(messages, &ConsumerMessage{
|
|
||||||
Topic: child.topic,
|
|
||||||
Partition: child.partition,
|
|
||||||
Key: msg.Msg.Key,
|
|
||||||
Value: msg.Msg.Value,
|
|
||||||
Offset: offset,
|
|
||||||
Timestamp: timestamp,
|
|
||||||
BlockTimestamp: msgBlock.Msg.Timestamp,
|
|
||||||
})
|
|
||||||
child.offset = offset + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(messages) == 0 {
|
|
||||||
child.offset++
|
|
||||||
}
|
|
||||||
return messages, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (child *partitionConsumer) parseRecords(batch *RecordBatch) ([]*ConsumerMessage, error) {
|
|
||||||
messages := make([]*ConsumerMessage, 0, len(batch.Records))
|
|
||||||
|
|
||||||
for _, rec := range batch.Records {
|
|
||||||
offset := batch.FirstOffset + rec.OffsetDelta
|
|
||||||
if offset < child.offset {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
timestamp := batch.FirstTimestamp.Add(rec.TimestampDelta)
|
|
||||||
if batch.LogAppendTime {
|
|
||||||
timestamp = batch.MaxTimestamp
|
|
||||||
}
|
|
||||||
messages = append(messages, &ConsumerMessage{
|
|
||||||
Topic: child.topic,
|
|
||||||
Partition: child.partition,
|
|
||||||
Key: rec.Key,
|
|
||||||
Value: rec.Value,
|
|
||||||
Offset: offset,
|
|
||||||
Timestamp: timestamp,
|
|
||||||
Headers: rec.Headers,
|
|
||||||
})
|
|
||||||
child.offset = offset + 1
|
|
||||||
}
|
|
||||||
if len(messages) == 0 {
|
|
||||||
child.offset++
|
|
||||||
}
|
|
||||||
return messages, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (child *partitionConsumer) parseResponse(response *FetchResponse) ([]*ConsumerMessage, error) {
|
|
||||||
var (
|
|
||||||
metricRegistry = child.conf.MetricRegistry
|
|
||||||
consumerBatchSizeMetric metrics.Histogram
|
|
||||||
)
|
|
||||||
|
|
||||||
if metricRegistry != nil {
|
|
||||||
consumerBatchSizeMetric = getOrRegisterHistogram("consumer-batch-size", metricRegistry)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If request was throttled and empty we log and return without error
|
|
||||||
if response.ThrottleTime != time.Duration(0) && len(response.Blocks) == 0 {
|
|
||||||
Logger.Printf(
|
|
||||||
"consumer/broker/%d FetchResponse throttled %v\n",
|
|
||||||
child.broker.broker.ID(), response.ThrottleTime)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
block := response.GetBlock(child.topic, child.partition)
|
|
||||||
if block == nil {
|
|
||||||
return nil, ErrIncompleteResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
if block.Err != ErrNoError {
|
|
||||||
return nil, block.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
nRecs, err := block.numRecords()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
consumerBatchSizeMetric.Update(int64(nRecs))
|
|
||||||
|
|
||||||
if nRecs == 0 {
|
|
||||||
partialTrailingMessage, err := block.isPartial()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// We got no messages. If we got a trailing one then we need to ask for more data.
|
|
||||||
// Otherwise we just poll again and wait for one to be produced...
|
|
||||||
if partialTrailingMessage {
|
|
||||||
if child.conf.Consumer.Fetch.Max > 0 && child.fetchSize == child.conf.Consumer.Fetch.Max {
|
|
||||||
// we can't ask for more data, we've hit the configured limit
|
|
||||||
child.sendError(ErrMessageTooLarge)
|
|
||||||
child.offset++ // skip this one so we can keep processing future messages
|
|
||||||
} else {
|
|
||||||
child.fetchSize *= 2
|
|
||||||
// check int32 overflow
|
|
||||||
if child.fetchSize < 0 {
|
|
||||||
child.fetchSize = math.MaxInt32
|
|
||||||
}
|
|
||||||
if child.conf.Consumer.Fetch.Max > 0 && child.fetchSize > child.conf.Consumer.Fetch.Max {
|
|
||||||
child.fetchSize = child.conf.Consumer.Fetch.Max
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// we got messages, reset our fetch size in case it was increased for a previous request
|
|
||||||
child.fetchSize = child.conf.Consumer.Fetch.Default
|
|
||||||
atomic.StoreInt64(&child.highWaterMarkOffset, block.HighWaterMarkOffset)
|
|
||||||
|
|
||||||
// abortedProducerIDs contains producerID which message should be ignored as uncommitted
|
|
||||||
// - producerID are added when the partitionConsumer iterate over the offset at which an aborted transaction begins (abortedTransaction.FirstOffset)
|
|
||||||
// - producerID are removed when partitionConsumer iterate over an aborted controlRecord, meaning the aborted transaction for this producer is over
|
|
||||||
abortedProducerIDs := make(map[int64]struct{}, len(block.AbortedTransactions))
|
|
||||||
abortedTransactions := block.getAbortedTransactions()
|
|
||||||
|
|
||||||
messages := []*ConsumerMessage{}
|
|
||||||
for _, records := range block.RecordsSet {
|
|
||||||
switch records.recordsType {
|
|
||||||
case legacyRecords:
|
|
||||||
messageSetMessages, err := child.parseMessages(records.MsgSet)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
messages = append(messages, messageSetMessages...)
|
|
||||||
case defaultRecords:
|
|
||||||
// Consume remaining abortedTransaction up to last offset of current batch
|
|
||||||
for _, txn := range abortedTransactions {
|
|
||||||
if txn.FirstOffset > records.RecordBatch.LastOffset() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
abortedProducerIDs[txn.ProducerID] = struct{}{}
|
|
||||||
// Pop abortedTransactions so that we never add it again
|
|
||||||
abortedTransactions = abortedTransactions[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
recordBatchMessages, err := child.parseRecords(records.RecordBatch)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse and commit offset but do not expose messages that are:
|
|
||||||
// - control records
|
|
||||||
// - part of an aborted transaction when set to `ReadCommitted`
|
|
||||||
|
|
||||||
// control record
|
|
||||||
isControl, err := records.isControl()
|
|
||||||
if err != nil {
|
|
||||||
// I don't know why there is this continue in case of error to begin with
|
|
||||||
// Safe bet is to ignore control messages if ReadUncommitted
|
|
||||||
// and block on them in case of error and ReadCommitted
|
|
||||||
if child.conf.Consumer.IsolationLevel == ReadCommitted {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if isControl {
|
|
||||||
controlRecord, err := records.getControlRecord()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if controlRecord.Type == ControlRecordAbort {
|
|
||||||
delete(abortedProducerIDs, records.RecordBatch.ProducerID)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter aborted transactions
|
|
||||||
if child.conf.Consumer.IsolationLevel == ReadCommitted {
|
|
||||||
_, isAborted := abortedProducerIDs[records.RecordBatch.ProducerID]
|
|
||||||
if records.RecordBatch.IsTransactional && isAborted {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
messages = append(messages, recordBatchMessages...)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown records type: %v", records.recordsType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return messages, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type brokerConsumer struct {
|
|
||||||
consumer *consumer
|
|
||||||
broker *Broker
|
|
||||||
input chan *partitionConsumer
|
|
||||||
newSubscriptions chan []*partitionConsumer
|
|
||||||
subscriptions map[*partitionConsumer]none
|
|
||||||
wait chan none
|
|
||||||
acks sync.WaitGroup
|
|
||||||
refs int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumer) newBrokerConsumer(broker *Broker) *brokerConsumer {
|
|
||||||
bc := &brokerConsumer{
|
|
||||||
consumer: c,
|
|
||||||
broker: broker,
|
|
||||||
input: make(chan *partitionConsumer),
|
|
||||||
newSubscriptions: make(chan []*partitionConsumer),
|
|
||||||
wait: make(chan none),
|
|
||||||
subscriptions: make(map[*partitionConsumer]none),
|
|
||||||
refs: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
go withRecover(bc.subscriptionManager)
|
|
||||||
go withRecover(bc.subscriptionConsumer)
|
|
||||||
|
|
||||||
return bc
|
|
||||||
}
|
|
||||||
|
|
||||||
// The subscriptionManager constantly accepts new subscriptions on `input` (even when the main subscriptionConsumer
|
|
||||||
// goroutine is in the middle of a network request) and batches it up. The main worker goroutine picks
|
|
||||||
// up a batch of new subscriptions between every network request by reading from `newSubscriptions`, so we give
|
|
||||||
// it nil if no new subscriptions are available. We also write to `wait` only when new subscriptions is available,
|
|
||||||
// so the main goroutine can block waiting for work if it has none.
|
|
||||||
func (bc *brokerConsumer) subscriptionManager() {
|
|
||||||
var buffer []*partitionConsumer
|
|
||||||
|
|
||||||
for {
|
|
||||||
if len(buffer) > 0 {
|
|
||||||
select {
|
|
||||||
case event, ok := <-bc.input:
|
|
||||||
if !ok {
|
|
||||||
goto done
|
|
||||||
}
|
|
||||||
buffer = append(buffer, event)
|
|
||||||
case bc.newSubscriptions <- buffer:
|
|
||||||
buffer = nil
|
|
||||||
case bc.wait <- none{}:
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
select {
|
|
||||||
case event, ok := <-bc.input:
|
|
||||||
if !ok {
|
|
||||||
goto done
|
|
||||||
}
|
|
||||||
buffer = append(buffer, event)
|
|
||||||
case bc.newSubscriptions <- nil:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
done:
|
|
||||||
close(bc.wait)
|
|
||||||
if len(buffer) > 0 {
|
|
||||||
bc.newSubscriptions <- buffer
|
|
||||||
}
|
|
||||||
close(bc.newSubscriptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
//subscriptionConsumer ensures we will get nil right away if no new subscriptions is available
|
|
||||||
func (bc *brokerConsumer) subscriptionConsumer() {
|
|
||||||
<-bc.wait // wait for our first piece of work
|
|
||||||
|
|
||||||
for newSubscriptions := range bc.newSubscriptions {
|
|
||||||
bc.updateSubscriptions(newSubscriptions)
|
|
||||||
|
|
||||||
if len(bc.subscriptions) == 0 {
|
|
||||||
// We're about to be shut down or we're about to receive more subscriptions.
|
|
||||||
// Either way, the signal just hasn't propagated to our goroutine yet.
|
|
||||||
<-bc.wait
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := bc.fetchNewMessages()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
Logger.Printf("consumer/broker/%d disconnecting due to error processing FetchRequest: %s\n", bc.broker.ID(), err)
|
|
||||||
bc.abort(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bc.acks.Add(len(bc.subscriptions))
|
|
||||||
for child := range bc.subscriptions {
|
|
||||||
child.feeder <- response
|
|
||||||
}
|
|
||||||
bc.acks.Wait()
|
|
||||||
bc.handleResponses()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc *brokerConsumer) updateSubscriptions(newSubscriptions []*partitionConsumer) {
|
|
||||||
for _, child := range newSubscriptions {
|
|
||||||
bc.subscriptions[child] = none{}
|
|
||||||
Logger.Printf("consumer/broker/%d added subscription to %s/%d\n", bc.broker.ID(), child.topic, child.partition)
|
|
||||||
}
|
|
||||||
|
|
||||||
for child := range bc.subscriptions {
|
|
||||||
select {
|
|
||||||
case <-child.dying:
|
|
||||||
Logger.Printf("consumer/broker/%d closed dead subscription to %s/%d\n", bc.broker.ID(), child.topic, child.partition)
|
|
||||||
close(child.trigger)
|
|
||||||
delete(bc.subscriptions, child)
|
|
||||||
default:
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//handleResponses handles the response codes left for us by our subscriptions, and abandons ones that have been closed
|
|
||||||
func (bc *brokerConsumer) handleResponses() {
|
|
||||||
for child := range bc.subscriptions {
|
|
||||||
result := child.responseResult
|
|
||||||
child.responseResult = nil
|
|
||||||
|
|
||||||
switch result {
|
|
||||||
case nil:
|
|
||||||
// no-op
|
|
||||||
case errTimedOut:
|
|
||||||
Logger.Printf("consumer/broker/%d abandoned subscription to %s/%d because consuming was taking too long\n",
|
|
||||||
bc.broker.ID(), child.topic, child.partition)
|
|
||||||
delete(bc.subscriptions, child)
|
|
||||||
case ErrOffsetOutOfRange:
|
|
||||||
// there's no point in retrying this it will just fail the same way again
|
|
||||||
// shut it down and force the user to choose what to do
|
|
||||||
child.sendError(result)
|
|
||||||
Logger.Printf("consumer/%s/%d shutting down because %s\n", child.topic, child.partition, result)
|
|
||||||
close(child.trigger)
|
|
||||||
delete(bc.subscriptions, child)
|
|
||||||
case ErrUnknownTopicOrPartition, ErrNotLeaderForPartition, ErrLeaderNotAvailable, ErrReplicaNotAvailable:
|
|
||||||
// not an error, but does need redispatching
|
|
||||||
Logger.Printf("consumer/broker/%d abandoned subscription to %s/%d because %s\n",
|
|
||||||
bc.broker.ID(), child.topic, child.partition, result)
|
|
||||||
child.trigger <- none{}
|
|
||||||
delete(bc.subscriptions, child)
|
|
||||||
default:
|
|
||||||
// dunno, tell the user and try redispatching
|
|
||||||
child.sendError(result)
|
|
||||||
Logger.Printf("consumer/broker/%d abandoned subscription to %s/%d because %s\n",
|
|
||||||
bc.broker.ID(), child.topic, child.partition, result)
|
|
||||||
child.trigger <- none{}
|
|
||||||
delete(bc.subscriptions, child)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc *brokerConsumer) abort(err error) {
|
|
||||||
bc.consumer.abandonBrokerConsumer(bc)
|
|
||||||
_ = bc.broker.Close() // we don't care about the error this might return, we already have one
|
|
||||||
|
|
||||||
for child := range bc.subscriptions {
|
|
||||||
child.sendError(err)
|
|
||||||
child.trigger <- none{}
|
|
||||||
}
|
|
||||||
|
|
||||||
for newSubscriptions := range bc.newSubscriptions {
|
|
||||||
if len(newSubscriptions) == 0 {
|
|
||||||
<-bc.wait
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, child := range newSubscriptions {
|
|
||||||
child.sendError(err)
|
|
||||||
child.trigger <- none{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc *brokerConsumer) fetchNewMessages() (*FetchResponse, error) {
|
|
||||||
request := &FetchRequest{
|
|
||||||
MinBytes: bc.consumer.conf.Consumer.Fetch.Min,
|
|
||||||
MaxWaitTime: int32(bc.consumer.conf.Consumer.MaxWaitTime / time.Millisecond),
|
|
||||||
}
|
|
||||||
if bc.consumer.conf.Version.IsAtLeast(V0_9_0_0) {
|
|
||||||
request.Version = 1
|
|
||||||
}
|
|
||||||
if bc.consumer.conf.Version.IsAtLeast(V0_10_0_0) {
|
|
||||||
request.Version = 2
|
|
||||||
}
|
|
||||||
if bc.consumer.conf.Version.IsAtLeast(V0_10_1_0) {
|
|
||||||
request.Version = 3
|
|
||||||
request.MaxBytes = MaxResponseSize
|
|
||||||
}
|
|
||||||
if bc.consumer.conf.Version.IsAtLeast(V0_11_0_0) {
|
|
||||||
request.Version = 4
|
|
||||||
request.Isolation = bc.consumer.conf.Consumer.IsolationLevel
|
|
||||||
}
|
|
||||||
if bc.consumer.conf.Version.IsAtLeast(V1_1_0_0) {
|
|
||||||
request.Version = 7
|
|
||||||
// We do not currently implement KIP-227 FetchSessions. Setting the id to 0
|
|
||||||
// and the epoch to -1 tells the broker not to generate as session ID we're going
|
|
||||||
// to just ignore anyway.
|
|
||||||
request.SessionID = 0
|
|
||||||
request.SessionEpoch = -1
|
|
||||||
}
|
|
||||||
if bc.consumer.conf.Version.IsAtLeast(V2_1_0_0) {
|
|
||||||
request.Version = 10
|
|
||||||
}
|
|
||||||
if bc.consumer.conf.Version.IsAtLeast(V2_3_0_0) {
|
|
||||||
request.Version = 11
|
|
||||||
request.RackID = bc.consumer.conf.RackID
|
|
||||||
}
|
|
||||||
|
|
||||||
for child := range bc.subscriptions {
|
|
||||||
request.AddBlock(child.topic, child.partition, child.offset, child.fetchSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
return bc.broker.Fetch(request)
|
|
||||||
}
|
|
||||||
-867
@@ -1,867 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrClosedConsumerGroup is the error returned when a method is called on a consumer group that has been closed.
|
|
||||||
var ErrClosedConsumerGroup = errors.New("kafka: tried to use a consumer group that was closed")
|
|
||||||
|
|
||||||
// ConsumerGroup is responsible for dividing up processing of topics and partitions
|
|
||||||
// over a collection of processes (the members of the consumer group).
|
|
||||||
type ConsumerGroup interface {
|
|
||||||
// Consume joins a cluster of consumers for a given list of topics and
|
|
||||||
// starts a blocking ConsumerGroupSession through the ConsumerGroupHandler.
|
|
||||||
//
|
|
||||||
// The life-cycle of a session is represented by the following steps:
|
|
||||||
//
|
|
||||||
// 1. The consumers join the group (as explained in https://kafka.apache.org/documentation/#intro_consumers)
|
|
||||||
// and is assigned their "fair share" of partitions, aka 'claims'.
|
|
||||||
// 2. Before processing starts, the handler's Setup() hook is called to notify the user
|
|
||||||
// of the claims and allow any necessary preparation or alteration of state.
|
|
||||||
// 3. For each of the assigned claims the handler's ConsumeClaim() function is then called
|
|
||||||
// in a separate goroutine which requires it to be thread-safe. Any state must be carefully protected
|
|
||||||
// from concurrent reads/writes.
|
|
||||||
// 4. The session will persist until one of the ConsumeClaim() functions exits. This can be either when the
|
|
||||||
// parent context is cancelled or when a server-side rebalance cycle is initiated.
|
|
||||||
// 5. Once all the ConsumeClaim() loops have exited, the handler's Cleanup() hook is called
|
|
||||||
// to allow the user to perform any final tasks before a rebalance.
|
|
||||||
// 6. Finally, marked offsets are committed one last time before claims are released.
|
|
||||||
//
|
|
||||||
// Please note, that once a rebalance is triggered, sessions must be completed within
|
|
||||||
// Config.Consumer.Group.Rebalance.Timeout. This means that ConsumeClaim() functions must exit
|
|
||||||
// as quickly as possible to allow time for Cleanup() and the final offset commit. If the timeout
|
|
||||||
// is exceeded, the consumer will be removed from the group by Kafka, which will cause offset
|
|
||||||
// commit failures.
|
|
||||||
// This method should be called inside an infinite loop, when a
|
|
||||||
// server-side rebalance happens, the consumer session will need to be
|
|
||||||
// recreated to get the new claims.
|
|
||||||
Consume(ctx context.Context, topics []string, handler ConsumerGroupHandler) error
|
|
||||||
|
|
||||||
// Errors returns a read channel of errors that occurred during the consumer life-cycle.
|
|
||||||
// By default, errors are logged and not returned over this channel.
|
|
||||||
// If you want to implement any custom error handling, set your config's
|
|
||||||
// Consumer.Return.Errors setting to true, and read from this channel.
|
|
||||||
Errors() <-chan error
|
|
||||||
|
|
||||||
// Close stops the ConsumerGroup and detaches any running sessions. It is required to call
|
|
||||||
// this function before the object passes out of scope, as it will otherwise leak memory.
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type consumerGroup struct {
|
|
||||||
client Client
|
|
||||||
|
|
||||||
config *Config
|
|
||||||
consumer Consumer
|
|
||||||
groupID string
|
|
||||||
memberID string
|
|
||||||
errors chan error
|
|
||||||
|
|
||||||
lock sync.Mutex
|
|
||||||
closed chan none
|
|
||||||
closeOnce sync.Once
|
|
||||||
|
|
||||||
userData []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConsumerGroup creates a new consumer group the given broker addresses and configuration.
|
|
||||||
func NewConsumerGroup(addrs []string, groupID string, config *Config) (ConsumerGroup, error) {
|
|
||||||
client, err := NewClient(addrs, config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := newConsumerGroup(groupID, client)
|
|
||||||
if err != nil {
|
|
||||||
_ = client.Close()
|
|
||||||
}
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConsumerGroupFromClient creates a new consumer group using the given client. It is still
|
|
||||||
// necessary to call Close() on the underlying client when shutting down this consumer.
|
|
||||||
// PLEASE NOTE: consumer groups can only re-use but not share clients.
|
|
||||||
func NewConsumerGroupFromClient(groupID string, client Client) (ConsumerGroup, error) {
|
|
||||||
// For clients passed in by the client, ensure we don't
|
|
||||||
// call Close() on it.
|
|
||||||
cli := &nopCloserClient{client}
|
|
||||||
return newConsumerGroup(groupID, cli)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConsumerGroup(groupID string, client Client) (ConsumerGroup, error) {
|
|
||||||
config := client.Config()
|
|
||||||
if !config.Version.IsAtLeast(V0_10_2_0) {
|
|
||||||
return nil, ConfigurationError("consumer groups require Version to be >= V0_10_2_0")
|
|
||||||
}
|
|
||||||
|
|
||||||
consumer, err := NewConsumerFromClient(client)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &consumerGroup{
|
|
||||||
client: client,
|
|
||||||
consumer: consumer,
|
|
||||||
config: config,
|
|
||||||
groupID: groupID,
|
|
||||||
errors: make(chan error, config.ChannelBufferSize),
|
|
||||||
closed: make(chan none),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errors implements ConsumerGroup.
|
|
||||||
func (c *consumerGroup) Errors() <-chan error { return c.errors }
|
|
||||||
|
|
||||||
// Close implements ConsumerGroup.
|
|
||||||
func (c *consumerGroup) Close() (err error) {
|
|
||||||
c.closeOnce.Do(func() {
|
|
||||||
close(c.closed)
|
|
||||||
|
|
||||||
// leave group
|
|
||||||
if e := c.leave(); e != nil {
|
|
||||||
err = e
|
|
||||||
}
|
|
||||||
|
|
||||||
// drain errors
|
|
||||||
go func() {
|
|
||||||
close(c.errors)
|
|
||||||
}()
|
|
||||||
for e := range c.errors {
|
|
||||||
err = e
|
|
||||||
}
|
|
||||||
|
|
||||||
if e := c.client.Close(); e != nil {
|
|
||||||
err = e
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consume implements ConsumerGroup.
|
|
||||||
func (c *consumerGroup) Consume(ctx context.Context, topics []string, handler ConsumerGroupHandler) error {
|
|
||||||
// Ensure group is not closed
|
|
||||||
select {
|
|
||||||
case <-c.closed:
|
|
||||||
return ErrClosedConsumerGroup
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
|
|
||||||
// Quick exit when no topics are provided
|
|
||||||
if len(topics) == 0 {
|
|
||||||
return fmt.Errorf("no topics provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh metadata for requested topics
|
|
||||||
if err := c.client.RefreshMetadata(topics...); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init session
|
|
||||||
sess, err := c.newSession(ctx, topics, handler, c.config.Consumer.Group.Rebalance.Retry.Max)
|
|
||||||
if err == ErrClosedClient {
|
|
||||||
return ErrClosedConsumerGroup
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop check topic partition numbers changed
|
|
||||||
// will trigger rebalance when any topic partitions number had changed
|
|
||||||
// avoid Consume function called again that will generate more than loopCheckPartitionNumbers coroutine
|
|
||||||
go c.loopCheckPartitionNumbers(topics, sess)
|
|
||||||
|
|
||||||
// Wait for session exit signal
|
|
||||||
<-sess.ctx.Done()
|
|
||||||
|
|
||||||
// Gracefully release session claims
|
|
||||||
return sess.release(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumerGroup) retryNewSession(ctx context.Context, topics []string, handler ConsumerGroupHandler, retries int, refreshCoordinator bool) (*consumerGroupSession, error) {
|
|
||||||
select {
|
|
||||||
case <-c.closed:
|
|
||||||
return nil, ErrClosedConsumerGroup
|
|
||||||
case <-time.After(c.config.Consumer.Group.Rebalance.Retry.Backoff):
|
|
||||||
}
|
|
||||||
|
|
||||||
if refreshCoordinator {
|
|
||||||
err := c.client.RefreshCoordinator(c.groupID)
|
|
||||||
if err != nil {
|
|
||||||
return c.retryNewSession(ctx, topics, handler, retries, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.newSession(ctx, topics, handler, retries-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumerGroup) newSession(ctx context.Context, topics []string, handler ConsumerGroupHandler, retries int) (*consumerGroupSession, error) {
|
|
||||||
coordinator, err := c.client.Coordinator(c.groupID)
|
|
||||||
if err != nil {
|
|
||||||
if retries <= 0 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.retryNewSession(ctx, topics, handler, retries, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Join consumer group
|
|
||||||
join, err := c.joinGroupRequest(coordinator, topics)
|
|
||||||
if err != nil {
|
|
||||||
_ = coordinator.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch join.Err {
|
|
||||||
case ErrNoError:
|
|
||||||
c.memberID = join.MemberId
|
|
||||||
case ErrUnknownMemberId, ErrIllegalGeneration: // reset member ID and retry immediately
|
|
||||||
c.memberID = ""
|
|
||||||
return c.newSession(ctx, topics, handler, retries)
|
|
||||||
case ErrNotCoordinatorForConsumer: // retry after backoff with coordinator refresh
|
|
||||||
if retries <= 0 {
|
|
||||||
return nil, join.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.retryNewSession(ctx, topics, handler, retries, true)
|
|
||||||
case ErrRebalanceInProgress: // retry after backoff
|
|
||||||
if retries <= 0 {
|
|
||||||
return nil, join.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.retryNewSession(ctx, topics, handler, retries, false)
|
|
||||||
default:
|
|
||||||
return nil, join.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare distribution plan if we joined as the leader
|
|
||||||
var plan BalanceStrategyPlan
|
|
||||||
if join.LeaderId == join.MemberId {
|
|
||||||
members, err := join.GetMembers()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
plan, err = c.balance(members)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sync consumer group
|
|
||||||
sync, err := c.syncGroupRequest(coordinator, plan, join.GenerationId)
|
|
||||||
if err != nil {
|
|
||||||
_ = coordinator.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch sync.Err {
|
|
||||||
case ErrNoError:
|
|
||||||
case ErrUnknownMemberId, ErrIllegalGeneration: // reset member ID and retry immediately
|
|
||||||
c.memberID = ""
|
|
||||||
return c.newSession(ctx, topics, handler, retries)
|
|
||||||
case ErrNotCoordinatorForConsumer: // retry after backoff with coordinator refresh
|
|
||||||
if retries <= 0 {
|
|
||||||
return nil, sync.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.retryNewSession(ctx, topics, handler, retries, true)
|
|
||||||
case ErrRebalanceInProgress: // retry after backoff
|
|
||||||
if retries <= 0 {
|
|
||||||
return nil, sync.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.retryNewSession(ctx, topics, handler, retries, false)
|
|
||||||
default:
|
|
||||||
return nil, sync.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve and sort claims
|
|
||||||
var claims map[string][]int32
|
|
||||||
if len(sync.MemberAssignment) > 0 {
|
|
||||||
members, err := sync.GetMemberAssignment()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
claims = members.Topics
|
|
||||||
c.userData = members.UserData
|
|
||||||
|
|
||||||
for _, partitions := range claims {
|
|
||||||
sort.Sort(int32Slice(partitions))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newConsumerGroupSession(ctx, c, claims, join.MemberId, join.GenerationId, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumerGroup) joinGroupRequest(coordinator *Broker, topics []string) (*JoinGroupResponse, error) {
|
|
||||||
req := &JoinGroupRequest{
|
|
||||||
GroupId: c.groupID,
|
|
||||||
MemberId: c.memberID,
|
|
||||||
SessionTimeout: int32(c.config.Consumer.Group.Session.Timeout / time.Millisecond),
|
|
||||||
ProtocolType: "consumer",
|
|
||||||
}
|
|
||||||
if c.config.Version.IsAtLeast(V0_10_1_0) {
|
|
||||||
req.Version = 1
|
|
||||||
req.RebalanceTimeout = int32(c.config.Consumer.Group.Rebalance.Timeout / time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
// use static user-data if configured, otherwise use consumer-group userdata from the last sync
|
|
||||||
userData := c.config.Consumer.Group.Member.UserData
|
|
||||||
if len(userData) == 0 {
|
|
||||||
userData = c.userData
|
|
||||||
}
|
|
||||||
meta := &ConsumerGroupMemberMetadata{
|
|
||||||
Topics: topics,
|
|
||||||
UserData: userData,
|
|
||||||
}
|
|
||||||
strategy := c.config.Consumer.Group.Rebalance.Strategy
|
|
||||||
if err := req.AddGroupProtocolMetadata(strategy.Name(), meta); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return coordinator.JoinGroup(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumerGroup) syncGroupRequest(coordinator *Broker, plan BalanceStrategyPlan, generationID int32) (*SyncGroupResponse, error) {
|
|
||||||
req := &SyncGroupRequest{
|
|
||||||
GroupId: c.groupID,
|
|
||||||
MemberId: c.memberID,
|
|
||||||
GenerationId: generationID,
|
|
||||||
}
|
|
||||||
strategy := c.config.Consumer.Group.Rebalance.Strategy
|
|
||||||
for memberID, topics := range plan {
|
|
||||||
assignment := &ConsumerGroupMemberAssignment{Topics: topics}
|
|
||||||
userDataBytes, err := strategy.AssignmentData(memberID, topics, generationID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
assignment.UserData = userDataBytes
|
|
||||||
if err := req.AddGroupAssignmentMember(memberID, assignment); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return coordinator.SyncGroup(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumerGroup) heartbeatRequest(coordinator *Broker, memberID string, generationID int32) (*HeartbeatResponse, error) {
|
|
||||||
req := &HeartbeatRequest{
|
|
||||||
GroupId: c.groupID,
|
|
||||||
MemberId: memberID,
|
|
||||||
GenerationId: generationID,
|
|
||||||
}
|
|
||||||
|
|
||||||
return coordinator.Heartbeat(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumerGroup) balance(members map[string]ConsumerGroupMemberMetadata) (BalanceStrategyPlan, error) {
|
|
||||||
topics := make(map[string][]int32)
|
|
||||||
for _, meta := range members {
|
|
||||||
for _, topic := range meta.Topics {
|
|
||||||
topics[topic] = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for topic := range topics {
|
|
||||||
partitions, err := c.client.Partitions(topic)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
topics[topic] = partitions
|
|
||||||
}
|
|
||||||
|
|
||||||
strategy := c.config.Consumer.Group.Rebalance.Strategy
|
|
||||||
return strategy.Plan(members, topics)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Leaves the cluster, called by Close.
|
|
||||||
func (c *consumerGroup) leave() error {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
if c.memberID == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
coordinator, err := c.client.Coordinator(c.groupID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := coordinator.LeaveGroup(&LeaveGroupRequest{
|
|
||||||
GroupId: c.groupID,
|
|
||||||
MemberId: c.memberID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
_ = coordinator.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unset memberID
|
|
||||||
c.memberID = ""
|
|
||||||
|
|
||||||
// Check response
|
|
||||||
switch resp.Err {
|
|
||||||
case ErrRebalanceInProgress, ErrUnknownMemberId, ErrNoError:
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return resp.Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumerGroup) handleError(err error, topic string, partition int32) {
|
|
||||||
if _, ok := err.(*ConsumerError); !ok && topic != "" && partition > -1 {
|
|
||||||
err = &ConsumerError{
|
|
||||||
Topic: topic,
|
|
||||||
Partition: partition,
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.config.Consumer.Return.Errors {
|
|
||||||
Logger.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-c.closed:
|
|
||||||
//consumer is closed
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case c.errors <- err:
|
|
||||||
default:
|
|
||||||
// no error listener
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumerGroup) loopCheckPartitionNumbers(topics []string, session *consumerGroupSession) {
|
|
||||||
pause := time.NewTicker(c.config.Metadata.RefreshFrequency)
|
|
||||||
defer session.cancel()
|
|
||||||
defer pause.Stop()
|
|
||||||
var oldTopicToPartitionNum map[string]int
|
|
||||||
var err error
|
|
||||||
if oldTopicToPartitionNum, err = c.topicToPartitionNumbers(topics); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
if newTopicToPartitionNum, err := c.topicToPartitionNumbers(topics); err != nil {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
for topic, num := range oldTopicToPartitionNum {
|
|
||||||
if newTopicToPartitionNum[topic] != num {
|
|
||||||
return // trigger the end of the session on exit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-pause.C:
|
|
||||||
case <-session.ctx.Done():
|
|
||||||
Logger.Printf("loop check partition number coroutine will exit, topics %s", topics)
|
|
||||||
// if session closed by other, should be exited
|
|
||||||
return
|
|
||||||
case <-c.closed:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumerGroup) topicToPartitionNumbers(topics []string) (map[string]int, error) {
|
|
||||||
topicToPartitionNum := make(map[string]int, len(topics))
|
|
||||||
for _, topic := range topics {
|
|
||||||
if partitionNum, err := c.client.Partitions(topic); err != nil {
|
|
||||||
Logger.Printf("Consumer Group topic %s get partition number failed %v", topic, err)
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
topicToPartitionNum[topic] = len(partitionNum)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return topicToPartitionNum, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
|
|
||||||
// ConsumerGroupSession represents a consumer group member session.
|
|
||||||
type ConsumerGroupSession interface {
|
|
||||||
// Claims returns information about the claimed partitions by topic.
|
|
||||||
Claims() map[string][]int32
|
|
||||||
|
|
||||||
// MemberID returns the cluster member ID.
|
|
||||||
MemberID() string
|
|
||||||
|
|
||||||
// GenerationID returns the current generation ID.
|
|
||||||
GenerationID() int32
|
|
||||||
|
|
||||||
// MarkOffset marks the provided offset, alongside a metadata string
|
|
||||||
// that represents the state of the partition consumer at that point in time. The
|
|
||||||
// metadata string can be used by another consumer to restore that state, so it
|
|
||||||
// can resume consumption.
|
|
||||||
//
|
|
||||||
// To follow upstream conventions, you are expected to mark the offset of the
|
|
||||||
// next message to read, not the last message read. Thus, when calling `MarkOffset`
|
|
||||||
// you should typically add one to the offset of the last consumed message.
|
|
||||||
//
|
|
||||||
// Note: calling MarkOffset does not necessarily commit the offset to the backend
|
|
||||||
// store immediately for efficiency reasons, and it may never be committed if
|
|
||||||
// your application crashes. This means that you may end up processing the same
|
|
||||||
// message twice, and your processing should ideally be idempotent.
|
|
||||||
MarkOffset(topic string, partition int32, offset int64, metadata string)
|
|
||||||
|
|
||||||
// ResetOffset resets to the provided offset, alongside a metadata string that
|
|
||||||
// represents the state of the partition consumer at that point in time. Reset
|
|
||||||
// acts as a counterpart to MarkOffset, the difference being that it allows to
|
|
||||||
// reset an offset to an earlier or smaller value, where MarkOffset only
|
|
||||||
// allows incrementing the offset. cf MarkOffset for more details.
|
|
||||||
ResetOffset(topic string, partition int32, offset int64, metadata string)
|
|
||||||
|
|
||||||
// MarkMessage marks a message as consumed.
|
|
||||||
MarkMessage(msg *ConsumerMessage, metadata string)
|
|
||||||
|
|
||||||
// Context returns the session context.
|
|
||||||
Context() context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
type consumerGroupSession struct {
|
|
||||||
parent *consumerGroup
|
|
||||||
memberID string
|
|
||||||
generationID int32
|
|
||||||
handler ConsumerGroupHandler
|
|
||||||
|
|
||||||
claims map[string][]int32
|
|
||||||
offsets *offsetManager
|
|
||||||
ctx context.Context
|
|
||||||
cancel func()
|
|
||||||
|
|
||||||
waitGroup sync.WaitGroup
|
|
||||||
releaseOnce sync.Once
|
|
||||||
hbDying, hbDead chan none
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConsumerGroupSession(ctx context.Context, parent *consumerGroup, claims map[string][]int32, memberID string, generationID int32, handler ConsumerGroupHandler) (*consumerGroupSession, error) {
|
|
||||||
// init offset manager
|
|
||||||
offsets, err := newOffsetManagerFromClient(parent.groupID, memberID, generationID, parent.client)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// init context
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
|
|
||||||
// init session
|
|
||||||
sess := &consumerGroupSession{
|
|
||||||
parent: parent,
|
|
||||||
memberID: memberID,
|
|
||||||
generationID: generationID,
|
|
||||||
handler: handler,
|
|
||||||
offsets: offsets,
|
|
||||||
claims: claims,
|
|
||||||
ctx: ctx,
|
|
||||||
cancel: cancel,
|
|
||||||
hbDying: make(chan none),
|
|
||||||
hbDead: make(chan none),
|
|
||||||
}
|
|
||||||
|
|
||||||
// start heartbeat loop
|
|
||||||
go sess.heartbeatLoop()
|
|
||||||
|
|
||||||
// create a POM for each claim
|
|
||||||
for topic, partitions := range claims {
|
|
||||||
for _, partition := range partitions {
|
|
||||||
pom, err := offsets.ManagePartition(topic, partition)
|
|
||||||
if err != nil {
|
|
||||||
_ = sess.release(false)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle POM errors
|
|
||||||
go func(topic string, partition int32) {
|
|
||||||
for err := range pom.Errors() {
|
|
||||||
sess.parent.handleError(err, topic, partition)
|
|
||||||
}
|
|
||||||
}(topic, partition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// perform setup
|
|
||||||
if err := handler.Setup(sess); err != nil {
|
|
||||||
_ = sess.release(true)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// start consuming
|
|
||||||
for topic, partitions := range claims {
|
|
||||||
for _, partition := range partitions {
|
|
||||||
sess.waitGroup.Add(1)
|
|
||||||
|
|
||||||
go func(topic string, partition int32) {
|
|
||||||
defer sess.waitGroup.Done()
|
|
||||||
|
|
||||||
// cancel the as session as soon as the first
|
|
||||||
// goroutine exits
|
|
||||||
defer sess.cancel()
|
|
||||||
|
|
||||||
// consume a single topic/partition, blocking
|
|
||||||
sess.consume(topic, partition)
|
|
||||||
}(topic, partition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sess, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *consumerGroupSession) Claims() map[string][]int32 { return s.claims }
|
|
||||||
func (s *consumerGroupSession) MemberID() string { return s.memberID }
|
|
||||||
func (s *consumerGroupSession) GenerationID() int32 { return s.generationID }
|
|
||||||
|
|
||||||
func (s *consumerGroupSession) MarkOffset(topic string, partition int32, offset int64, metadata string) {
|
|
||||||
if pom := s.offsets.findPOM(topic, partition); pom != nil {
|
|
||||||
pom.MarkOffset(offset, metadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *consumerGroupSession) ResetOffset(topic string, partition int32, offset int64, metadata string) {
|
|
||||||
if pom := s.offsets.findPOM(topic, partition); pom != nil {
|
|
||||||
pom.ResetOffset(offset, metadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *consumerGroupSession) MarkMessage(msg *ConsumerMessage, metadata string) {
|
|
||||||
s.MarkOffset(msg.Topic, msg.Partition, msg.Offset+1, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *consumerGroupSession) Context() context.Context {
|
|
||||||
return s.ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *consumerGroupSession) consume(topic string, partition int32) {
|
|
||||||
// quick exit if rebalance is due
|
|
||||||
select {
|
|
||||||
case <-s.ctx.Done():
|
|
||||||
return
|
|
||||||
case <-s.parent.closed:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// get next offset
|
|
||||||
offset := s.parent.config.Consumer.Offsets.Initial
|
|
||||||
if pom := s.offsets.findPOM(topic, partition); pom != nil {
|
|
||||||
offset, _ = pom.NextOffset()
|
|
||||||
}
|
|
||||||
|
|
||||||
// create new claim
|
|
||||||
claim, err := newConsumerGroupClaim(s, topic, partition, offset)
|
|
||||||
if err != nil {
|
|
||||||
s.parent.handleError(err, topic, partition)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle errors
|
|
||||||
go func() {
|
|
||||||
for err := range claim.Errors() {
|
|
||||||
s.parent.handleError(err, topic, partition)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// trigger close when session is done
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-s.ctx.Done():
|
|
||||||
case <-s.parent.closed:
|
|
||||||
}
|
|
||||||
claim.AsyncClose()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// start processing
|
|
||||||
if err := s.handler.ConsumeClaim(s, claim); err != nil {
|
|
||||||
s.parent.handleError(err, topic, partition)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure consumer is closed & drained
|
|
||||||
claim.AsyncClose()
|
|
||||||
for _, err := range claim.waitClosed() {
|
|
||||||
s.parent.handleError(err, topic, partition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *consumerGroupSession) release(withCleanup bool) (err error) {
|
|
||||||
// signal release, stop heartbeat
|
|
||||||
s.cancel()
|
|
||||||
|
|
||||||
// wait for consumers to exit
|
|
||||||
s.waitGroup.Wait()
|
|
||||||
|
|
||||||
// perform release
|
|
||||||
s.releaseOnce.Do(func() {
|
|
||||||
if withCleanup {
|
|
||||||
if e := s.handler.Cleanup(s); e != nil {
|
|
||||||
s.parent.handleError(e, "", -1)
|
|
||||||
err = e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if e := s.offsets.Close(); e != nil {
|
|
||||||
err = e
|
|
||||||
}
|
|
||||||
|
|
||||||
close(s.hbDying)
|
|
||||||
<-s.hbDead
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *consumerGroupSession) heartbeatLoop() {
|
|
||||||
defer close(s.hbDead)
|
|
||||||
defer s.cancel() // trigger the end of the session on exit
|
|
||||||
|
|
||||||
pause := time.NewTicker(s.parent.config.Consumer.Group.Heartbeat.Interval)
|
|
||||||
defer pause.Stop()
|
|
||||||
|
|
||||||
retries := s.parent.config.Metadata.Retry.Max
|
|
||||||
for {
|
|
||||||
coordinator, err := s.parent.client.Coordinator(s.parent.groupID)
|
|
||||||
if err != nil {
|
|
||||||
if retries <= 0 {
|
|
||||||
s.parent.handleError(err, "", -1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-s.hbDying:
|
|
||||||
return
|
|
||||||
case <-time.After(s.parent.config.Metadata.Retry.Backoff):
|
|
||||||
retries--
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := s.parent.heartbeatRequest(coordinator, s.memberID, s.generationID)
|
|
||||||
if err != nil {
|
|
||||||
_ = coordinator.Close()
|
|
||||||
|
|
||||||
if retries <= 0 {
|
|
||||||
s.parent.handleError(err, "", -1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
retries--
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch resp.Err {
|
|
||||||
case ErrNoError:
|
|
||||||
retries = s.parent.config.Metadata.Retry.Max
|
|
||||||
case ErrRebalanceInProgress, ErrUnknownMemberId, ErrIllegalGeneration:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
s.parent.handleError(resp.Err, "", -1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-pause.C:
|
|
||||||
case <-s.hbDying:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
|
|
||||||
// ConsumerGroupHandler instances are used to handle individual topic/partition claims.
|
|
||||||
// It also provides hooks for your consumer group session life-cycle and allow you to
|
|
||||||
// trigger logic before or after the consume loop(s).
|
|
||||||
//
|
|
||||||
// PLEASE NOTE that handlers are likely be called from several goroutines concurrently,
|
|
||||||
// ensure that all state is safely protected against race conditions.
|
|
||||||
type ConsumerGroupHandler interface {
|
|
||||||
// Setup is run at the beginning of a new session, before ConsumeClaim.
|
|
||||||
Setup(ConsumerGroupSession) error
|
|
||||||
|
|
||||||
// Cleanup is run at the end of a session, once all ConsumeClaim goroutines have exited
|
|
||||||
// but before the offsets are committed for the very last time.
|
|
||||||
Cleanup(ConsumerGroupSession) error
|
|
||||||
|
|
||||||
// ConsumeClaim must start a consumer loop of ConsumerGroupClaim's Messages().
|
|
||||||
// Once the Messages() channel is closed, the Handler must finish its processing
|
|
||||||
// loop and exit.
|
|
||||||
ConsumeClaim(ConsumerGroupSession, ConsumerGroupClaim) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConsumerGroupClaim processes Kafka messages from a given topic and partition within a consumer group.
|
|
||||||
type ConsumerGroupClaim interface {
|
|
||||||
// Topic returns the consumed topic name.
|
|
||||||
Topic() string
|
|
||||||
|
|
||||||
// Partition returns the consumed partition.
|
|
||||||
Partition() int32
|
|
||||||
|
|
||||||
// InitialOffset returns the initial offset that was used as a starting point for this claim.
|
|
||||||
InitialOffset() int64
|
|
||||||
|
|
||||||
// HighWaterMarkOffset returns the high water mark offset of the partition,
|
|
||||||
// i.e. the offset that will be used for the next message that will be produced.
|
|
||||||
// You can use this to determine how far behind the processing is.
|
|
||||||
HighWaterMarkOffset() int64
|
|
||||||
|
|
||||||
// Messages returns the read channel for the messages that are returned by
|
|
||||||
// the broker. The messages channel will be closed when a new rebalance cycle
|
|
||||||
// is due. You must finish processing and mark offsets within
|
|
||||||
// Config.Consumer.Group.Session.Timeout before the topic/partition is eventually
|
|
||||||
// re-assigned to another group member.
|
|
||||||
Messages() <-chan *ConsumerMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
type consumerGroupClaim struct {
|
|
||||||
topic string
|
|
||||||
partition int32
|
|
||||||
offset int64
|
|
||||||
PartitionConsumer
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConsumerGroupClaim(sess *consumerGroupSession, topic string, partition int32, offset int64) (*consumerGroupClaim, error) {
|
|
||||||
pcm, err := sess.parent.consumer.ConsumePartition(topic, partition, offset)
|
|
||||||
if err == ErrOffsetOutOfRange {
|
|
||||||
offset = sess.parent.config.Consumer.Offsets.Initial
|
|
||||||
pcm, err = sess.parent.consumer.ConsumePartition(topic, partition, offset)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for err := range pcm.Errors() {
|
|
||||||
sess.parent.handleError(err, topic, partition)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return &consumerGroupClaim{
|
|
||||||
topic: topic,
|
|
||||||
partition: partition,
|
|
||||||
offset: offset,
|
|
||||||
PartitionConsumer: pcm,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consumerGroupClaim) Topic() string { return c.topic }
|
|
||||||
func (c *consumerGroupClaim) Partition() int32 { return c.partition }
|
|
||||||
func (c *consumerGroupClaim) InitialOffset() int64 { return c.offset }
|
|
||||||
|
|
||||||
// Drains messages and errors, ensures the claim is fully closed.
|
|
||||||
func (c *consumerGroupClaim) waitClosed() (errs ConsumerErrors) {
|
|
||||||
go func() {
|
|
||||||
for range c.Messages() {
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for err := range c.Errors() {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
-96
@@ -1,96 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
//ConsumerGroupMemberMetadata holds the metadata for consumer group
|
|
||||||
type ConsumerGroupMemberMetadata struct {
|
|
||||||
Version int16
|
|
||||||
Topics []string
|
|
||||||
UserData []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ConsumerGroupMemberMetadata) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt16(m.Version)
|
|
||||||
|
|
||||||
if err := pe.putStringArray(m.Topics); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pe.putBytes(m.UserData); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ConsumerGroupMemberMetadata) decode(pd packetDecoder) (err error) {
|
|
||||||
if m.Version, err = pd.getInt16(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.Topics, err = pd.getStringArray(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.UserData, err = pd.getBytes(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//ConsumerGroupMemberAssignment holds the member assignment for a consume group
|
|
||||||
type ConsumerGroupMemberAssignment struct {
|
|
||||||
Version int16
|
|
||||||
Topics map[string][]int32
|
|
||||||
UserData []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ConsumerGroupMemberAssignment) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt16(m.Version)
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(m.Topics)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for topic, partitions := range m.Topics {
|
|
||||||
if err := pe.putString(topic); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := pe.putInt32Array(partitions); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pe.putBytes(m.UserData); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ConsumerGroupMemberAssignment) decode(pd packetDecoder) (err error) {
|
|
||||||
if m.Version, err = pd.getInt16(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var topicLen int
|
|
||||||
if topicLen, err = pd.getArrayLength(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m.Topics = make(map[string][]int32, topicLen)
|
|
||||||
for i := 0; i < topicLen; i++ {
|
|
||||||
var topic string
|
|
||||||
if topic, err = pd.getString(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if m.Topics[topic], err = pd.getInt32Array(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.UserData, err = pd.getBytes(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-38
@@ -1,38 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
//ConsumerMetadataRequest is used for metadata requests
|
|
||||||
type ConsumerMetadataRequest struct {
|
|
||||||
ConsumerGroup string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ConsumerMetadataRequest) encode(pe packetEncoder) error {
|
|
||||||
tmp := new(FindCoordinatorRequest)
|
|
||||||
tmp.CoordinatorKey = r.ConsumerGroup
|
|
||||||
tmp.CoordinatorType = CoordinatorGroup
|
|
||||||
return tmp.encode(pe)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ConsumerMetadataRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
tmp := new(FindCoordinatorRequest)
|
|
||||||
if err := tmp.decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.ConsumerGroup = tmp.CoordinatorKey
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ConsumerMetadataRequest) key() int16 {
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ConsumerMetadataRequest) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ConsumerMetadataRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ConsumerMetadataRequest) requiredVersion() KafkaVersion {
|
|
||||||
return V0_8_2_0
|
|
||||||
}
|
|
||||||
-82
@@ -1,82 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
//ConsumerMetadataResponse holds the response for a consumer group meta data requests
|
|
||||||
type ConsumerMetadataResponse struct {
|
|
||||||
Err KError
|
|
||||||
Coordinator *Broker
|
|
||||||
CoordinatorID int32 // deprecated: use Coordinator.ID()
|
|
||||||
CoordinatorHost string // deprecated: use Coordinator.Addr()
|
|
||||||
CoordinatorPort int32 // deprecated: use Coordinator.Addr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ConsumerMetadataResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
tmp := new(FindCoordinatorResponse)
|
|
||||||
|
|
||||||
if err := tmp.decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Err = tmp.Err
|
|
||||||
|
|
||||||
r.Coordinator = tmp.Coordinator
|
|
||||||
if tmp.Coordinator == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// this can all go away in 2.0, but we have to fill in deprecated fields to maintain
|
|
||||||
// backwards compatibility
|
|
||||||
host, portstr, err := net.SplitHostPort(r.Coordinator.Addr())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
port, err := strconv.ParseInt(portstr, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.CoordinatorID = r.Coordinator.ID()
|
|
||||||
r.CoordinatorHost = host
|
|
||||||
r.CoordinatorPort = int32(port)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ConsumerMetadataResponse) encode(pe packetEncoder) error {
|
|
||||||
if r.Coordinator == nil {
|
|
||||||
r.Coordinator = new(Broker)
|
|
||||||
r.Coordinator.id = r.CoordinatorID
|
|
||||||
r.Coordinator.addr = net.JoinHostPort(r.CoordinatorHost, strconv.Itoa(int(r.CoordinatorPort)))
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp := &FindCoordinatorResponse{
|
|
||||||
Version: 0,
|
|
||||||
Err: r.Err,
|
|
||||||
Coordinator: r.Coordinator,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tmp.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ConsumerMetadataResponse) key() int16 {
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ConsumerMetadataResponse) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ConsumerMetadataResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ConsumerMetadataResponse) requiredVersion() KafkaVersion {
|
|
||||||
return V0_8_2_0
|
|
||||||
}
|
|
||||||
-72
@@ -1,72 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
//ControlRecordType ...
|
|
||||||
type ControlRecordType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
//ControlRecordAbort is a control record for abort
|
|
||||||
ControlRecordAbort ControlRecordType = iota
|
|
||||||
//ControlRecordCommit is a control record for commit
|
|
||||||
ControlRecordCommit
|
|
||||||
//ControlRecordUnknown is a control record of unknown type
|
|
||||||
ControlRecordUnknown
|
|
||||||
)
|
|
||||||
|
|
||||||
// Control records are returned as a record by fetchRequest
|
|
||||||
// However unlike "normal" records, they mean nothing application wise.
|
|
||||||
// They only serve internal logic for supporting transactions.
|
|
||||||
type ControlRecord struct {
|
|
||||||
Version int16
|
|
||||||
CoordinatorEpoch int32
|
|
||||||
Type ControlRecordType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cr *ControlRecord) decode(key, value packetDecoder) error {
|
|
||||||
var err error
|
|
||||||
cr.Version, err = value.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cr.CoordinatorEpoch, err = value.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// There a version for the value part AND the key part. And I have no idea if they are supposed to match or not
|
|
||||||
// Either way, all these version can only be 0 for now
|
|
||||||
cr.Version, err = key.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
recordType, err := key.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch recordType {
|
|
||||||
case 0:
|
|
||||||
cr.Type = ControlRecordAbort
|
|
||||||
case 1:
|
|
||||||
cr.Type = ControlRecordCommit
|
|
||||||
default:
|
|
||||||
// from JAVA implementation:
|
|
||||||
// UNKNOWN is used to indicate a control type which the client is not aware of and should be ignored
|
|
||||||
cr.Type = ControlRecordUnknown
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cr *ControlRecord) encode(key, value packetEncoder) {
|
|
||||||
value.putInt16(cr.Version)
|
|
||||||
value.putInt32(cr.CoordinatorEpoch)
|
|
||||||
key.putInt16(cr.Version)
|
|
||||||
|
|
||||||
switch cr.Type {
|
|
||||||
case ControlRecordAbort:
|
|
||||||
key.putInt16(0)
|
|
||||||
case ControlRecordCommit:
|
|
||||||
key.putInt16(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-86
@@ -1,86 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"hash/crc32"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type crcPolynomial int8
|
|
||||||
|
|
||||||
const (
|
|
||||||
crcIEEE crcPolynomial = iota
|
|
||||||
crcCastagnoli
|
|
||||||
)
|
|
||||||
|
|
||||||
var crc32FieldPool = sync.Pool{}
|
|
||||||
|
|
||||||
func acquireCrc32Field(polynomial crcPolynomial) *crc32Field {
|
|
||||||
val := crc32FieldPool.Get()
|
|
||||||
if val != nil {
|
|
||||||
c := val.(*crc32Field)
|
|
||||||
c.polynomial = polynomial
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
return newCRC32Field(polynomial)
|
|
||||||
}
|
|
||||||
|
|
||||||
func releaseCrc32Field(c *crc32Field) {
|
|
||||||
crc32FieldPool.Put(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
var castagnoliTable = crc32.MakeTable(crc32.Castagnoli)
|
|
||||||
|
|
||||||
// crc32Field implements the pushEncoder and pushDecoder interfaces for calculating CRC32s.
|
|
||||||
type crc32Field struct {
|
|
||||||
startOffset int
|
|
||||||
polynomial crcPolynomial
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *crc32Field) saveOffset(in int) {
|
|
||||||
c.startOffset = in
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *crc32Field) reserveLength() int {
|
|
||||||
return 4
|
|
||||||
}
|
|
||||||
|
|
||||||
func newCRC32Field(polynomial crcPolynomial) *crc32Field {
|
|
||||||
return &crc32Field{polynomial: polynomial}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *crc32Field) run(curOffset int, buf []byte) error {
|
|
||||||
crc, err := c.crc(curOffset, buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
binary.BigEndian.PutUint32(buf[c.startOffset:], crc)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *crc32Field) check(curOffset int, buf []byte) error {
|
|
||||||
crc, err := c.crc(curOffset, buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := binary.BigEndian.Uint32(buf[c.startOffset:])
|
|
||||||
if crc != expected {
|
|
||||||
return PacketDecodingError{fmt.Sprintf("CRC didn't match expected %#x got %#x", expected, crc)}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (c *crc32Field) crc(curOffset int, buf []byte) (uint32, error) {
|
|
||||||
var tab *crc32.Table
|
|
||||||
switch c.polynomial {
|
|
||||||
case crcIEEE:
|
|
||||||
tab = crc32.IEEETable
|
|
||||||
case crcCastagnoli:
|
|
||||||
tab = castagnoliTable
|
|
||||||
default:
|
|
||||||
return 0, PacketDecodingError{"invalid CRC type"}
|
|
||||||
}
|
|
||||||
return crc32.Checksum(buf[c.startOffset+4:curOffset], tab), nil
|
|
||||||
}
|
|
||||||
-125
@@ -1,125 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type CreatePartitionsRequest struct {
|
|
||||||
TopicPartitions map[string]*TopicPartition
|
|
||||||
Timeout time.Duration
|
|
||||||
ValidateOnly bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreatePartitionsRequest) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putArrayLength(len(c.TopicPartitions)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for topic, partition := range c.TopicPartitions {
|
|
||||||
if err := pe.putString(topic); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := partition.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pe.putInt32(int32(c.Timeout / time.Millisecond))
|
|
||||||
|
|
||||||
pe.putBool(c.ValidateOnly)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreatePartitionsRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.TopicPartitions = make(map[string]*TopicPartition, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
topic, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.TopicPartitions[topic] = new(TopicPartition)
|
|
||||||
if err := c.TopicPartitions[topic].decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.Timeout = time.Duration(timeout) * time.Millisecond
|
|
||||||
|
|
||||||
if c.ValidateOnly, err = pd.getBool(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *CreatePartitionsRequest) key() int16 {
|
|
||||||
return 37
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *CreatePartitionsRequest) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *CreatePartitionsRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *CreatePartitionsRequest) requiredVersion() KafkaVersion {
|
|
||||||
return V1_0_0_0
|
|
||||||
}
|
|
||||||
|
|
||||||
type TopicPartition struct {
|
|
||||||
Count int32
|
|
||||||
Assignment [][]int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TopicPartition) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt32(t.Count)
|
|
||||||
|
|
||||||
if len(t.Assignment) == 0 {
|
|
||||||
pe.putInt32(-1)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(t.Assignment)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, assign := range t.Assignment {
|
|
||||||
if err := pe.putInt32Array(assign); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TopicPartition) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
if t.Count, err = pd.getInt32(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if n <= 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
t.Assignment = make([][]int32, n)
|
|
||||||
|
|
||||||
for i := 0; i < int(n); i++ {
|
|
||||||
if t.Assignment[i], err = pd.getInt32Array(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-109
@@ -1,109 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CreatePartitionsResponse struct {
|
|
||||||
ThrottleTime time.Duration
|
|
||||||
TopicPartitionErrors map[string]*TopicPartitionError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreatePartitionsResponse) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt32(int32(c.ThrottleTime / time.Millisecond))
|
|
||||||
if err := pe.putArrayLength(len(c.TopicPartitionErrors)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for topic, partitionError := range c.TopicPartitionErrors {
|
|
||||||
if err := pe.putString(topic); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := partitionError.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreatePartitionsResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
throttleTime, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.ThrottleTime = time.Duration(throttleTime) * time.Millisecond
|
|
||||||
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.TopicPartitionErrors = make(map[string]*TopicPartitionError, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
topic, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.TopicPartitionErrors[topic] = new(TopicPartitionError)
|
|
||||||
if err := c.TopicPartitionErrors[topic].decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *CreatePartitionsResponse) key() int16 {
|
|
||||||
return 37
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *CreatePartitionsResponse) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *CreatePartitionsResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *CreatePartitionsResponse) requiredVersion() KafkaVersion {
|
|
||||||
return V1_0_0_0
|
|
||||||
}
|
|
||||||
|
|
||||||
type TopicPartitionError struct {
|
|
||||||
Err KError
|
|
||||||
ErrMsg *string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TopicPartitionError) Error() string {
|
|
||||||
text := t.Err.Error()
|
|
||||||
if t.ErrMsg != nil {
|
|
||||||
text = fmt.Sprintf("%s - %s", text, *t.ErrMsg)
|
|
||||||
}
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TopicPartitionError) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt16(int16(t.Err))
|
|
||||||
|
|
||||||
if err := pe.putNullableString(t.ErrMsg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TopicPartitionError) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
kerr, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
t.Err = KError(kerr)
|
|
||||||
|
|
||||||
if t.ErrMsg, err = pd.getNullableString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-178
@@ -1,178 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CreateTopicsRequest struct {
|
|
||||||
Version int16
|
|
||||||
|
|
||||||
TopicDetails map[string]*TopicDetail
|
|
||||||
Timeout time.Duration
|
|
||||||
ValidateOnly bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateTopicsRequest) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putArrayLength(len(c.TopicDetails)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for topic, detail := range c.TopicDetails {
|
|
||||||
if err := pe.putString(topic); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := detail.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pe.putInt32(int32(c.Timeout / time.Millisecond))
|
|
||||||
|
|
||||||
if c.Version >= 1 {
|
|
||||||
pe.putBool(c.ValidateOnly)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateTopicsRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.TopicDetails = make(map[string]*TopicDetail, n)
|
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
topic, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.TopicDetails[topic] = new(TopicDetail)
|
|
||||||
if err = c.TopicDetails[topic].decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.Timeout = time.Duration(timeout) * time.Millisecond
|
|
||||||
|
|
||||||
if version >= 1 {
|
|
||||||
c.ValidateOnly, err = pd.getBool()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Version = version
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateTopicsRequest) key() int16 {
|
|
||||||
return 19
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateTopicsRequest) version() int16 {
|
|
||||||
return c.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *CreateTopicsRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateTopicsRequest) requiredVersion() KafkaVersion {
|
|
||||||
switch c.Version {
|
|
||||||
case 2:
|
|
||||||
return V1_0_0_0
|
|
||||||
case 1:
|
|
||||||
return V0_11_0_0
|
|
||||||
default:
|
|
||||||
return V0_10_1_0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type TopicDetail struct {
|
|
||||||
NumPartitions int32
|
|
||||||
ReplicationFactor int16
|
|
||||||
ReplicaAssignment map[int32][]int32
|
|
||||||
ConfigEntries map[string]*string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TopicDetail) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt32(t.NumPartitions)
|
|
||||||
pe.putInt16(t.ReplicationFactor)
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(t.ReplicaAssignment)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for partition, assignment := range t.ReplicaAssignment {
|
|
||||||
pe.putInt32(partition)
|
|
||||||
if err := pe.putInt32Array(assignment); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(t.ConfigEntries)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for configKey, configValue := range t.ConfigEntries {
|
|
||||||
if err := pe.putString(configKey); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := pe.putNullableString(configValue); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TopicDetail) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
if t.NumPartitions, err = pd.getInt32(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if t.ReplicationFactor, err = pd.getInt16(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if n > 0 {
|
|
||||||
t.ReplicaAssignment = make(map[int32][]int32, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
replica, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if t.ReplicaAssignment[replica], err = pd.getInt32Array(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err = pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if n > 0 {
|
|
||||||
t.ConfigEntries = make(map[string]*string, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
configKey, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if t.ConfigEntries[configKey], err = pd.getNullableString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-127
@@ -1,127 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CreateTopicsResponse struct {
|
|
||||||
Version int16
|
|
||||||
ThrottleTime time.Duration
|
|
||||||
TopicErrors map[string]*TopicError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateTopicsResponse) encode(pe packetEncoder) error {
|
|
||||||
if c.Version >= 2 {
|
|
||||||
pe.putInt32(int32(c.ThrottleTime / time.Millisecond))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(c.TopicErrors)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for topic, topicError := range c.TopicErrors {
|
|
||||||
if err := pe.putString(topic); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := topicError.encode(pe, c.Version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateTopicsResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
c.Version = version
|
|
||||||
|
|
||||||
if version >= 2 {
|
|
||||||
throttleTime, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.ThrottleTime = time.Duration(throttleTime) * time.Millisecond
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.TopicErrors = make(map[string]*TopicError, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
topic, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.TopicErrors[topic] = new(TopicError)
|
|
||||||
if err := c.TopicErrors[topic].decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateTopicsResponse) key() int16 {
|
|
||||||
return 19
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateTopicsResponse) version() int16 {
|
|
||||||
return c.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateTopicsResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CreateTopicsResponse) requiredVersion() KafkaVersion {
|
|
||||||
switch c.Version {
|
|
||||||
case 2:
|
|
||||||
return V1_0_0_0
|
|
||||||
case 1:
|
|
||||||
return V0_11_0_0
|
|
||||||
default:
|
|
||||||
return V0_10_1_0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type TopicError struct {
|
|
||||||
Err KError
|
|
||||||
ErrMsg *string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TopicError) Error() string {
|
|
||||||
text := t.Err.Error()
|
|
||||||
if t.ErrMsg != nil {
|
|
||||||
text = fmt.Sprintf("%s - %s", text, *t.ErrMsg)
|
|
||||||
}
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TopicError) encode(pe packetEncoder, version int16) error {
|
|
||||||
pe.putInt16(int16(t.Err))
|
|
||||||
|
|
||||||
if version >= 1 {
|
|
||||||
if err := pe.putNullableString(t.ErrMsg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TopicError) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
kErr, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
t.Err = KError(kErr)
|
|
||||||
|
|
||||||
if version >= 1 {
|
|
||||||
if t.ErrMsg, err = pd.getNullableString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-63
@@ -1,63 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
snappy "github.com/eapache/go-xerial-snappy"
|
|
||||||
"github.com/pierrec/lz4"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
lz4ReaderPool = sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return lz4.NewReader(nil)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
gzipReaderPool sync.Pool
|
|
||||||
)
|
|
||||||
|
|
||||||
func decompress(cc CompressionCodec, data []byte) ([]byte, error) {
|
|
||||||
switch cc {
|
|
||||||
case CompressionNone:
|
|
||||||
return data, nil
|
|
||||||
case CompressionGZIP:
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
reader *gzip.Reader
|
|
||||||
readerIntf = gzipReaderPool.Get()
|
|
||||||
)
|
|
||||||
if readerIntf != nil {
|
|
||||||
reader = readerIntf.(*gzip.Reader)
|
|
||||||
} else {
|
|
||||||
reader, err = gzip.NewReader(bytes.NewReader(data))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
defer gzipReaderPool.Put(reader)
|
|
||||||
|
|
||||||
if err := reader.Reset(bytes.NewReader(data)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ioutil.ReadAll(reader)
|
|
||||||
case CompressionSnappy:
|
|
||||||
return snappy.Decode(data)
|
|
||||||
case CompressionLZ4:
|
|
||||||
reader := lz4ReaderPool.Get().(*lz4.Reader)
|
|
||||||
defer lz4ReaderPool.Put(reader)
|
|
||||||
|
|
||||||
reader.Reset(bytes.NewReader(data))
|
|
||||||
return ioutil.ReadAll(reader)
|
|
||||||
case CompressionZSTD:
|
|
||||||
return zstdDecompress(nil, data)
|
|
||||||
default:
|
|
||||||
return nil, PacketDecodingError{fmt.Sprintf("invalid compression specified (%d)", cc)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-34
@@ -1,34 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
type DeleteGroupsRequest struct {
|
|
||||||
Groups []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DeleteGroupsRequest) encode(pe packetEncoder) error {
|
|
||||||
return pe.putStringArray(r.Groups)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DeleteGroupsRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
r.Groups, err = pd.getStringArray()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DeleteGroupsRequest) key() int16 {
|
|
||||||
return 42
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DeleteGroupsRequest) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DeleteGroupsRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DeleteGroupsRequest) requiredVersion() KafkaVersion {
|
|
||||||
return V1_1_0_0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DeleteGroupsRequest) AddGroup(group string) {
|
|
||||||
r.Groups = append(r.Groups, group)
|
|
||||||
}
|
|
||||||
-74
@@ -1,74 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DeleteGroupsResponse struct {
|
|
||||||
ThrottleTime time.Duration
|
|
||||||
GroupErrorCodes map[string]KError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DeleteGroupsResponse) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt32(int32(r.ThrottleTime / time.Millisecond))
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(r.GroupErrorCodes)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for groupID, errorCode := range r.GroupErrorCodes {
|
|
||||||
if err := pe.putString(groupID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pe.putInt16(int16(errorCode))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DeleteGroupsResponse) decode(pd packetDecoder, version int16) error {
|
|
||||||
throttleTime, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.ThrottleTime = time.Duration(throttleTime) * time.Millisecond
|
|
||||||
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if n == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
r.GroupErrorCodes = make(map[string]KError, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
groupID, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
errorCode, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.GroupErrorCodes[groupID] = KError(errorCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DeleteGroupsResponse) key() int16 {
|
|
||||||
return 42
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DeleteGroupsResponse) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DeleteGroupsResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DeleteGroupsResponse) requiredVersion() KafkaVersion {
|
|
||||||
return V1_1_0_0
|
|
||||||
}
|
|
||||||
-130
@@ -1,130 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// request message format is:
|
|
||||||
// [topic] timeout(int32)
|
|
||||||
// where topic is:
|
|
||||||
// name(string) [partition]
|
|
||||||
// where partition is:
|
|
||||||
// id(int32) offset(int64)
|
|
||||||
|
|
||||||
type DeleteRecordsRequest struct {
|
|
||||||
Topics map[string]*DeleteRecordsRequestTopic
|
|
||||||
Timeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteRecordsRequest) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putArrayLength(len(d.Topics)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
keys := make([]string, 0, len(d.Topics))
|
|
||||||
for topic := range d.Topics {
|
|
||||||
keys = append(keys, topic)
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
for _, topic := range keys {
|
|
||||||
if err := pe.putString(topic); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := d.Topics[topic].encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pe.putInt32(int32(d.Timeout / time.Millisecond))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteRecordsRequest) decode(pd packetDecoder, version int16) error {
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if n > 0 {
|
|
||||||
d.Topics = make(map[string]*DeleteRecordsRequestTopic, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
topic, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
details := new(DeleteRecordsRequestTopic)
|
|
||||||
if err = details.decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.Topics[topic] = details
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.Timeout = time.Duration(timeout) * time.Millisecond
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteRecordsRequest) key() int16 {
|
|
||||||
return 21
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteRecordsRequest) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteRecordsRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteRecordsRequest) requiredVersion() KafkaVersion {
|
|
||||||
return V0_11_0_0
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeleteRecordsRequestTopic struct {
|
|
||||||
PartitionOffsets map[int32]int64 // partition => offset
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *DeleteRecordsRequestTopic) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putArrayLength(len(t.PartitionOffsets)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
keys := make([]int32, 0, len(t.PartitionOffsets))
|
|
||||||
for partition := range t.PartitionOffsets {
|
|
||||||
keys = append(keys, partition)
|
|
||||||
}
|
|
||||||
sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
|
|
||||||
for _, partition := range keys {
|
|
||||||
pe.putInt32(partition)
|
|
||||||
pe.putInt64(t.PartitionOffsets[partition])
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *DeleteRecordsRequestTopic) decode(pd packetDecoder, version int16) error {
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if n > 0 {
|
|
||||||
t.PartitionOffsets = make(map[int32]int64, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
partition, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
offset, err := pd.getInt64()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
t.PartitionOffsets[partition] = offset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-162
@@ -1,162 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// response message format is:
|
|
||||||
// throttleMs(int32) [topic]
|
|
||||||
// where topic is:
|
|
||||||
// name(string) [partition]
|
|
||||||
// where partition is:
|
|
||||||
// id(int32) low_watermark(int64) error_code(int16)
|
|
||||||
|
|
||||||
type DeleteRecordsResponse struct {
|
|
||||||
Version int16
|
|
||||||
ThrottleTime time.Duration
|
|
||||||
Topics map[string]*DeleteRecordsResponseTopic
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteRecordsResponse) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt32(int32(d.ThrottleTime / time.Millisecond))
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(d.Topics)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
keys := make([]string, 0, len(d.Topics))
|
|
||||||
for topic := range d.Topics {
|
|
||||||
keys = append(keys, topic)
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
for _, topic := range keys {
|
|
||||||
if err := pe.putString(topic); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := d.Topics[topic].encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteRecordsResponse) decode(pd packetDecoder, version int16) error {
|
|
||||||
d.Version = version
|
|
||||||
|
|
||||||
throttleTime, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.ThrottleTime = time.Duration(throttleTime) * time.Millisecond
|
|
||||||
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if n > 0 {
|
|
||||||
d.Topics = make(map[string]*DeleteRecordsResponseTopic, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
topic, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
details := new(DeleteRecordsResponseTopic)
|
|
||||||
if err = details.decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.Topics[topic] = details
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteRecordsResponse) key() int16 {
|
|
||||||
return 21
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteRecordsResponse) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteRecordsResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteRecordsResponse) requiredVersion() KafkaVersion {
|
|
||||||
return V0_11_0_0
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeleteRecordsResponseTopic struct {
|
|
||||||
Partitions map[int32]*DeleteRecordsResponsePartition
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *DeleteRecordsResponseTopic) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putArrayLength(len(t.Partitions)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
keys := make([]int32, 0, len(t.Partitions))
|
|
||||||
for partition := range t.Partitions {
|
|
||||||
keys = append(keys, partition)
|
|
||||||
}
|
|
||||||
sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
|
|
||||||
for _, partition := range keys {
|
|
||||||
pe.putInt32(partition)
|
|
||||||
if err := t.Partitions[partition].encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *DeleteRecordsResponseTopic) decode(pd packetDecoder, version int16) error {
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if n > 0 {
|
|
||||||
t.Partitions = make(map[int32]*DeleteRecordsResponsePartition, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
partition, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
details := new(DeleteRecordsResponsePartition)
|
|
||||||
if err = details.decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
t.Partitions[partition] = details
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeleteRecordsResponsePartition struct {
|
|
||||||
LowWatermark int64
|
|
||||||
Err KError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *DeleteRecordsResponsePartition) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt64(t.LowWatermark)
|
|
||||||
pe.putInt16(int16(t.Err))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *DeleteRecordsResponsePartition) decode(pd packetDecoder, version int16) error {
|
|
||||||
lowWatermark, err := pd.getInt64()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
t.LowWatermark = lowWatermark
|
|
||||||
|
|
||||||
kErr, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
t.Err = KError(kErr)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-52
@@ -1,52 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type DeleteTopicsRequest struct {
|
|
||||||
Version int16
|
|
||||||
Topics []string
|
|
||||||
Timeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteTopicsRequest) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putStringArray(d.Topics); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pe.putInt32(int32(d.Timeout / time.Millisecond))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteTopicsRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
if d.Topics, err = pd.getStringArray(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
timeout, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.Timeout = time.Duration(timeout) * time.Millisecond
|
|
||||||
d.Version = version
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteTopicsRequest) key() int16 {
|
|
||||||
return 20
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteTopicsRequest) version() int16 {
|
|
||||||
return d.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteTopicsRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteTopicsRequest) requiredVersion() KafkaVersion {
|
|
||||||
switch d.Version {
|
|
||||||
case 1:
|
|
||||||
return V0_11_0_0
|
|
||||||
default:
|
|
||||||
return V0_10_1_0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-82
@@ -1,82 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type DeleteTopicsResponse struct {
|
|
||||||
Version int16
|
|
||||||
ThrottleTime time.Duration
|
|
||||||
TopicErrorCodes map[string]KError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteTopicsResponse) encode(pe packetEncoder) error {
|
|
||||||
if d.Version >= 1 {
|
|
||||||
pe.putInt32(int32(d.ThrottleTime / time.Millisecond))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(d.TopicErrorCodes)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for topic, errorCode := range d.TopicErrorCodes {
|
|
||||||
if err := pe.putString(topic); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pe.putInt16(int16(errorCode))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteTopicsResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
if version >= 1 {
|
|
||||||
throttleTime, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.ThrottleTime = time.Duration(throttleTime) * time.Millisecond
|
|
||||||
|
|
||||||
d.Version = version
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.TopicErrorCodes = make(map[string]KError, n)
|
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
topic, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
errorCode, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.TopicErrorCodes[topic] = KError(errorCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteTopicsResponse) key() int16 {
|
|
||||||
return 20
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteTopicsResponse) version() int16 {
|
|
||||||
return d.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteTopicsResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DeleteTopicsResponse) requiredVersion() KafkaVersion {
|
|
||||||
switch d.Version {
|
|
||||||
case 1:
|
|
||||||
return V0_11_0_0
|
|
||||||
default:
|
|
||||||
return V0_10_1_0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-116
@@ -1,116 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
type DescribeConfigsRequest struct {
|
|
||||||
Version int16
|
|
||||||
Resources []*ConfigResource
|
|
||||||
IncludeSynonyms bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigResource struct {
|
|
||||||
Type ConfigResourceType
|
|
||||||
Name string
|
|
||||||
ConfigNames []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeConfigsRequest) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putArrayLength(len(r.Resources)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range r.Resources {
|
|
||||||
pe.putInt8(int8(c.Type))
|
|
||||||
if err := pe.putString(c.Name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.ConfigNames) == 0 {
|
|
||||||
pe.putInt32(-1)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := pe.putStringArray(c.ConfigNames); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Version >= 1 {
|
|
||||||
pe.putBool(r.IncludeSynonyms)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeConfigsRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Resources = make([]*ConfigResource, n)
|
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
r.Resources[i] = &ConfigResource{}
|
|
||||||
t, err := pd.getInt8()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Resources[i].Type = ConfigResourceType(t)
|
|
||||||
name, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Resources[i].Name = name
|
|
||||||
|
|
||||||
confLength, err := pd.getArrayLength()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if confLength == -1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
cfnames := make([]string, confLength)
|
|
||||||
for i := 0; i < confLength; i++ {
|
|
||||||
s, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cfnames[i] = s
|
|
||||||
}
|
|
||||||
r.Resources[i].ConfigNames = cfnames
|
|
||||||
}
|
|
||||||
r.Version = version
|
|
||||||
if r.Version >= 1 {
|
|
||||||
b, err := pd.getBool()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.IncludeSynonyms = b
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeConfigsRequest) key() int16 {
|
|
||||||
return 32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeConfigsRequest) version() int16 {
|
|
||||||
return r.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeConfigsRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeConfigsRequest) requiredVersion() KafkaVersion {
|
|
||||||
switch r.Version {
|
|
||||||
case 1:
|
|
||||||
return V1_1_0_0
|
|
||||||
case 2:
|
|
||||||
return V2_0_0_0
|
|
||||||
default:
|
|
||||||
return V0_11_0_0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-327
@@ -1,327 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConfigSource int8
|
|
||||||
|
|
||||||
func (s ConfigSource) String() string {
|
|
||||||
switch s {
|
|
||||||
case SourceUnknown:
|
|
||||||
return "Unknown"
|
|
||||||
case SourceTopic:
|
|
||||||
return "Topic"
|
|
||||||
case SourceDynamicBroker:
|
|
||||||
return "DynamicBroker"
|
|
||||||
case SourceDynamicDefaultBroker:
|
|
||||||
return "DynamicDefaultBroker"
|
|
||||||
case SourceStaticBroker:
|
|
||||||
return "StaticBroker"
|
|
||||||
case SourceDefault:
|
|
||||||
return "Default"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("Source Invalid: %d", int(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
SourceUnknown ConfigSource = iota
|
|
||||||
SourceTopic
|
|
||||||
SourceDynamicBroker
|
|
||||||
SourceDynamicDefaultBroker
|
|
||||||
SourceStaticBroker
|
|
||||||
SourceDefault
|
|
||||||
)
|
|
||||||
|
|
||||||
type DescribeConfigsResponse struct {
|
|
||||||
Version int16
|
|
||||||
ThrottleTime time.Duration
|
|
||||||
Resources []*ResourceResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResourceResponse struct {
|
|
||||||
ErrorCode int16
|
|
||||||
ErrorMsg string
|
|
||||||
Type ConfigResourceType
|
|
||||||
Name string
|
|
||||||
Configs []*ConfigEntry
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigEntry struct {
|
|
||||||
Name string
|
|
||||||
Value string
|
|
||||||
ReadOnly bool
|
|
||||||
Default bool
|
|
||||||
Source ConfigSource
|
|
||||||
Sensitive bool
|
|
||||||
Synonyms []*ConfigSynonym
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigSynonym struct {
|
|
||||||
ConfigName string
|
|
||||||
ConfigValue string
|
|
||||||
Source ConfigSource
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeConfigsResponse) encode(pe packetEncoder) (err error) {
|
|
||||||
pe.putInt32(int32(r.ThrottleTime / time.Millisecond))
|
|
||||||
if err = pe.putArrayLength(len(r.Resources)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range r.Resources {
|
|
||||||
if err = c.encode(pe, r.Version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeConfigsResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
r.Version = version
|
|
||||||
throttleTime, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.ThrottleTime = time.Duration(throttleTime) * time.Millisecond
|
|
||||||
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Resources = make([]*ResourceResponse, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
rr := &ResourceResponse{}
|
|
||||||
if err := rr.decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Resources[i] = rr
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeConfigsResponse) key() int16 {
|
|
||||||
return 32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeConfigsResponse) version() int16 {
|
|
||||||
return r.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeConfigsResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeConfigsResponse) requiredVersion() KafkaVersion {
|
|
||||||
switch r.Version {
|
|
||||||
case 1:
|
|
||||||
return V1_0_0_0
|
|
||||||
case 2:
|
|
||||||
return V2_0_0_0
|
|
||||||
default:
|
|
||||||
return V0_11_0_0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ResourceResponse) encode(pe packetEncoder, version int16) (err error) {
|
|
||||||
pe.putInt16(r.ErrorCode)
|
|
||||||
|
|
||||||
if err = pe.putString(r.ErrorMsg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pe.putInt8(int8(r.Type))
|
|
||||||
|
|
||||||
if err = pe.putString(r.Name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = pe.putArrayLength(len(r.Configs)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range r.Configs {
|
|
||||||
if err = c.encode(pe, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ResourceResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
ec, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.ErrorCode = ec
|
|
||||||
|
|
||||||
em, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.ErrorMsg = em
|
|
||||||
|
|
||||||
t, err := pd.getInt8()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Type = ConfigResourceType(t)
|
|
||||||
|
|
||||||
name, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Name = name
|
|
||||||
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Configs = make([]*ConfigEntry, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
c := &ConfigEntry{}
|
|
||||||
if err := c.decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Configs[i] = c
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ConfigEntry) encode(pe packetEncoder, version int16) (err error) {
|
|
||||||
if err = pe.putString(r.Name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = pe.putString(r.Value); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pe.putBool(r.ReadOnly)
|
|
||||||
|
|
||||||
if version <= 0 {
|
|
||||||
pe.putBool(r.Default)
|
|
||||||
pe.putBool(r.Sensitive)
|
|
||||||
} else {
|
|
||||||
pe.putInt8(int8(r.Source))
|
|
||||||
pe.putBool(r.Sensitive)
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(r.Synonyms)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, c := range r.Synonyms {
|
|
||||||
if err = c.encode(pe, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//https://cwiki.apache.org/confluence/display/KAFKA/KIP-226+-+Dynamic+Broker+Configuration
|
|
||||||
func (r *ConfigEntry) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
if version == 0 {
|
|
||||||
r.Source = SourceUnknown
|
|
||||||
}
|
|
||||||
name, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Name = name
|
|
||||||
|
|
||||||
value, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Value = value
|
|
||||||
|
|
||||||
read, err := pd.getBool()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.ReadOnly = read
|
|
||||||
|
|
||||||
if version == 0 {
|
|
||||||
defaultB, err := pd.getBool()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Default = defaultB
|
|
||||||
if defaultB {
|
|
||||||
r.Source = SourceDefault
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
source, err := pd.getInt8()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Source = ConfigSource(source)
|
|
||||||
r.Default = r.Source == SourceDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
sensitive, err := pd.getBool()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Sensitive = sensitive
|
|
||||||
|
|
||||||
if version > 0 {
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Synonyms = make([]*ConfigSynonym, n)
|
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
s := &ConfigSynonym{}
|
|
||||||
if err := s.decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Synonyms[i] = s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConfigSynonym) encode(pe packetEncoder, version int16) (err error) {
|
|
||||||
err = pe.putString(c.ConfigName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = pe.putString(c.ConfigValue)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pe.putInt8(int8(c.Source))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConfigSynonym) decode(pd packetDecoder, version int16) error {
|
|
||||||
name, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
c.ConfigName = name
|
|
||||||
|
|
||||||
value, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
c.ConfigValue = value
|
|
||||||
|
|
||||||
source, err := pd.getInt8()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
c.Source = ConfigSource(source)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-34
@@ -1,34 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
type DescribeGroupsRequest struct {
|
|
||||||
Groups []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeGroupsRequest) encode(pe packetEncoder) error {
|
|
||||||
return pe.putStringArray(r.Groups)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeGroupsRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
r.Groups, err = pd.getStringArray()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeGroupsRequest) key() int16 {
|
|
||||||
return 15
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeGroupsRequest) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeGroupsRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeGroupsRequest) requiredVersion() KafkaVersion {
|
|
||||||
return V0_9_0_0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeGroupsRequest) AddGroup(group string) {
|
|
||||||
r.Groups = append(r.Groups, group)
|
|
||||||
}
|
|
||||||
-191
@@ -1,191 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
type DescribeGroupsResponse struct {
|
|
||||||
Groups []*GroupDescription
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeGroupsResponse) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putArrayLength(len(r.Groups)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, groupDescription := range r.Groups {
|
|
||||||
if err := groupDescription.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeGroupsResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Groups = make([]*GroupDescription, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
r.Groups[i] = new(GroupDescription)
|
|
||||||
if err := r.Groups[i].decode(pd); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeGroupsResponse) key() int16 {
|
|
||||||
return 15
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeGroupsResponse) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeGroupsResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeGroupsResponse) requiredVersion() KafkaVersion {
|
|
||||||
return V0_9_0_0
|
|
||||||
}
|
|
||||||
|
|
||||||
type GroupDescription struct {
|
|
||||||
Err KError
|
|
||||||
GroupId string
|
|
||||||
State string
|
|
||||||
ProtocolType string
|
|
||||||
Protocol string
|
|
||||||
Members map[string]*GroupMemberDescription
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gd *GroupDescription) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt16(int16(gd.Err))
|
|
||||||
|
|
||||||
if err := pe.putString(gd.GroupId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := pe.putString(gd.State); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := pe.putString(gd.ProtocolType); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := pe.putString(gd.Protocol); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(gd.Members)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for memberId, groupMemberDescription := range gd.Members {
|
|
||||||
if err := pe.putString(memberId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := groupMemberDescription.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gd *GroupDescription) decode(pd packetDecoder) (err error) {
|
|
||||||
kerr, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
gd.Err = KError(kerr)
|
|
||||||
|
|
||||||
if gd.GroupId, err = pd.getString(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if gd.State, err = pd.getString(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if gd.ProtocolType, err = pd.getString(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if gd.Protocol, err = pd.getString(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if n == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
gd.Members = make(map[string]*GroupMemberDescription)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
memberId, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
gd.Members[memberId] = new(GroupMemberDescription)
|
|
||||||
if err := gd.Members[memberId].decode(pd); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type GroupMemberDescription struct {
|
|
||||||
ClientId string
|
|
||||||
ClientHost string
|
|
||||||
MemberMetadata []byte
|
|
||||||
MemberAssignment []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gmd *GroupMemberDescription) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putString(gmd.ClientId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := pe.putString(gmd.ClientHost); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := pe.putBytes(gmd.MemberMetadata); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := pe.putBytes(gmd.MemberAssignment); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gmd *GroupMemberDescription) decode(pd packetDecoder) (err error) {
|
|
||||||
if gmd.ClientId, err = pd.getString(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if gmd.ClientHost, err = pd.getString(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if gmd.MemberMetadata, err = pd.getBytes(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if gmd.MemberAssignment, err = pd.getBytes(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gmd *GroupMemberDescription) GetMemberAssignment() (*ConsumerGroupMemberAssignment, error) {
|
|
||||||
assignment := new(ConsumerGroupMemberAssignment)
|
|
||||||
err := decode(gmd.MemberAssignment, assignment)
|
|
||||||
return assignment, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gmd *GroupMemberDescription) GetMemberMetadata() (*ConsumerGroupMemberMetadata, error) {
|
|
||||||
metadata := new(ConsumerGroupMemberMetadata)
|
|
||||||
err := decode(gmd.MemberMetadata, metadata)
|
|
||||||
return metadata, err
|
|
||||||
}
|
|
||||||
-87
@@ -1,87 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
// DescribeLogDirsRequest is a describe request to get partitions' log size
|
|
||||||
type DescribeLogDirsRequest struct {
|
|
||||||
// Version 0 and 1 are equal
|
|
||||||
// The version number is bumped to indicate that on quota violation brokers send out responses before throttling.
|
|
||||||
Version int16
|
|
||||||
|
|
||||||
// If this is an empty array, all topics will be queried
|
|
||||||
DescribeTopics []DescribeLogDirsRequestTopic
|
|
||||||
}
|
|
||||||
|
|
||||||
// DescribeLogDirsRequestTopic is a describe request about the log dir of one or more partitions within a Topic
|
|
||||||
type DescribeLogDirsRequestTopic struct {
|
|
||||||
Topic string
|
|
||||||
PartitionIDs []int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeLogDirsRequest) encode(pe packetEncoder) error {
|
|
||||||
length := len(r.DescribeTopics)
|
|
||||||
if length == 0 {
|
|
||||||
// In order to query all topics we must send null
|
|
||||||
length = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(length); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, d := range r.DescribeTopics {
|
|
||||||
if err := pe.putString(d.Topic); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pe.putInt32Array(d.PartitionIDs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeLogDirsRequest) decode(pd packetDecoder, version int16) error {
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if n == -1 {
|
|
||||||
n = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
topics := make([]DescribeLogDirsRequestTopic, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
topics[i] = DescribeLogDirsRequestTopic{}
|
|
||||||
|
|
||||||
topic, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
topics[i].Topic = topic
|
|
||||||
|
|
||||||
pIDs, err := pd.getInt32Array()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
topics[i].PartitionIDs = pIDs
|
|
||||||
}
|
|
||||||
r.DescribeTopics = topics
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeLogDirsRequest) key() int16 {
|
|
||||||
return 35
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeLogDirsRequest) version() int16 {
|
|
||||||
return r.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeLogDirsRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeLogDirsRequest) requiredVersion() KafkaVersion {
|
|
||||||
return V1_0_0_0
|
|
||||||
}
|
|
||||||
-229
@@ -1,229 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type DescribeLogDirsResponse struct {
|
|
||||||
ThrottleTime time.Duration
|
|
||||||
|
|
||||||
// Version 0 and 1 are equal
|
|
||||||
// The version number is bumped to indicate that on quota violation brokers send out responses before throttling.
|
|
||||||
Version int16
|
|
||||||
|
|
||||||
LogDirs []DescribeLogDirsResponseDirMetadata
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeLogDirsResponse) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt32(int32(r.ThrottleTime / time.Millisecond))
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(r.LogDirs)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, dir := range r.LogDirs {
|
|
||||||
if err := dir.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeLogDirsResponse) decode(pd packetDecoder, version int16) error {
|
|
||||||
throttleTime, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.ThrottleTime = time.Duration(throttleTime) * time.Millisecond
|
|
||||||
|
|
||||||
// Decode array of DescribeLogDirsResponseDirMetadata
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.LogDirs = make([]DescribeLogDirsResponseDirMetadata, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
dir := DescribeLogDirsResponseDirMetadata{}
|
|
||||||
if err := dir.decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.LogDirs[i] = dir
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeLogDirsResponse) key() int16 {
|
|
||||||
return 35
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeLogDirsResponse) version() int16 {
|
|
||||||
return r.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeLogDirsResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeLogDirsResponse) requiredVersion() KafkaVersion {
|
|
||||||
return V1_0_0_0
|
|
||||||
}
|
|
||||||
|
|
||||||
type DescribeLogDirsResponseDirMetadata struct {
|
|
||||||
ErrorCode KError
|
|
||||||
|
|
||||||
// The absolute log directory path
|
|
||||||
Path string
|
|
||||||
Topics []DescribeLogDirsResponseTopic
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeLogDirsResponseDirMetadata) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt16(int16(r.ErrorCode))
|
|
||||||
|
|
||||||
if err := pe.putString(r.Path); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(r.Topics)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, topic := range r.Topics {
|
|
||||||
if err := topic.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeLogDirsResponseDirMetadata) decode(pd packetDecoder, version int16) error {
|
|
||||||
errCode, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.ErrorCode = KError(errCode)
|
|
||||||
|
|
||||||
path, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Path = path
|
|
||||||
|
|
||||||
// Decode array of DescribeLogDirsResponseTopic
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Topics = make([]DescribeLogDirsResponseTopic, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
t := DescribeLogDirsResponseTopic{}
|
|
||||||
|
|
||||||
if err := t.decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Topics[i] = t
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DescribeLogDirsResponseTopic contains a topic's partitions descriptions
|
|
||||||
type DescribeLogDirsResponseTopic struct {
|
|
||||||
Topic string
|
|
||||||
Partitions []DescribeLogDirsResponsePartition
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeLogDirsResponseTopic) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putString(r.Topic); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(r.Partitions)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, partition := range r.Partitions {
|
|
||||||
if err := partition.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeLogDirsResponseTopic) decode(pd packetDecoder, version int16) error {
|
|
||||||
t, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Topic = t
|
|
||||||
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Partitions = make([]DescribeLogDirsResponsePartition, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
p := DescribeLogDirsResponsePartition{}
|
|
||||||
if err := p.decode(pd, version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Partitions[i] = p
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DescribeLogDirsResponsePartition describes a partition's log directory
|
|
||||||
type DescribeLogDirsResponsePartition struct {
|
|
||||||
PartitionID int32
|
|
||||||
|
|
||||||
// The size of the log segments of the partition in bytes.
|
|
||||||
Size int64
|
|
||||||
|
|
||||||
// The lag of the log's LEO w.r.t. partition's HW (if it is the current log for the partition) or
|
|
||||||
// current replica's LEO (if it is the future log for the partition)
|
|
||||||
OffsetLag int64
|
|
||||||
|
|
||||||
// True if this log is created by AlterReplicaLogDirsRequest and will replace the current log of
|
|
||||||
// the replica in the future.
|
|
||||||
IsTemporary bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeLogDirsResponsePartition) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt32(r.PartitionID)
|
|
||||||
pe.putInt64(r.Size)
|
|
||||||
pe.putInt64(r.OffsetLag)
|
|
||||||
pe.putBool(r.IsTemporary)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DescribeLogDirsResponsePartition) decode(pd packetDecoder, version int16) error {
|
|
||||||
pID, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.PartitionID = pID
|
|
||||||
|
|
||||||
size, err := pd.getInt64()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Size = size
|
|
||||||
|
|
||||||
lag, err := pd.getInt64()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.OffsetLag = lag
|
|
||||||
|
|
||||||
isTemp, err := pd.getBool()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.IsTemporary = isTemp
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-10
@@ -1,10 +0,0 @@
|
|||||||
name: sarama
|
|
||||||
|
|
||||||
up:
|
|
||||||
- go:
|
|
||||||
version: '1.14'
|
|
||||||
|
|
||||||
commands:
|
|
||||||
test:
|
|
||||||
run: make test
|
|
||||||
desc: 'run unit tests'
|
|
||||||
-94
@@ -1,94 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/rcrowley/go-metrics"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Encoder is the interface that wraps the basic Encode method.
|
|
||||||
// Anything implementing Encoder can be turned into bytes using Kafka's encoding rules.
|
|
||||||
type encoder interface {
|
|
||||||
encode(pe packetEncoder) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type encoderWithHeader interface {
|
|
||||||
encoder
|
|
||||||
headerVersion() int16
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode takes an Encoder and turns it into bytes while potentially recording metrics.
|
|
||||||
func encode(e encoder, metricRegistry metrics.Registry) ([]byte, error) {
|
|
||||||
if e == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var prepEnc prepEncoder
|
|
||||||
var realEnc realEncoder
|
|
||||||
|
|
||||||
err := e.encode(&prepEnc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if prepEnc.length < 0 || prepEnc.length > int(MaxRequestSize) {
|
|
||||||
return nil, PacketEncodingError{fmt.Sprintf("invalid request size (%d)", prepEnc.length)}
|
|
||||||
}
|
|
||||||
|
|
||||||
realEnc.raw = make([]byte, prepEnc.length)
|
|
||||||
realEnc.registry = metricRegistry
|
|
||||||
err = e.encode(&realEnc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return realEnc.raw, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decoder is the interface that wraps the basic Decode method.
|
|
||||||
// Anything implementing Decoder can be extracted from bytes using Kafka's encoding rules.
|
|
||||||
type decoder interface {
|
|
||||||
decode(pd packetDecoder) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type versionedDecoder interface {
|
|
||||||
decode(pd packetDecoder, version int16) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode takes bytes and a Decoder and fills the fields of the decoder from the bytes,
|
|
||||||
// interpreted using Kafka's encoding rules.
|
|
||||||
func decode(buf []byte, in decoder) error {
|
|
||||||
if buf == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
helper := realDecoder{raw: buf}
|
|
||||||
err := in.decode(&helper)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if helper.off != len(buf) {
|
|
||||||
return PacketDecodingError{"invalid length"}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func versionedDecode(buf []byte, in versionedDecoder, version int16) error {
|
|
||||||
if buf == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
helper := realDecoder{raw: buf}
|
|
||||||
err := in.decode(&helper, version)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if helper.off != len(buf) {
|
|
||||||
return PacketDecodingError{"invalid length"}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-54
@@ -1,54 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
type EndTxnRequest struct {
|
|
||||||
TransactionalID string
|
|
||||||
ProducerID int64
|
|
||||||
ProducerEpoch int16
|
|
||||||
TransactionResult bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *EndTxnRequest) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putString(a.TransactionalID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pe.putInt64(a.ProducerID)
|
|
||||||
|
|
||||||
pe.putInt16(a.ProducerEpoch)
|
|
||||||
|
|
||||||
pe.putBool(a.TransactionResult)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *EndTxnRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
if a.TransactionalID, err = pd.getString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if a.ProducerID, err = pd.getInt64(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if a.ProducerEpoch, err = pd.getInt16(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if a.TransactionResult, err = pd.getBool(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *EndTxnRequest) key() int16 {
|
|
||||||
return 26
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *EndTxnRequest) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EndTxnRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *EndTxnRequest) requiredVersion() KafkaVersion {
|
|
||||||
return V0_11_0_0
|
|
||||||
}
|
|
||||||
-48
@@ -1,48 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type EndTxnResponse struct {
|
|
||||||
ThrottleTime time.Duration
|
|
||||||
Err KError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EndTxnResponse) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt32(int32(e.ThrottleTime / time.Millisecond))
|
|
||||||
pe.putInt16(int16(e.Err))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EndTxnResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
throttleTime, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
e.ThrottleTime = time.Duration(throttleTime) * time.Millisecond
|
|
||||||
|
|
||||||
kerr, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
e.Err = KError(kerr)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EndTxnResponse) key() int16 {
|
|
||||||
return 25
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EndTxnResponse) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EndTxnResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EndTxnResponse) requiredVersion() KafkaVersion {
|
|
||||||
return V0_11_0_0
|
|
||||||
}
|
|
||||||
-385
@@ -1,385 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrOutOfBrokers is the error returned when the client has run out of brokers to talk to because all of them errored
|
|
||||||
// or otherwise failed to respond.
|
|
||||||
var ErrOutOfBrokers = errors.New("kafka: client has run out of available brokers to talk to (Is your cluster reachable?)")
|
|
||||||
|
|
||||||
// ErrClosedClient is the error returned when a method is called on a client that has been closed.
|
|
||||||
var ErrClosedClient = errors.New("kafka: tried to use a client that was closed")
|
|
||||||
|
|
||||||
// ErrIncompleteResponse is the error returned when the server returns a syntactically valid response, but it does
|
|
||||||
// not contain the expected information.
|
|
||||||
var ErrIncompleteResponse = errors.New("kafka: response did not contain all the expected topic/partition blocks")
|
|
||||||
|
|
||||||
// ErrInvalidPartition is the error returned when a partitioner returns an invalid partition index
|
|
||||||
// (meaning one outside of the range [0...numPartitions-1]).
|
|
||||||
var ErrInvalidPartition = errors.New("kafka: partitioner returned an invalid partition index")
|
|
||||||
|
|
||||||
// ErrAlreadyConnected is the error returned when calling Open() on a Broker that is already connected or connecting.
|
|
||||||
var ErrAlreadyConnected = errors.New("kafka: broker connection already initiated")
|
|
||||||
|
|
||||||
// ErrNotConnected is the error returned when trying to send or call Close() on a Broker that is not connected.
|
|
||||||
var ErrNotConnected = errors.New("kafka: broker not connected")
|
|
||||||
|
|
||||||
// ErrInsufficientData is returned when decoding and the packet is truncated. This can be expected
|
|
||||||
// when requesting messages, since as an optimization the server is allowed to return a partial message at the end
|
|
||||||
// of the message set.
|
|
||||||
var ErrInsufficientData = errors.New("kafka: insufficient data to decode packet, more bytes expected")
|
|
||||||
|
|
||||||
// ErrShuttingDown is returned when a producer receives a message during shutdown.
|
|
||||||
var ErrShuttingDown = errors.New("kafka: message received by producer in process of shutting down")
|
|
||||||
|
|
||||||
// ErrMessageTooLarge is returned when the next message to consume is larger than the configured Consumer.Fetch.Max
|
|
||||||
var ErrMessageTooLarge = errors.New("kafka: message is larger than Consumer.Fetch.Max")
|
|
||||||
|
|
||||||
// ErrConsumerOffsetNotAdvanced is returned when a partition consumer didn't advance its offset after parsing
|
|
||||||
// a RecordBatch.
|
|
||||||
var ErrConsumerOffsetNotAdvanced = errors.New("kafka: consumer offset was not advanced after a RecordBatch")
|
|
||||||
|
|
||||||
// ErrControllerNotAvailable is returned when server didn't give correct controller id. May be kafka server's version
|
|
||||||
// is lower than 0.10.0.0.
|
|
||||||
var ErrControllerNotAvailable = errors.New("kafka: controller is not available")
|
|
||||||
|
|
||||||
// ErrNoTopicsToUpdateMetadata is returned when Meta.Full is set to false but no specific topics were found to update
|
|
||||||
// the metadata.
|
|
||||||
var ErrNoTopicsToUpdateMetadata = errors.New("kafka: no specific topics to update metadata")
|
|
||||||
|
|
||||||
// PacketEncodingError is returned from a failure while encoding a Kafka packet. This can happen, for example,
|
|
||||||
// if you try to encode a string over 2^15 characters in length, since Kafka's encoding rules do not permit that.
|
|
||||||
type PacketEncodingError struct {
|
|
||||||
Info string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err PacketEncodingError) Error() string {
|
|
||||||
return fmt.Sprintf("kafka: error encoding packet: %s", err.Info)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PacketDecodingError is returned when there was an error (other than truncated data) decoding the Kafka broker's response.
|
|
||||||
// This can be a bad CRC or length field, or any other invalid value.
|
|
||||||
type PacketDecodingError struct {
|
|
||||||
Info string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err PacketDecodingError) Error() string {
|
|
||||||
return fmt.Sprintf("kafka: error decoding packet: %s", err.Info)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConfigurationError is the type of error returned from a constructor (e.g. NewClient, or NewConsumer)
|
|
||||||
// when the specified configuration is invalid.
|
|
||||||
type ConfigurationError string
|
|
||||||
|
|
||||||
func (err ConfigurationError) Error() string {
|
|
||||||
return "kafka: invalid configuration (" + string(err) + ")"
|
|
||||||
}
|
|
||||||
|
|
||||||
// KError is the type of error that can be returned directly by the Kafka broker.
|
|
||||||
// See https://cwiki.apache.org/confluence/display/KAFKA/A+Guide+To+The+Kafka+Protocol#AGuideToTheKafkaProtocol-ErrorCodes
|
|
||||||
type KError int16
|
|
||||||
|
|
||||||
// MultiError is used to contain multi error.
|
|
||||||
type MultiError struct {
|
|
||||||
Errors *[]error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mErr MultiError) Error() string {
|
|
||||||
var errString = ""
|
|
||||||
for _, err := range *mErr.Errors {
|
|
||||||
errString += err.Error() + ","
|
|
||||||
}
|
|
||||||
return errString
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mErr MultiError) PrettyError() string {
|
|
||||||
var errString = ""
|
|
||||||
for _, err := range *mErr.Errors {
|
|
||||||
errString += err.Error() + "\n"
|
|
||||||
}
|
|
||||||
return errString
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrDeleteRecords is the type of error returned when fail to delete the required records
|
|
||||||
type ErrDeleteRecords struct {
|
|
||||||
MultiError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrDeleteRecords) Error() string {
|
|
||||||
return "kafka server: failed to delete records " + err.MultiError.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
type ErrReassignPartitions struct {
|
|
||||||
MultiError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrReassignPartitions) Error() string {
|
|
||||||
return fmt.Sprintf("failed to reassign partitions for topic: \n%s", err.MultiError.PrettyError())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Numeric error codes returned by the Kafka server.
|
|
||||||
const (
|
|
||||||
ErrNoError KError = 0
|
|
||||||
ErrUnknown KError = -1
|
|
||||||
ErrOffsetOutOfRange KError = 1
|
|
||||||
ErrInvalidMessage KError = 2
|
|
||||||
ErrUnknownTopicOrPartition KError = 3
|
|
||||||
ErrInvalidMessageSize KError = 4
|
|
||||||
ErrLeaderNotAvailable KError = 5
|
|
||||||
ErrNotLeaderForPartition KError = 6
|
|
||||||
ErrRequestTimedOut KError = 7
|
|
||||||
ErrBrokerNotAvailable KError = 8
|
|
||||||
ErrReplicaNotAvailable KError = 9
|
|
||||||
ErrMessageSizeTooLarge KError = 10
|
|
||||||
ErrStaleControllerEpochCode KError = 11
|
|
||||||
ErrOffsetMetadataTooLarge KError = 12
|
|
||||||
ErrNetworkException KError = 13
|
|
||||||
ErrOffsetsLoadInProgress KError = 14
|
|
||||||
ErrConsumerCoordinatorNotAvailable KError = 15
|
|
||||||
ErrNotCoordinatorForConsumer KError = 16
|
|
||||||
ErrInvalidTopic KError = 17
|
|
||||||
ErrMessageSetSizeTooLarge KError = 18
|
|
||||||
ErrNotEnoughReplicas KError = 19
|
|
||||||
ErrNotEnoughReplicasAfterAppend KError = 20
|
|
||||||
ErrInvalidRequiredAcks KError = 21
|
|
||||||
ErrIllegalGeneration KError = 22
|
|
||||||
ErrInconsistentGroupProtocol KError = 23
|
|
||||||
ErrInvalidGroupId KError = 24
|
|
||||||
ErrUnknownMemberId KError = 25
|
|
||||||
ErrInvalidSessionTimeout KError = 26
|
|
||||||
ErrRebalanceInProgress KError = 27
|
|
||||||
ErrInvalidCommitOffsetSize KError = 28
|
|
||||||
ErrTopicAuthorizationFailed KError = 29
|
|
||||||
ErrGroupAuthorizationFailed KError = 30
|
|
||||||
ErrClusterAuthorizationFailed KError = 31
|
|
||||||
ErrInvalidTimestamp KError = 32
|
|
||||||
ErrUnsupportedSASLMechanism KError = 33
|
|
||||||
ErrIllegalSASLState KError = 34
|
|
||||||
ErrUnsupportedVersion KError = 35
|
|
||||||
ErrTopicAlreadyExists KError = 36
|
|
||||||
ErrInvalidPartitions KError = 37
|
|
||||||
ErrInvalidReplicationFactor KError = 38
|
|
||||||
ErrInvalidReplicaAssignment KError = 39
|
|
||||||
ErrInvalidConfig KError = 40
|
|
||||||
ErrNotController KError = 41
|
|
||||||
ErrInvalidRequest KError = 42
|
|
||||||
ErrUnsupportedForMessageFormat KError = 43
|
|
||||||
ErrPolicyViolation KError = 44
|
|
||||||
ErrOutOfOrderSequenceNumber KError = 45
|
|
||||||
ErrDuplicateSequenceNumber KError = 46
|
|
||||||
ErrInvalidProducerEpoch KError = 47
|
|
||||||
ErrInvalidTxnState KError = 48
|
|
||||||
ErrInvalidProducerIDMapping KError = 49
|
|
||||||
ErrInvalidTransactionTimeout KError = 50
|
|
||||||
ErrConcurrentTransactions KError = 51
|
|
||||||
ErrTransactionCoordinatorFenced KError = 52
|
|
||||||
ErrTransactionalIDAuthorizationFailed KError = 53
|
|
||||||
ErrSecurityDisabled KError = 54
|
|
||||||
ErrOperationNotAttempted KError = 55
|
|
||||||
ErrKafkaStorageError KError = 56
|
|
||||||
ErrLogDirNotFound KError = 57
|
|
||||||
ErrSASLAuthenticationFailed KError = 58
|
|
||||||
ErrUnknownProducerID KError = 59
|
|
||||||
ErrReassignmentInProgress KError = 60
|
|
||||||
ErrDelegationTokenAuthDisabled KError = 61
|
|
||||||
ErrDelegationTokenNotFound KError = 62
|
|
||||||
ErrDelegationTokenOwnerMismatch KError = 63
|
|
||||||
ErrDelegationTokenRequestNotAllowed KError = 64
|
|
||||||
ErrDelegationTokenAuthorizationFailed KError = 65
|
|
||||||
ErrDelegationTokenExpired KError = 66
|
|
||||||
ErrInvalidPrincipalType KError = 67
|
|
||||||
ErrNonEmptyGroup KError = 68
|
|
||||||
ErrGroupIDNotFound KError = 69
|
|
||||||
ErrFetchSessionIDNotFound KError = 70
|
|
||||||
ErrInvalidFetchSessionEpoch KError = 71
|
|
||||||
ErrListenerNotFound KError = 72
|
|
||||||
ErrTopicDeletionDisabled KError = 73
|
|
||||||
ErrFencedLeaderEpoch KError = 74
|
|
||||||
ErrUnknownLeaderEpoch KError = 75
|
|
||||||
ErrUnsupportedCompressionType KError = 76
|
|
||||||
ErrStaleBrokerEpoch KError = 77
|
|
||||||
ErrOffsetNotAvailable KError = 78
|
|
||||||
ErrMemberIdRequired KError = 79
|
|
||||||
ErrPreferredLeaderNotAvailable KError = 80
|
|
||||||
ErrGroupMaxSizeReached KError = 81
|
|
||||||
ErrFencedInstancedId KError = 82
|
|
||||||
)
|
|
||||||
|
|
||||||
func (err KError) Error() string {
|
|
||||||
// Error messages stolen/adapted from
|
|
||||||
// https://kafka.apache.org/protocol#protocol_error_codes
|
|
||||||
switch err {
|
|
||||||
case ErrNoError:
|
|
||||||
return "kafka server: Not an error, why are you printing me?"
|
|
||||||
case ErrUnknown:
|
|
||||||
return "kafka server: Unexpected (unknown?) server error."
|
|
||||||
case ErrOffsetOutOfRange:
|
|
||||||
return "kafka server: The requested offset is outside the range of offsets maintained by the server for the given topic/partition."
|
|
||||||
case ErrInvalidMessage:
|
|
||||||
return "kafka server: Message contents does not match its CRC."
|
|
||||||
case ErrUnknownTopicOrPartition:
|
|
||||||
return "kafka server: Request was for a topic or partition that does not exist on this broker."
|
|
||||||
case ErrInvalidMessageSize:
|
|
||||||
return "kafka server: The message has a negative size."
|
|
||||||
case ErrLeaderNotAvailable:
|
|
||||||
return "kafka server: In the middle of a leadership election, there is currently no leader for this partition and hence it is unavailable for writes."
|
|
||||||
case ErrNotLeaderForPartition:
|
|
||||||
return "kafka server: Tried to send a message to a replica that is not the leader for some partition. Your metadata is out of date."
|
|
||||||
case ErrRequestTimedOut:
|
|
||||||
return "kafka server: Request exceeded the user-specified time limit in the request."
|
|
||||||
case ErrBrokerNotAvailable:
|
|
||||||
return "kafka server: Broker not available. Not a client facing error, we should never receive this!!!"
|
|
||||||
case ErrReplicaNotAvailable:
|
|
||||||
return "kafka server: Replica information not available, one or more brokers are down."
|
|
||||||
case ErrMessageSizeTooLarge:
|
|
||||||
return "kafka server: Message was too large, server rejected it to avoid allocation error."
|
|
||||||
case ErrStaleControllerEpochCode:
|
|
||||||
return "kafka server: StaleControllerEpochCode (internal error code for broker-to-broker communication)."
|
|
||||||
case ErrOffsetMetadataTooLarge:
|
|
||||||
return "kafka server: Specified a string larger than the configured maximum for offset metadata."
|
|
||||||
case ErrNetworkException:
|
|
||||||
return "kafka server: The server disconnected before a response was received."
|
|
||||||
case ErrOffsetsLoadInProgress:
|
|
||||||
return "kafka server: The broker is still loading offsets after a leader change for that offset's topic partition."
|
|
||||||
case ErrConsumerCoordinatorNotAvailable:
|
|
||||||
return "kafka server: Offset's topic has not yet been created."
|
|
||||||
case ErrNotCoordinatorForConsumer:
|
|
||||||
return "kafka server: Request was for a consumer group that is not coordinated by this broker."
|
|
||||||
case ErrInvalidTopic:
|
|
||||||
return "kafka server: The request attempted to perform an operation on an invalid topic."
|
|
||||||
case ErrMessageSetSizeTooLarge:
|
|
||||||
return "kafka server: The request included message batch larger than the configured segment size on the server."
|
|
||||||
case ErrNotEnoughReplicas:
|
|
||||||
return "kafka server: Messages are rejected since there are fewer in-sync replicas than required."
|
|
||||||
case ErrNotEnoughReplicasAfterAppend:
|
|
||||||
return "kafka server: Messages are written to the log, but to fewer in-sync replicas than required."
|
|
||||||
case ErrInvalidRequiredAcks:
|
|
||||||
return "kafka server: The number of required acks is invalid (should be either -1, 0, or 1)."
|
|
||||||
case ErrIllegalGeneration:
|
|
||||||
return "kafka server: The provided generation id is not the current generation."
|
|
||||||
case ErrInconsistentGroupProtocol:
|
|
||||||
return "kafka server: The provider group protocol type is incompatible with the other members."
|
|
||||||
case ErrInvalidGroupId:
|
|
||||||
return "kafka server: The provided group id was empty."
|
|
||||||
case ErrUnknownMemberId:
|
|
||||||
return "kafka server: The provided member is not known in the current generation."
|
|
||||||
case ErrInvalidSessionTimeout:
|
|
||||||
return "kafka server: The provided session timeout is outside the allowed range."
|
|
||||||
case ErrRebalanceInProgress:
|
|
||||||
return "kafka server: A rebalance for the group is in progress. Please re-join the group."
|
|
||||||
case ErrInvalidCommitOffsetSize:
|
|
||||||
return "kafka server: The provided commit metadata was too large."
|
|
||||||
case ErrTopicAuthorizationFailed:
|
|
||||||
return "kafka server: The client is not authorized to access this topic."
|
|
||||||
case ErrGroupAuthorizationFailed:
|
|
||||||
return "kafka server: The client is not authorized to access this group."
|
|
||||||
case ErrClusterAuthorizationFailed:
|
|
||||||
return "kafka server: The client is not authorized to send this request type."
|
|
||||||
case ErrInvalidTimestamp:
|
|
||||||
return "kafka server: The timestamp of the message is out of acceptable range."
|
|
||||||
case ErrUnsupportedSASLMechanism:
|
|
||||||
return "kafka server: The broker does not support the requested SASL mechanism."
|
|
||||||
case ErrIllegalSASLState:
|
|
||||||
return "kafka server: Request is not valid given the current SASL state."
|
|
||||||
case ErrUnsupportedVersion:
|
|
||||||
return "kafka server: The version of API is not supported."
|
|
||||||
case ErrTopicAlreadyExists:
|
|
||||||
return "kafka server: Topic with this name already exists."
|
|
||||||
case ErrInvalidPartitions:
|
|
||||||
return "kafka server: Number of partitions is invalid."
|
|
||||||
case ErrInvalidReplicationFactor:
|
|
||||||
return "kafka server: Replication-factor is invalid."
|
|
||||||
case ErrInvalidReplicaAssignment:
|
|
||||||
return "kafka server: Replica assignment is invalid."
|
|
||||||
case ErrInvalidConfig:
|
|
||||||
return "kafka server: Configuration is invalid."
|
|
||||||
case ErrNotController:
|
|
||||||
return "kafka server: This is not the correct controller for this cluster."
|
|
||||||
case ErrInvalidRequest:
|
|
||||||
return "kafka server: This most likely occurs because of a request being malformed by the client library or the message was sent to an incompatible broker. See the broker logs for more details."
|
|
||||||
case ErrUnsupportedForMessageFormat:
|
|
||||||
return "kafka server: The requested operation is not supported by the message format version."
|
|
||||||
case ErrPolicyViolation:
|
|
||||||
return "kafka server: Request parameters do not satisfy the configured policy."
|
|
||||||
case ErrOutOfOrderSequenceNumber:
|
|
||||||
return "kafka server: The broker received an out of order sequence number."
|
|
||||||
case ErrDuplicateSequenceNumber:
|
|
||||||
return "kafka server: The broker received a duplicate sequence number."
|
|
||||||
case ErrInvalidProducerEpoch:
|
|
||||||
return "kafka server: Producer attempted an operation with an old epoch."
|
|
||||||
case ErrInvalidTxnState:
|
|
||||||
return "kafka server: The producer attempted a transactional operation in an invalid state."
|
|
||||||
case ErrInvalidProducerIDMapping:
|
|
||||||
return "kafka server: The producer attempted to use a producer id which is not currently assigned to its transactional id."
|
|
||||||
case ErrInvalidTransactionTimeout:
|
|
||||||
return "kafka server: The transaction timeout is larger than the maximum value allowed by the broker (as configured by max.transaction.timeout.ms)."
|
|
||||||
case ErrConcurrentTransactions:
|
|
||||||
return "kafka server: The producer attempted to update a transaction while another concurrent operation on the same transaction was ongoing."
|
|
||||||
case ErrTransactionCoordinatorFenced:
|
|
||||||
return "kafka server: The transaction coordinator sending a WriteTxnMarker is no longer the current coordinator for a given producer."
|
|
||||||
case ErrTransactionalIDAuthorizationFailed:
|
|
||||||
return "kafka server: Transactional ID authorization failed."
|
|
||||||
case ErrSecurityDisabled:
|
|
||||||
return "kafka server: Security features are disabled."
|
|
||||||
case ErrOperationNotAttempted:
|
|
||||||
return "kafka server: The broker did not attempt to execute this operation."
|
|
||||||
case ErrKafkaStorageError:
|
|
||||||
return "kafka server: Disk error when trying to access log file on the disk."
|
|
||||||
case ErrLogDirNotFound:
|
|
||||||
return "kafka server: The specified log directory is not found in the broker config."
|
|
||||||
case ErrSASLAuthenticationFailed:
|
|
||||||
return "kafka server: SASL Authentication failed."
|
|
||||||
case ErrUnknownProducerID:
|
|
||||||
return "kafka server: The broker could not locate the producer metadata associated with the Producer ID."
|
|
||||||
case ErrReassignmentInProgress:
|
|
||||||
return "kafka server: A partition reassignment is in progress."
|
|
||||||
case ErrDelegationTokenAuthDisabled:
|
|
||||||
return "kafka server: Delegation Token feature is not enabled."
|
|
||||||
case ErrDelegationTokenNotFound:
|
|
||||||
return "kafka server: Delegation Token is not found on server."
|
|
||||||
case ErrDelegationTokenOwnerMismatch:
|
|
||||||
return "kafka server: Specified Principal is not valid Owner/Renewer."
|
|
||||||
case ErrDelegationTokenRequestNotAllowed:
|
|
||||||
return "kafka server: Delegation Token requests are not allowed on PLAINTEXT/1-way SSL channels and on delegation token authenticated channels."
|
|
||||||
case ErrDelegationTokenAuthorizationFailed:
|
|
||||||
return "kafka server: Delegation Token authorization failed."
|
|
||||||
case ErrDelegationTokenExpired:
|
|
||||||
return "kafka server: Delegation Token is expired."
|
|
||||||
case ErrInvalidPrincipalType:
|
|
||||||
return "kafka server: Supplied principalType is not supported."
|
|
||||||
case ErrNonEmptyGroup:
|
|
||||||
return "kafka server: The group is not empty."
|
|
||||||
case ErrGroupIDNotFound:
|
|
||||||
return "kafka server: The group id does not exist."
|
|
||||||
case ErrFetchSessionIDNotFound:
|
|
||||||
return "kafka server: The fetch session ID was not found."
|
|
||||||
case ErrInvalidFetchSessionEpoch:
|
|
||||||
return "kafka server: The fetch session epoch is invalid."
|
|
||||||
case ErrListenerNotFound:
|
|
||||||
return "kafka server: There is no listener on the leader broker that matches the listener on which metadata request was processed."
|
|
||||||
case ErrTopicDeletionDisabled:
|
|
||||||
return "kafka server: Topic deletion is disabled."
|
|
||||||
case ErrFencedLeaderEpoch:
|
|
||||||
return "kafka server: The leader epoch in the request is older than the epoch on the broker."
|
|
||||||
case ErrUnknownLeaderEpoch:
|
|
||||||
return "kafka server: The leader epoch in the request is newer than the epoch on the broker."
|
|
||||||
case ErrUnsupportedCompressionType:
|
|
||||||
return "kafka server: The requesting client does not support the compression type of given partition."
|
|
||||||
case ErrStaleBrokerEpoch:
|
|
||||||
return "kafka server: Broker epoch has changed"
|
|
||||||
case ErrOffsetNotAvailable:
|
|
||||||
return "kafka server: The leader high watermark has not caught up from a recent leader election so the offsets cannot be guaranteed to be monotonically increasing"
|
|
||||||
case ErrMemberIdRequired:
|
|
||||||
return "kafka server: The group member needs to have a valid member id before actually entering a consumer group"
|
|
||||||
case ErrPreferredLeaderNotAvailable:
|
|
||||||
return "kafka server: The preferred leader was not available"
|
|
||||||
case ErrGroupMaxSizeReached:
|
|
||||||
return "kafka server: Consumer group The consumer group has reached its max size. already has the configured maximum number of members."
|
|
||||||
case ErrFencedInstancedId:
|
|
||||||
return "kafka server: The broker rejected this static consumer since another consumer with the same group.instance.id has registered with a different member.id."
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("Unknown error, how did this happen? Error code = %d", err)
|
|
||||||
}
|
|
||||||
-295
@@ -1,295 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
type fetchRequestBlock struct {
|
|
||||||
Version int16
|
|
||||||
currentLeaderEpoch int32
|
|
||||||
fetchOffset int64
|
|
||||||
logStartOffset int64
|
|
||||||
maxBytes int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *fetchRequestBlock) encode(pe packetEncoder, version int16) error {
|
|
||||||
b.Version = version
|
|
||||||
if b.Version >= 9 {
|
|
||||||
pe.putInt32(b.currentLeaderEpoch)
|
|
||||||
}
|
|
||||||
pe.putInt64(b.fetchOffset)
|
|
||||||
if b.Version >= 5 {
|
|
||||||
pe.putInt64(b.logStartOffset)
|
|
||||||
}
|
|
||||||
pe.putInt32(b.maxBytes)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *fetchRequestBlock) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
b.Version = version
|
|
||||||
if b.Version >= 9 {
|
|
||||||
if b.currentLeaderEpoch, err = pd.getInt32(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if b.fetchOffset, err = pd.getInt64(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if b.Version >= 5 {
|
|
||||||
if b.logStartOffset, err = pd.getInt64(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if b.maxBytes, err = pd.getInt32(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FetchRequest (API key 1) will fetch Kafka messages. Version 3 introduced the MaxBytes field. See
|
|
||||||
// https://issues.apache.org/jira/browse/KAFKA-2063 for a discussion of the issues leading up to that. The KIP is at
|
|
||||||
// https://cwiki.apache.org/confluence/display/KAFKA/KIP-74%3A+Add+Fetch+Response+Size+Limit+in+Bytes
|
|
||||||
type FetchRequest struct {
|
|
||||||
MaxWaitTime int32
|
|
||||||
MinBytes int32
|
|
||||||
MaxBytes int32
|
|
||||||
Version int16
|
|
||||||
Isolation IsolationLevel
|
|
||||||
SessionID int32
|
|
||||||
SessionEpoch int32
|
|
||||||
blocks map[string]map[int32]*fetchRequestBlock
|
|
||||||
forgotten map[string][]int32
|
|
||||||
RackID string
|
|
||||||
}
|
|
||||||
|
|
||||||
type IsolationLevel int8
|
|
||||||
|
|
||||||
const (
|
|
||||||
ReadUncommitted IsolationLevel = iota
|
|
||||||
ReadCommitted
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r *FetchRequest) encode(pe packetEncoder) (err error) {
|
|
||||||
pe.putInt32(-1) // replica ID is always -1 for clients
|
|
||||||
pe.putInt32(r.MaxWaitTime)
|
|
||||||
pe.putInt32(r.MinBytes)
|
|
||||||
if r.Version >= 3 {
|
|
||||||
pe.putInt32(r.MaxBytes)
|
|
||||||
}
|
|
||||||
if r.Version >= 4 {
|
|
||||||
pe.putInt8(int8(r.Isolation))
|
|
||||||
}
|
|
||||||
if r.Version >= 7 {
|
|
||||||
pe.putInt32(r.SessionID)
|
|
||||||
pe.putInt32(r.SessionEpoch)
|
|
||||||
}
|
|
||||||
err = pe.putArrayLength(len(r.blocks))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for topic, blocks := range r.blocks {
|
|
||||||
err = pe.putString(topic)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = pe.putArrayLength(len(blocks))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for partition, block := range blocks {
|
|
||||||
pe.putInt32(partition)
|
|
||||||
err = block.encode(pe, r.Version)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r.Version >= 7 {
|
|
||||||
err = pe.putArrayLength(len(r.forgotten))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for topic, partitions := range r.forgotten {
|
|
||||||
err = pe.putString(topic)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = pe.putArrayLength(len(partitions))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, partition := range partitions {
|
|
||||||
pe.putInt32(partition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r.Version >= 11 {
|
|
||||||
err = pe.putString(r.RackID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
r.Version = version
|
|
||||||
|
|
||||||
if _, err = pd.getInt32(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if r.MaxWaitTime, err = pd.getInt32(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if r.MinBytes, err = pd.getInt32(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if r.Version >= 3 {
|
|
||||||
if r.MaxBytes, err = pd.getInt32(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r.Version >= 4 {
|
|
||||||
isolation, err := pd.getInt8()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Isolation = IsolationLevel(isolation)
|
|
||||||
}
|
|
||||||
if r.Version >= 7 {
|
|
||||||
r.SessionID, err = pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.SessionEpoch, err = pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
topicCount, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if topicCount == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
r.blocks = make(map[string]map[int32]*fetchRequestBlock)
|
|
||||||
for i := 0; i < topicCount; i++ {
|
|
||||||
topic, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
partitionCount, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.blocks[topic] = make(map[int32]*fetchRequestBlock)
|
|
||||||
for j := 0; j < partitionCount; j++ {
|
|
||||||
partition, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fetchBlock := &fetchRequestBlock{}
|
|
||||||
if err = fetchBlock.decode(pd, r.Version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.blocks[topic][partition] = fetchBlock
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Version >= 7 {
|
|
||||||
forgottenCount, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.forgotten = make(map[string][]int32)
|
|
||||||
for i := 0; i < forgottenCount; i++ {
|
|
||||||
topic, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
partitionCount, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.forgotten[topic] = make([]int32, partitionCount)
|
|
||||||
|
|
||||||
for j := 0; j < partitionCount; j++ {
|
|
||||||
partition, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.forgotten[topic][j] = partition
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Version >= 11 {
|
|
||||||
r.RackID, err = pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchRequest) key() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchRequest) version() int16 {
|
|
||||||
return r.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchRequest) requiredVersion() KafkaVersion {
|
|
||||||
switch r.Version {
|
|
||||||
case 0:
|
|
||||||
return MinVersion
|
|
||||||
case 1:
|
|
||||||
return V0_9_0_0
|
|
||||||
case 2:
|
|
||||||
return V0_10_0_0
|
|
||||||
case 3:
|
|
||||||
return V0_10_1_0
|
|
||||||
case 4, 5:
|
|
||||||
return V0_11_0_0
|
|
||||||
case 6:
|
|
||||||
return V1_0_0_0
|
|
||||||
case 7:
|
|
||||||
return V1_1_0_0
|
|
||||||
case 8:
|
|
||||||
return V2_0_0_0
|
|
||||||
case 9, 10:
|
|
||||||
return V2_1_0_0
|
|
||||||
case 11:
|
|
||||||
return V2_3_0_0
|
|
||||||
default:
|
|
||||||
return MaxVersion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchRequest) AddBlock(topic string, partitionID int32, fetchOffset int64, maxBytes int32) {
|
|
||||||
if r.blocks == nil {
|
|
||||||
r.blocks = make(map[string]map[int32]*fetchRequestBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Version >= 7 && r.forgotten == nil {
|
|
||||||
r.forgotten = make(map[string][]int32)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.blocks[topic] == nil {
|
|
||||||
r.blocks[topic] = make(map[int32]*fetchRequestBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp := new(fetchRequestBlock)
|
|
||||||
tmp.Version = r.Version
|
|
||||||
tmp.maxBytes = maxBytes
|
|
||||||
tmp.fetchOffset = fetchOffset
|
|
||||||
if r.Version >= 9 {
|
|
||||||
tmp.currentLeaderEpoch = int32(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.blocks[topic][partitionID] = tmp
|
|
||||||
}
|
|
||||||
-546
@@ -1,546 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AbortedTransaction struct {
|
|
||||||
ProducerID int64
|
|
||||||
FirstOffset int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *AbortedTransaction) decode(pd packetDecoder) (err error) {
|
|
||||||
if t.ProducerID, err = pd.getInt64(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.FirstOffset, err = pd.getInt64(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *AbortedTransaction) encode(pe packetEncoder) (err error) {
|
|
||||||
pe.putInt64(t.ProducerID)
|
|
||||||
pe.putInt64(t.FirstOffset)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type FetchResponseBlock struct {
|
|
||||||
Err KError
|
|
||||||
HighWaterMarkOffset int64
|
|
||||||
LastStableOffset int64
|
|
||||||
LogStartOffset int64
|
|
||||||
AbortedTransactions []*AbortedTransaction
|
|
||||||
PreferredReadReplica int32
|
|
||||||
Records *Records // deprecated: use FetchResponseBlock.RecordsSet
|
|
||||||
RecordsSet []*Records
|
|
||||||
Partial bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *FetchResponseBlock) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
tmp, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.Err = KError(tmp)
|
|
||||||
|
|
||||||
b.HighWaterMarkOffset, err = pd.getInt64()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if version >= 4 {
|
|
||||||
b.LastStableOffset, err = pd.getInt64()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if version >= 5 {
|
|
||||||
b.LogStartOffset, err = pd.getInt64()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
numTransact, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if numTransact >= 0 {
|
|
||||||
b.AbortedTransactions = make([]*AbortedTransaction, numTransact)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < numTransact; i++ {
|
|
||||||
transact := new(AbortedTransaction)
|
|
||||||
if err = transact.decode(pd); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.AbortedTransactions[i] = transact
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if version >= 11 {
|
|
||||||
b.PreferredReadReplica, err = pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
recordsSize, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
recordsDecoder, err := pd.getSubset(int(recordsSize))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.RecordsSet = []*Records{}
|
|
||||||
|
|
||||||
for recordsDecoder.remaining() > 0 {
|
|
||||||
records := &Records{}
|
|
||||||
if err := records.decode(recordsDecoder); err != nil {
|
|
||||||
// If we have at least one decoded records, this is not an error
|
|
||||||
if err == ErrInsufficientData {
|
|
||||||
if len(b.RecordsSet) == 0 {
|
|
||||||
b.Partial = true
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
partial, err := records.isPartial()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := records.numRecords()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if n > 0 || (partial && len(b.RecordsSet) == 0) {
|
|
||||||
b.RecordsSet = append(b.RecordsSet, records)
|
|
||||||
|
|
||||||
if b.Records == nil {
|
|
||||||
b.Records = records
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
overflow, err := records.isOverflow()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if partial || overflow {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *FetchResponseBlock) numRecords() (int, error) {
|
|
||||||
sum := 0
|
|
||||||
|
|
||||||
for _, records := range b.RecordsSet {
|
|
||||||
count, err := records.numRecords()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sum += count
|
|
||||||
}
|
|
||||||
|
|
||||||
return sum, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *FetchResponseBlock) isPartial() (bool, error) {
|
|
||||||
if b.Partial {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(b.RecordsSet) == 1 {
|
|
||||||
return b.RecordsSet[0].isPartial()
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *FetchResponseBlock) encode(pe packetEncoder, version int16) (err error) {
|
|
||||||
pe.putInt16(int16(b.Err))
|
|
||||||
|
|
||||||
pe.putInt64(b.HighWaterMarkOffset)
|
|
||||||
|
|
||||||
if version >= 4 {
|
|
||||||
pe.putInt64(b.LastStableOffset)
|
|
||||||
|
|
||||||
if version >= 5 {
|
|
||||||
pe.putInt64(b.LogStartOffset)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = pe.putArrayLength(len(b.AbortedTransactions)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, transact := range b.AbortedTransactions {
|
|
||||||
if err = transact.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if version >= 11 {
|
|
||||||
pe.putInt32(b.PreferredReadReplica)
|
|
||||||
}
|
|
||||||
|
|
||||||
pe.push(&lengthField{})
|
|
||||||
for _, records := range b.RecordsSet {
|
|
||||||
err = records.encode(pe)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pe.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *FetchResponseBlock) getAbortedTransactions() []*AbortedTransaction {
|
|
||||||
// I can't find any doc that guarantee the field `fetchResponse.AbortedTransactions` is ordered
|
|
||||||
// plus Java implementation use a PriorityQueue based on `FirstOffset`. I guess we have to order it ourself
|
|
||||||
at := b.AbortedTransactions
|
|
||||||
sort.Slice(
|
|
||||||
at,
|
|
||||||
func(i, j int) bool { return at[i].FirstOffset < at[j].FirstOffset },
|
|
||||||
)
|
|
||||||
return at
|
|
||||||
}
|
|
||||||
|
|
||||||
type FetchResponse struct {
|
|
||||||
Blocks map[string]map[int32]*FetchResponseBlock
|
|
||||||
ThrottleTime time.Duration
|
|
||||||
ErrorCode int16
|
|
||||||
SessionID int32
|
|
||||||
Version int16
|
|
||||||
LogAppendTime bool
|
|
||||||
Timestamp time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
r.Version = version
|
|
||||||
|
|
||||||
if r.Version >= 1 {
|
|
||||||
throttle, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.ThrottleTime = time.Duration(throttle) * time.Millisecond
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Version >= 7 {
|
|
||||||
r.ErrorCode, err = pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.SessionID, err = pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
numTopics, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Blocks = make(map[string]map[int32]*FetchResponseBlock, numTopics)
|
|
||||||
for i := 0; i < numTopics; i++ {
|
|
||||||
name, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
numBlocks, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Blocks[name] = make(map[int32]*FetchResponseBlock, numBlocks)
|
|
||||||
|
|
||||||
for j := 0; j < numBlocks; j++ {
|
|
||||||
id, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
block := new(FetchResponseBlock)
|
|
||||||
err = block.decode(pd, version)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Blocks[name][id] = block
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchResponse) encode(pe packetEncoder) (err error) {
|
|
||||||
if r.Version >= 1 {
|
|
||||||
pe.putInt32(int32(r.ThrottleTime / time.Millisecond))
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Version >= 7 {
|
|
||||||
pe.putInt16(r.ErrorCode)
|
|
||||||
pe.putInt32(r.SessionID)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = pe.putArrayLength(len(r.Blocks))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for topic, partitions := range r.Blocks {
|
|
||||||
err = pe.putString(topic)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = pe.putArrayLength(len(partitions))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for id, block := range partitions {
|
|
||||||
pe.putInt32(id)
|
|
||||||
err = block.encode(pe, r.Version)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchResponse) key() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchResponse) version() int16 {
|
|
||||||
return r.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchResponse) requiredVersion() KafkaVersion {
|
|
||||||
switch r.Version {
|
|
||||||
case 0:
|
|
||||||
return MinVersion
|
|
||||||
case 1:
|
|
||||||
return V0_9_0_0
|
|
||||||
case 2:
|
|
||||||
return V0_10_0_0
|
|
||||||
case 3:
|
|
||||||
return V0_10_1_0
|
|
||||||
case 4, 5:
|
|
||||||
return V0_11_0_0
|
|
||||||
case 6:
|
|
||||||
return V1_0_0_0
|
|
||||||
case 7:
|
|
||||||
return V1_1_0_0
|
|
||||||
case 8:
|
|
||||||
return V2_0_0_0
|
|
||||||
case 9, 10:
|
|
||||||
return V2_1_0_0
|
|
||||||
case 11:
|
|
||||||
return V2_3_0_0
|
|
||||||
default:
|
|
||||||
return MaxVersion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchResponse) GetBlock(topic string, partition int32) *FetchResponseBlock {
|
|
||||||
if r.Blocks == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Blocks[topic] == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.Blocks[topic][partition]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchResponse) AddError(topic string, partition int32, err KError) {
|
|
||||||
if r.Blocks == nil {
|
|
||||||
r.Blocks = make(map[string]map[int32]*FetchResponseBlock)
|
|
||||||
}
|
|
||||||
partitions, ok := r.Blocks[topic]
|
|
||||||
if !ok {
|
|
||||||
partitions = make(map[int32]*FetchResponseBlock)
|
|
||||||
r.Blocks[topic] = partitions
|
|
||||||
}
|
|
||||||
frb, ok := partitions[partition]
|
|
||||||
if !ok {
|
|
||||||
frb = new(FetchResponseBlock)
|
|
||||||
partitions[partition] = frb
|
|
||||||
}
|
|
||||||
frb.Err = err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchResponse) getOrCreateBlock(topic string, partition int32) *FetchResponseBlock {
|
|
||||||
if r.Blocks == nil {
|
|
||||||
r.Blocks = make(map[string]map[int32]*FetchResponseBlock)
|
|
||||||
}
|
|
||||||
partitions, ok := r.Blocks[topic]
|
|
||||||
if !ok {
|
|
||||||
partitions = make(map[int32]*FetchResponseBlock)
|
|
||||||
r.Blocks[topic] = partitions
|
|
||||||
}
|
|
||||||
frb, ok := partitions[partition]
|
|
||||||
if !ok {
|
|
||||||
frb = new(FetchResponseBlock)
|
|
||||||
partitions[partition] = frb
|
|
||||||
}
|
|
||||||
|
|
||||||
return frb
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeKV(key, value Encoder) ([]byte, []byte) {
|
|
||||||
var kb []byte
|
|
||||||
var vb []byte
|
|
||||||
if key != nil {
|
|
||||||
kb, _ = key.Encode()
|
|
||||||
}
|
|
||||||
if value != nil {
|
|
||||||
vb, _ = value.Encode()
|
|
||||||
}
|
|
||||||
|
|
||||||
return kb, vb
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchResponse) AddMessageWithTimestamp(topic string, partition int32, key, value Encoder, offset int64, timestamp time.Time, version int8) {
|
|
||||||
frb := r.getOrCreateBlock(topic, partition)
|
|
||||||
kb, vb := encodeKV(key, value)
|
|
||||||
if r.LogAppendTime {
|
|
||||||
timestamp = r.Timestamp
|
|
||||||
}
|
|
||||||
msg := &Message{Key: kb, Value: vb, LogAppendTime: r.LogAppendTime, Timestamp: timestamp, Version: version}
|
|
||||||
msgBlock := &MessageBlock{Msg: msg, Offset: offset}
|
|
||||||
if len(frb.RecordsSet) == 0 {
|
|
||||||
records := newLegacyRecords(&MessageSet{})
|
|
||||||
frb.RecordsSet = []*Records{&records}
|
|
||||||
}
|
|
||||||
set := frb.RecordsSet[0].MsgSet
|
|
||||||
set.Messages = append(set.Messages, msgBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchResponse) AddRecordWithTimestamp(topic string, partition int32, key, value Encoder, offset int64, timestamp time.Time) {
|
|
||||||
frb := r.getOrCreateBlock(topic, partition)
|
|
||||||
kb, vb := encodeKV(key, value)
|
|
||||||
if len(frb.RecordsSet) == 0 {
|
|
||||||
records := newDefaultRecords(&RecordBatch{Version: 2, LogAppendTime: r.LogAppendTime, FirstTimestamp: timestamp, MaxTimestamp: r.Timestamp})
|
|
||||||
frb.RecordsSet = []*Records{&records}
|
|
||||||
}
|
|
||||||
batch := frb.RecordsSet[0].RecordBatch
|
|
||||||
rec := &Record{Key: kb, Value: vb, OffsetDelta: offset, TimestampDelta: timestamp.Sub(batch.FirstTimestamp)}
|
|
||||||
batch.addRecord(rec)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRecordBatchWithTimestamp is similar to AddRecordWithTimestamp
|
|
||||||
// But instead of appending 1 record to a batch, it append a new batch containing 1 record to the fetchResponse
|
|
||||||
// Since transaction are handled on batch level (the whole batch is either committed or aborted), use this to test transactions
|
|
||||||
func (r *FetchResponse) AddRecordBatchWithTimestamp(topic string, partition int32, key, value Encoder, offset int64, producerID int64, isTransactional bool, timestamp time.Time) {
|
|
||||||
frb := r.getOrCreateBlock(topic, partition)
|
|
||||||
kb, vb := encodeKV(key, value)
|
|
||||||
|
|
||||||
records := newDefaultRecords(&RecordBatch{Version: 2, LogAppendTime: r.LogAppendTime, FirstTimestamp: timestamp, MaxTimestamp: r.Timestamp})
|
|
||||||
batch := &RecordBatch{
|
|
||||||
Version: 2,
|
|
||||||
LogAppendTime: r.LogAppendTime,
|
|
||||||
FirstTimestamp: timestamp,
|
|
||||||
MaxTimestamp: r.Timestamp,
|
|
||||||
FirstOffset: offset,
|
|
||||||
LastOffsetDelta: 0,
|
|
||||||
ProducerID: producerID,
|
|
||||||
IsTransactional: isTransactional,
|
|
||||||
}
|
|
||||||
rec := &Record{Key: kb, Value: vb, OffsetDelta: 0, TimestampDelta: timestamp.Sub(batch.FirstTimestamp)}
|
|
||||||
batch.addRecord(rec)
|
|
||||||
records.RecordBatch = batch
|
|
||||||
|
|
||||||
frb.RecordsSet = append(frb.RecordsSet, &records)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchResponse) AddControlRecordWithTimestamp(topic string, partition int32, offset int64, producerID int64, recordType ControlRecordType, timestamp time.Time) {
|
|
||||||
frb := r.getOrCreateBlock(topic, partition)
|
|
||||||
|
|
||||||
// batch
|
|
||||||
batch := &RecordBatch{
|
|
||||||
Version: 2,
|
|
||||||
LogAppendTime: r.LogAppendTime,
|
|
||||||
FirstTimestamp: timestamp,
|
|
||||||
MaxTimestamp: r.Timestamp,
|
|
||||||
FirstOffset: offset,
|
|
||||||
LastOffsetDelta: 0,
|
|
||||||
ProducerID: producerID,
|
|
||||||
IsTransactional: true,
|
|
||||||
Control: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// records
|
|
||||||
records := newDefaultRecords(nil)
|
|
||||||
records.RecordBatch = batch
|
|
||||||
|
|
||||||
// record
|
|
||||||
crAbort := ControlRecord{
|
|
||||||
Version: 0,
|
|
||||||
Type: recordType,
|
|
||||||
}
|
|
||||||
crKey := &realEncoder{raw: make([]byte, 4)}
|
|
||||||
crValue := &realEncoder{raw: make([]byte, 6)}
|
|
||||||
crAbort.encode(crKey, crValue)
|
|
||||||
rec := &Record{Key: ByteEncoder(crKey.raw), Value: ByteEncoder(crValue.raw), OffsetDelta: 0, TimestampDelta: timestamp.Sub(batch.FirstTimestamp)}
|
|
||||||
batch.addRecord(rec)
|
|
||||||
|
|
||||||
frb.RecordsSet = append(frb.RecordsSet, &records)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchResponse) AddMessage(topic string, partition int32, key, value Encoder, offset int64) {
|
|
||||||
r.AddMessageWithTimestamp(topic, partition, key, value, offset, time.Time{}, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchResponse) AddRecord(topic string, partition int32, key, value Encoder, offset int64) {
|
|
||||||
r.AddRecordWithTimestamp(topic, partition, key, value, offset, time.Time{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchResponse) AddRecordBatch(topic string, partition int32, key, value Encoder, offset int64, producerID int64, isTransactional bool) {
|
|
||||||
r.AddRecordBatchWithTimestamp(topic, partition, key, value, offset, producerID, isTransactional, time.Time{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchResponse) AddControlRecord(topic string, partition int32, offset int64, producerID int64, recordType ControlRecordType) {
|
|
||||||
// define controlRecord key and value
|
|
||||||
r.AddControlRecordWithTimestamp(topic, partition, offset, producerID, recordType, time.Time{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchResponse) SetLastOffsetDelta(topic string, partition int32, offset int32) {
|
|
||||||
frb := r.getOrCreateBlock(topic, partition)
|
|
||||||
if len(frb.RecordsSet) == 0 {
|
|
||||||
records := newDefaultRecords(&RecordBatch{Version: 2})
|
|
||||||
frb.RecordsSet = []*Records{&records}
|
|
||||||
}
|
|
||||||
batch := frb.RecordsSet[0].RecordBatch
|
|
||||||
batch.LastOffsetDelta = offset
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FetchResponse) SetLastStableOffset(topic string, partition int32, offset int64) {
|
|
||||||
frb := r.getOrCreateBlock(topic, partition)
|
|
||||||
frb.LastStableOffset = offset
|
|
||||||
}
|
|
||||||
-65
@@ -1,65 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
type CoordinatorType int8
|
|
||||||
|
|
||||||
const (
|
|
||||||
CoordinatorGroup CoordinatorType = iota
|
|
||||||
CoordinatorTransaction
|
|
||||||
)
|
|
||||||
|
|
||||||
type FindCoordinatorRequest struct {
|
|
||||||
Version int16
|
|
||||||
CoordinatorKey string
|
|
||||||
CoordinatorType CoordinatorType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FindCoordinatorRequest) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putString(f.CoordinatorKey); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Version >= 1 {
|
|
||||||
pe.putInt8(int8(f.CoordinatorType))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FindCoordinatorRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
if f.CoordinatorKey, err = pd.getString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if version >= 1 {
|
|
||||||
f.Version = version
|
|
||||||
coordinatorType, err := pd.getInt8()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
f.CoordinatorType = CoordinatorType(coordinatorType)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FindCoordinatorRequest) key() int16 {
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FindCoordinatorRequest) version() int16 {
|
|
||||||
return f.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FindCoordinatorRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FindCoordinatorRequest) requiredVersion() KafkaVersion {
|
|
||||||
switch f.Version {
|
|
||||||
case 1:
|
|
||||||
return V0_11_0_0
|
|
||||||
default:
|
|
||||||
return V0_8_2_0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-96
@@ -1,96 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var NoNode = &Broker{id: -1, addr: ":-1"}
|
|
||||||
|
|
||||||
type FindCoordinatorResponse struct {
|
|
||||||
Version int16
|
|
||||||
ThrottleTime time.Duration
|
|
||||||
Err KError
|
|
||||||
ErrMsg *string
|
|
||||||
Coordinator *Broker
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FindCoordinatorResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
if version >= 1 {
|
|
||||||
f.Version = version
|
|
||||||
|
|
||||||
throttleTime, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.ThrottleTime = time.Duration(throttleTime) * time.Millisecond
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.Err = KError(tmp)
|
|
||||||
|
|
||||||
if version >= 1 {
|
|
||||||
if f.ErrMsg, err = pd.getNullableString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
coordinator := new(Broker)
|
|
||||||
// The version is hardcoded to 0, as version 1 of the Broker-decode
|
|
||||||
// contains the rack-field which is not present in the FindCoordinatorResponse.
|
|
||||||
if err := coordinator.decode(pd, 0); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if coordinator.addr == ":0" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
f.Coordinator = coordinator
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FindCoordinatorResponse) encode(pe packetEncoder) error {
|
|
||||||
if f.Version >= 1 {
|
|
||||||
pe.putInt32(int32(f.ThrottleTime / time.Millisecond))
|
|
||||||
}
|
|
||||||
|
|
||||||
pe.putInt16(int16(f.Err))
|
|
||||||
|
|
||||||
if f.Version >= 1 {
|
|
||||||
if err := pe.putNullableString(f.ErrMsg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
coordinator := f.Coordinator
|
|
||||||
if coordinator == nil {
|
|
||||||
coordinator = NoNode
|
|
||||||
}
|
|
||||||
if err := coordinator.encode(pe, 0); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FindCoordinatorResponse) key() int16 {
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FindCoordinatorResponse) version() int16 {
|
|
||||||
return f.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FindCoordinatorResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FindCoordinatorResponse) requiredVersion() KafkaVersion {
|
|
||||||
switch f.Version {
|
|
||||||
case 1:
|
|
||||||
return V0_11_0_0
|
|
||||||
default:
|
|
||||||
return V0_8_2_0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-34
@@ -1,34 +0,0 @@
|
|||||||
module github.com/Shopify/sarama
|
|
||||||
|
|
||||||
go 1.13
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/Shopify/toxiproxy v2.1.4+incompatible
|
|
||||||
github.com/davecgh/go-spew v1.1.1
|
|
||||||
github.com/eapache/go-resiliency v1.2.0
|
|
||||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21
|
|
||||||
github.com/eapache/queue v1.1.0
|
|
||||||
github.com/fortytw2/leaktest v1.3.0
|
|
||||||
github.com/frankban/quicktest v1.7.2 // indirect
|
|
||||||
github.com/golang/snappy v0.0.1 // indirect
|
|
||||||
github.com/google/go-cmp v0.4.0 // indirect
|
|
||||||
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
|
||||||
github.com/jcmturner/gofork v1.0.0 // indirect
|
|
||||||
github.com/klauspost/compress v1.9.8
|
|
||||||
github.com/kr/pretty v0.2.0 // indirect
|
|
||||||
github.com/pierrec/lz4 v2.4.1+incompatible
|
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563
|
|
||||||
github.com/stretchr/testify v1.4.0
|
|
||||||
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c
|
|
||||||
github.com/xdg/stringprep v1.0.0 // indirect
|
|
||||||
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 // indirect
|
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
|
|
||||||
golang.org/x/text v0.3.2 // indirect
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
|
||||||
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
|
|
||||||
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
|
|
||||||
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
|
|
||||||
gopkg.in/jcmturner/gokrb5.v7 v7.5.0
|
|
||||||
gopkg.in/jcmturner/rpc.v1 v1.1.0 // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.2.8 // indirect
|
|
||||||
)
|
|
||||||
-81
@@ -1,81 +0,0 @@
|
|||||||
github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
|
|
||||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q=
|
|
||||||
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
|
||||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
|
|
||||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
|
||||||
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
|
||||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
|
||||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
|
||||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
|
||||||
github.com/frankban/quicktest v1.7.2 h1:2QxQoC1TS09S7fhCPsrvqYdvP1H5M1P1ih5ABm3BTYk=
|
|
||||||
github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
|
|
||||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
|
|
||||||
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
|
||||||
github.com/klauspost/compress v1.9.8 h1:VMAMUUOh+gaxKTMk+zqbjsSjsIcUcL/LF4o63i82QyA=
|
|
||||||
github.com/klauspost/compress v1.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
|
||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/pierrec/lz4 v2.4.1+incompatible h1:mFe7ttWaflA46Mhqh+jUfjp2qTbPYxLB2/OyBppH9dg=
|
|
||||||
github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 h1:dY6ETXrvDG7Sa4vE8ZQG4yqWg6UnOcbqTAahkV813vQ=
|
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk=
|
|
||||||
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
|
|
||||||
github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0=
|
|
||||||
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 h1:+ELyKg6m8UBf0nPFSqD0mi7zUfwPyXo23HNjMnXPz7w=
|
|
||||||
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
|
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
|
|
||||||
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
|
|
||||||
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM=
|
|
||||||
gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
|
|
||||||
gopkg.in/jcmturner/goidentity.v3 v3.0.0 h1:1duIyWiTaYvVx3YX2CYtpJbUFd7/UuPYCfgXtQ3VTbI=
|
|
||||||
gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4=
|
|
||||||
gopkg.in/jcmturner/gokrb5.v7 v7.5.0 h1:a9tsXlIDD9SKxotJMK3niV7rPZAJeX2aD/0yg3qlIrg=
|
|
||||||
gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
|
|
||||||
gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU=
|
|
||||||
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
-257
@@ -1,257 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/asn1"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gopkg.in/jcmturner/gokrb5.v7/asn1tools"
|
|
||||||
"gopkg.in/jcmturner/gokrb5.v7/gssapi"
|
|
||||||
"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
|
|
||||||
"gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
|
|
||||||
"gopkg.in/jcmturner/gokrb5.v7/messages"
|
|
||||||
"gopkg.in/jcmturner/gokrb5.v7/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
TOK_ID_KRB_AP_REQ = 256
|
|
||||||
GSS_API_GENERIC_TAG = 0x60
|
|
||||||
KRB5_USER_AUTH = 1
|
|
||||||
KRB5_KEYTAB_AUTH = 2
|
|
||||||
GSS_API_INITIAL = 1
|
|
||||||
GSS_API_VERIFY = 2
|
|
||||||
GSS_API_FINISH = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
type GSSAPIConfig struct {
|
|
||||||
AuthType int
|
|
||||||
KeyTabPath string
|
|
||||||
KerberosConfigPath string
|
|
||||||
ServiceName string
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
Realm string
|
|
||||||
}
|
|
||||||
|
|
||||||
type GSSAPIKerberosAuth struct {
|
|
||||||
Config *GSSAPIConfig
|
|
||||||
ticket messages.Ticket
|
|
||||||
encKey types.EncryptionKey
|
|
||||||
NewKerberosClientFunc func(config *GSSAPIConfig) (KerberosClient, error)
|
|
||||||
step int
|
|
||||||
}
|
|
||||||
|
|
||||||
type KerberosClient interface {
|
|
||||||
Login() error
|
|
||||||
GetServiceTicket(spn string) (messages.Ticket, types.EncryptionKey, error)
|
|
||||||
Domain() string
|
|
||||||
CName() types.PrincipalName
|
|
||||||
Destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Appends length in big endian before payload, and send it to kafka
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (krbAuth *GSSAPIKerberosAuth) writePackage(broker *Broker, payload []byte) (int, error) {
|
|
||||||
length := len(payload)
|
|
||||||
finalPackage := make([]byte, length+4) //4 byte length header + payload
|
|
||||||
copy(finalPackage[4:], payload)
|
|
||||||
binary.BigEndian.PutUint32(finalPackage, uint32(length))
|
|
||||||
bytes, err := broker.conn.Write(finalPackage)
|
|
||||||
if err != nil {
|
|
||||||
return bytes, err
|
|
||||||
}
|
|
||||||
return bytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Read length (4 bytes) and then read the payload
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (krbAuth *GSSAPIKerberosAuth) readPackage(broker *Broker) ([]byte, int, error) {
|
|
||||||
bytesRead := 0
|
|
||||||
lengthInBytes := make([]byte, 4)
|
|
||||||
bytes, err := io.ReadFull(broker.conn, lengthInBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, bytesRead, err
|
|
||||||
}
|
|
||||||
bytesRead += bytes
|
|
||||||
payloadLength := binary.BigEndian.Uint32(lengthInBytes)
|
|
||||||
payloadBytes := make([]byte, payloadLength) // buffer for read..
|
|
||||||
bytes, err = io.ReadFull(broker.conn, payloadBytes) // read bytes
|
|
||||||
if err != nil {
|
|
||||||
return payloadBytes, bytesRead, err
|
|
||||||
}
|
|
||||||
bytesRead += bytes
|
|
||||||
return payloadBytes, bytesRead, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (krbAuth *GSSAPIKerberosAuth) newAuthenticatorChecksum() []byte {
|
|
||||||
a := make([]byte, 24)
|
|
||||||
flags := []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf}
|
|
||||||
binary.LittleEndian.PutUint32(a[:4], 16)
|
|
||||||
for _, i := range flags {
|
|
||||||
f := binary.LittleEndian.Uint32(a[20:24])
|
|
||||||
f |= uint32(i)
|
|
||||||
binary.LittleEndian.PutUint32(a[20:24], f)
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Construct Kerberos AP_REQ package, conforming to RFC-4120
|
|
||||||
* https://tools.ietf.org/html/rfc4120#page-84
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
func (krbAuth *GSSAPIKerberosAuth) createKrb5Token(
|
|
||||||
domain string, cname types.PrincipalName,
|
|
||||||
ticket messages.Ticket,
|
|
||||||
sessionKey types.EncryptionKey) ([]byte, error) {
|
|
||||||
auth, err := types.NewAuthenticator(domain, cname)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
auth.Cksum = types.Checksum{
|
|
||||||
CksumType: chksumtype.GSSAPI,
|
|
||||||
Checksum: krbAuth.newAuthenticatorChecksum(),
|
|
||||||
}
|
|
||||||
APReq, err := messages.NewAPReq(
|
|
||||||
ticket,
|
|
||||||
sessionKey,
|
|
||||||
auth,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
aprBytes := make([]byte, 2)
|
|
||||||
binary.BigEndian.PutUint16(aprBytes, TOK_ID_KRB_AP_REQ)
|
|
||||||
tb, err := APReq.Marshal()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
aprBytes = append(aprBytes, tb...)
|
|
||||||
return aprBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Append the GSS-API header to the payload, conforming to RFC-2743
|
|
||||||
* Section 3.1, Mechanism-Independent Token Format
|
|
||||||
*
|
|
||||||
* https://tools.ietf.org/html/rfc2743#page-81
|
|
||||||
*
|
|
||||||
* GSSAPIHeader + <specific mechanism payload>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
func (krbAuth *GSSAPIKerberosAuth) appendGSSAPIHeader(payload []byte) ([]byte, error) {
|
|
||||||
oidBytes, err := asn1.Marshal(gssapi.OID(gssapi.OIDKRB5))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tkoLengthBytes := asn1tools.MarshalLengthBytes(len(oidBytes) + len(payload))
|
|
||||||
GSSHeader := append([]byte{GSS_API_GENERIC_TAG}, tkoLengthBytes...)
|
|
||||||
GSSHeader = append(GSSHeader, oidBytes...)
|
|
||||||
GSSPackage := append(GSSHeader, payload...)
|
|
||||||
return GSSPackage, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (krbAuth *GSSAPIKerberosAuth) initSecContext(bytes []byte, kerberosClient KerberosClient) ([]byte, error) {
|
|
||||||
switch krbAuth.step {
|
|
||||||
case GSS_API_INITIAL:
|
|
||||||
aprBytes, err := krbAuth.createKrb5Token(
|
|
||||||
kerberosClient.Domain(),
|
|
||||||
kerberosClient.CName(),
|
|
||||||
krbAuth.ticket,
|
|
||||||
krbAuth.encKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
krbAuth.step = GSS_API_VERIFY
|
|
||||||
return krbAuth.appendGSSAPIHeader(aprBytes)
|
|
||||||
case GSS_API_VERIFY:
|
|
||||||
wrapTokenReq := gssapi.WrapToken{}
|
|
||||||
if err := wrapTokenReq.Unmarshal(bytes, true); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Validate response.
|
|
||||||
isValid, err := wrapTokenReq.Verify(krbAuth.encKey, keyusage.GSSAPI_ACCEPTOR_SEAL)
|
|
||||||
if !isValid {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapTokenResponse, err := gssapi.NewInitiatorWrapToken(wrapTokenReq.Payload, krbAuth.encKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
krbAuth.step = GSS_API_FINISH
|
|
||||||
return wrapTokenResponse.Marshal()
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This does the handshake for authorization */
|
|
||||||
func (krbAuth *GSSAPIKerberosAuth) Authorize(broker *Broker) error {
|
|
||||||
kerberosClient, err := krbAuth.NewKerberosClientFunc(krbAuth.Config)
|
|
||||||
if err != nil {
|
|
||||||
Logger.Printf("Kerberos client error: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = kerberosClient.Login()
|
|
||||||
if err != nil {
|
|
||||||
Logger.Printf("Kerberos client error: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Construct SPN using serviceName and host
|
|
||||||
// SPN format: <SERVICE>/<FQDN>
|
|
||||||
|
|
||||||
host := strings.SplitN(broker.addr, ":", 2)[0] // Strip port part
|
|
||||||
spn := fmt.Sprintf("%s/%s", broker.conf.Net.SASL.GSSAPI.ServiceName, host)
|
|
||||||
|
|
||||||
ticket, encKey, err := kerberosClient.GetServiceTicket(spn)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
Logger.Printf("Error getting Kerberos service ticket : %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
krbAuth.ticket = ticket
|
|
||||||
krbAuth.encKey = encKey
|
|
||||||
krbAuth.step = GSS_API_INITIAL
|
|
||||||
var receivedBytes []byte = nil
|
|
||||||
defer kerberosClient.Destroy()
|
|
||||||
for {
|
|
||||||
packBytes, err := krbAuth.initSecContext(receivedBytes, kerberosClient)
|
|
||||||
if err != nil {
|
|
||||||
Logger.Printf("Error while performing GSSAPI Kerberos Authentication: %s\n", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
requestTime := time.Now()
|
|
||||||
bytesWritten, err := krbAuth.writePackage(broker, packBytes)
|
|
||||||
if err != nil {
|
|
||||||
Logger.Printf("Error while performing GSSAPI Kerberos Authentication: %s\n", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
broker.updateOutgoingCommunicationMetrics(bytesWritten)
|
|
||||||
if krbAuth.step == GSS_API_VERIFY {
|
|
||||||
bytesRead := 0
|
|
||||||
receivedBytes, bytesRead, err = krbAuth.readPackage(broker)
|
|
||||||
requestLatency := time.Since(requestTime)
|
|
||||||
broker.updateIncomingCommunicationMetrics(bytesRead, requestLatency)
|
|
||||||
if err != nil {
|
|
||||||
Logger.Printf("Error while performing GSSAPI Kerberos Authentication: %s\n", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if krbAuth.step == GSS_API_FINISH {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-51
@@ -1,51 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
type HeartbeatRequest struct {
|
|
||||||
GroupId string
|
|
||||||
GenerationId int32
|
|
||||||
MemberId string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *HeartbeatRequest) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putString(r.GroupId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pe.putInt32(r.GenerationId)
|
|
||||||
|
|
||||||
if err := pe.putString(r.MemberId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *HeartbeatRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
if r.GroupId, err = pd.getString(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r.GenerationId, err = pd.getInt32(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r.MemberId, err = pd.getString(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *HeartbeatRequest) key() int16 {
|
|
||||||
return 12
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *HeartbeatRequest) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *HeartbeatRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *HeartbeatRequest) requiredVersion() KafkaVersion {
|
|
||||||
return V0_9_0_0
|
|
||||||
}
|
|
||||||
-36
@@ -1,36 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
type HeartbeatResponse struct {
|
|
||||||
Err KError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *HeartbeatResponse) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt16(int16(r.Err))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *HeartbeatResponse) decode(pd packetDecoder, version int16) error {
|
|
||||||
kerr, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Err = KError(kerr)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *HeartbeatResponse) key() int16 {
|
|
||||||
return 12
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *HeartbeatResponse) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *HeartbeatResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *HeartbeatResponse) requiredVersion() KafkaVersion {
|
|
||||||
return V0_9_0_0
|
|
||||||
}
|
|
||||||
-47
@@ -1,47 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type InitProducerIDRequest struct {
|
|
||||||
TransactionalID *string
|
|
||||||
TransactionTimeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *InitProducerIDRequest) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putNullableString(i.TransactionalID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pe.putInt32(int32(i.TransactionTimeout / time.Millisecond))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *InitProducerIDRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
if i.TransactionalID, err = pd.getNullableString(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
i.TransactionTimeout = time.Duration(timeout) * time.Millisecond
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *InitProducerIDRequest) key() int16 {
|
|
||||||
return 22
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *InitProducerIDRequest) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *InitProducerIDRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *InitProducerIDRequest) requiredVersion() KafkaVersion {
|
|
||||||
return V0_11_0_0
|
|
||||||
}
|
|
||||||
-59
@@ -1,59 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type InitProducerIDResponse struct {
|
|
||||||
ThrottleTime time.Duration
|
|
||||||
Err KError
|
|
||||||
ProducerID int64
|
|
||||||
ProducerEpoch int16
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *InitProducerIDResponse) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt32(int32(i.ThrottleTime / time.Millisecond))
|
|
||||||
pe.putInt16(int16(i.Err))
|
|
||||||
pe.putInt64(i.ProducerID)
|
|
||||||
pe.putInt16(i.ProducerEpoch)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *InitProducerIDResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
throttleTime, err := pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
i.ThrottleTime = time.Duration(throttleTime) * time.Millisecond
|
|
||||||
|
|
||||||
kerr, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
i.Err = KError(kerr)
|
|
||||||
|
|
||||||
if i.ProducerID, err = pd.getInt64(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if i.ProducerEpoch, err = pd.getInt16(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *InitProducerIDResponse) key() int16 {
|
|
||||||
return 22
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *InitProducerIDResponse) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *InitProducerIDResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *InitProducerIDResponse) requiredVersion() KafkaVersion {
|
|
||||||
return V0_11_0_0
|
|
||||||
}
|
|
||||||
-167
@@ -1,167 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
type GroupProtocol struct {
|
|
||||||
Name string
|
|
||||||
Metadata []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *GroupProtocol) decode(pd packetDecoder) (err error) {
|
|
||||||
p.Name, err = pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.Metadata, err = pd.getBytes()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *GroupProtocol) encode(pe packetEncoder) (err error) {
|
|
||||||
if err := pe.putString(p.Name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := pe.putBytes(p.Metadata); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type JoinGroupRequest struct {
|
|
||||||
Version int16
|
|
||||||
GroupId string
|
|
||||||
SessionTimeout int32
|
|
||||||
RebalanceTimeout int32
|
|
||||||
MemberId string
|
|
||||||
ProtocolType string
|
|
||||||
GroupProtocols map[string][]byte // deprecated; use OrderedGroupProtocols
|
|
||||||
OrderedGroupProtocols []*GroupProtocol
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *JoinGroupRequest) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putString(r.GroupId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pe.putInt32(r.SessionTimeout)
|
|
||||||
if r.Version >= 1 {
|
|
||||||
pe.putInt32(r.RebalanceTimeout)
|
|
||||||
}
|
|
||||||
if err := pe.putString(r.MemberId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := pe.putString(r.ProtocolType); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.GroupProtocols) > 0 {
|
|
||||||
if len(r.OrderedGroupProtocols) > 0 {
|
|
||||||
return PacketDecodingError{"cannot specify both GroupProtocols and OrderedGroupProtocols on JoinGroupRequest"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(r.GroupProtocols)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for name, metadata := range r.GroupProtocols {
|
|
||||||
if err := pe.putString(name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := pe.putBytes(metadata); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := pe.putArrayLength(len(r.OrderedGroupProtocols)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, protocol := range r.OrderedGroupProtocols {
|
|
||||||
if err := protocol.encode(pe); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *JoinGroupRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
r.Version = version
|
|
||||||
|
|
||||||
if r.GroupId, err = pd.getString(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.SessionTimeout, err = pd.getInt32(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if version >= 1 {
|
|
||||||
if r.RebalanceTimeout, err = pd.getInt32(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.MemberId, err = pd.getString(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.ProtocolType, err = pd.getString(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if n == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
r.GroupProtocols = make(map[string][]byte)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
protocol := &GroupProtocol{}
|
|
||||||
if err := protocol.decode(pd); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.GroupProtocols[protocol.Name] = protocol.Metadata
|
|
||||||
r.OrderedGroupProtocols = append(r.OrderedGroupProtocols, protocol)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *JoinGroupRequest) key() int16 {
|
|
||||||
return 11
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *JoinGroupRequest) version() int16 {
|
|
||||||
return r.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *JoinGroupRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *JoinGroupRequest) requiredVersion() KafkaVersion {
|
|
||||||
switch r.Version {
|
|
||||||
case 2:
|
|
||||||
return V0_11_0_0
|
|
||||||
case 1:
|
|
||||||
return V0_10_1_0
|
|
||||||
default:
|
|
||||||
return V0_9_0_0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *JoinGroupRequest) AddGroupProtocol(name string, metadata []byte) {
|
|
||||||
r.OrderedGroupProtocols = append(r.OrderedGroupProtocols, &GroupProtocol{
|
|
||||||
Name: name,
|
|
||||||
Metadata: metadata,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *JoinGroupRequest) AddGroupProtocolMetadata(name string, metadata *ConsumerGroupMemberMetadata) error {
|
|
||||||
bin, err := encode(metadata, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.AddGroupProtocol(name, bin)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-139
@@ -1,139 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
type JoinGroupResponse struct {
|
|
||||||
Version int16
|
|
||||||
ThrottleTime int32
|
|
||||||
Err KError
|
|
||||||
GenerationId int32
|
|
||||||
GroupProtocol string
|
|
||||||
LeaderId string
|
|
||||||
MemberId string
|
|
||||||
Members map[string][]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *JoinGroupResponse) GetMembers() (map[string]ConsumerGroupMemberMetadata, error) {
|
|
||||||
members := make(map[string]ConsumerGroupMemberMetadata, len(r.Members))
|
|
||||||
for id, bin := range r.Members {
|
|
||||||
meta := new(ConsumerGroupMemberMetadata)
|
|
||||||
if err := decode(bin, meta); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
members[id] = *meta
|
|
||||||
}
|
|
||||||
return members, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *JoinGroupResponse) encode(pe packetEncoder) error {
|
|
||||||
if r.Version >= 2 {
|
|
||||||
pe.putInt32(r.ThrottleTime)
|
|
||||||
}
|
|
||||||
pe.putInt16(int16(r.Err))
|
|
||||||
pe.putInt32(r.GenerationId)
|
|
||||||
|
|
||||||
if err := pe.putString(r.GroupProtocol); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := pe.putString(r.LeaderId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := pe.putString(r.MemberId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pe.putArrayLength(len(r.Members)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for memberId, memberMetadata := range r.Members {
|
|
||||||
if err := pe.putString(memberId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pe.putBytes(memberMetadata); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *JoinGroupResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
r.Version = version
|
|
||||||
|
|
||||||
if version >= 2 {
|
|
||||||
if r.ThrottleTime, err = pd.getInt32(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kerr, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Err = KError(kerr)
|
|
||||||
|
|
||||||
if r.GenerationId, err = pd.getInt32(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.GroupProtocol, err = pd.getString(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.LeaderId, err = pd.getString(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.MemberId, err = pd.getString(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := pd.getArrayLength()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if n == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Members = make(map[string][]byte)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
memberId, err := pd.getString()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
memberMetadata, err := pd.getBytes()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Members[memberId] = memberMetadata
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *JoinGroupResponse) key() int16 {
|
|
||||||
return 11
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *JoinGroupResponse) version() int16 {
|
|
||||||
return r.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *JoinGroupResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *JoinGroupResponse) requiredVersion() KafkaVersion {
|
|
||||||
switch r.Version {
|
|
||||||
case 2:
|
|
||||||
return V0_11_0_0
|
|
||||||
case 1:
|
|
||||||
return V0_10_1_0
|
|
||||||
default:
|
|
||||||
return V0_9_0_0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-51
@@ -1,51 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
krb5client "gopkg.in/jcmturner/gokrb5.v7/client"
|
|
||||||
krb5config "gopkg.in/jcmturner/gokrb5.v7/config"
|
|
||||||
"gopkg.in/jcmturner/gokrb5.v7/keytab"
|
|
||||||
"gopkg.in/jcmturner/gokrb5.v7/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type KerberosGoKrb5Client struct {
|
|
||||||
krb5client.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *KerberosGoKrb5Client) Domain() string {
|
|
||||||
return c.Credentials.Domain()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *KerberosGoKrb5Client) CName() types.PrincipalName {
|
|
||||||
return c.Credentials.CName()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Create kerberos client used to obtain TGT and TGS tokens
|
|
||||||
* used gokrb5 library, which is a pure go kerberos client with
|
|
||||||
* some GSS-API capabilities, and SPNEGO support. Kafka does not use SPNEGO
|
|
||||||
* it uses pure Kerberos 5 solution (RFC-4121 and RFC-4120).
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
func NewKerberosClient(config *GSSAPIConfig) (KerberosClient, error) {
|
|
||||||
cfg, err := krb5config.Load(config.KerberosConfigPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return createClient(config, cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createClient(config *GSSAPIConfig, cfg *krb5config.Config) (KerberosClient, error) {
|
|
||||||
var client *krb5client.Client
|
|
||||||
if config.AuthType == KRB5_KEYTAB_AUTH {
|
|
||||||
kt, err := keytab.Load(config.KeyTabPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
client = krb5client.NewClientWithKeytab(config.Username, config.Realm, kt, cfg)
|
|
||||||
} else {
|
|
||||||
client = krb5client.NewClientWithPassword(config.Username,
|
|
||||||
config.Realm, config.Password, cfg)
|
|
||||||
}
|
|
||||||
return &KerberosGoKrb5Client{*client}, nil
|
|
||||||
}
|
|
||||||
-44
@@ -1,44 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
type LeaveGroupRequest struct {
|
|
||||||
GroupId string
|
|
||||||
MemberId string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LeaveGroupRequest) encode(pe packetEncoder) error {
|
|
||||||
if err := pe.putString(r.GroupId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := pe.putString(r.MemberId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LeaveGroupRequest) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
if r.GroupId, err = pd.getString(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r.MemberId, err = pd.getString(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LeaveGroupRequest) key() int16 {
|
|
||||||
return 13
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LeaveGroupRequest) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LeaveGroupRequest) headerVersion() int16 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LeaveGroupRequest) requiredVersion() KafkaVersion {
|
|
||||||
return V0_9_0_0
|
|
||||||
}
|
|
||||||
-36
@@ -1,36 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
type LeaveGroupResponse struct {
|
|
||||||
Err KError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LeaveGroupResponse) encode(pe packetEncoder) error {
|
|
||||||
pe.putInt16(int16(r.Err))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LeaveGroupResponse) decode(pd packetDecoder, version int16) (err error) {
|
|
||||||
kerr, err := pd.getInt16()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Err = KError(kerr)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LeaveGroupResponse) key() int16 {
|
|
||||||
return 13
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LeaveGroupResponse) version() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LeaveGroupResponse) headerVersion() int16 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LeaveGroupResponse) requiredVersion() KafkaVersion {
|
|
||||||
return V0_9_0_0
|
|
||||||
}
|
|
||||||
-99
@@ -1,99 +0,0 @@
|
|||||||
package sarama
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LengthField implements the PushEncoder and PushDecoder interfaces for calculating 4-byte lengths.
|
|
||||||
type lengthField struct {
|
|
||||||
startOffset int
|
|
||||||
length int32
|
|
||||||
}
|
|
||||||
|
|
||||||
var lengthFieldPool = sync.Pool{}
|
|
||||||
|
|
||||||
func acquireLengthField() *lengthField {
|
|
||||||
val := lengthFieldPool.Get()
|
|
||||||
if val != nil {
|
|
||||||
return val.(*lengthField)
|
|
||||||
}
|
|
||||||
return &lengthField{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func releaseLengthField(m *lengthField) {
|
|
||||||
lengthFieldPool.Put(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lengthField) decode(pd packetDecoder) error {
|
|
||||||
var err error
|
|
||||||
l.length, err = pd.getInt32()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if l.length > int32(pd.remaining()) {
|
|
||||||
return ErrInsufficientData
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lengthField) saveOffset(in int) {
|
|
||||||
l.startOffset = in
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lengthField) reserveLength() int {
|
|
||||||
return 4
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lengthField) run(curOffset int, buf []byte) error {
|
|
||||||
binary.BigEndian.PutUint32(buf[l.startOffset:], uint32(curOffset-l.startOffset-4))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lengthField) check(curOffset int, buf []byte) error {
|
|
||||||
if int32(curOffset-l.startOffset-4) != l.length {
|
|
||||||
return PacketDecodingError{"length field invalid"}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type varintLengthField struct {
|
|
||||||
startOffset int
|
|
||||||
length int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *varintLengthField) decode(pd packetDecoder) error {
|
|
||||||
var err error
|
|
||||||
l.length, err = pd.getVarint()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *varintLengthField) saveOffset(in int) {
|
|
||||||
l.startOffset = in
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *varintLengthField) adjustLength(currOffset int) int {
|
|
||||||
oldFieldSize := l.reserveLength()
|
|
||||||
l.length = int64(currOffset - l.startOffset - oldFieldSize)
|
|
||||||
|
|
||||||
return l.reserveLength() - oldFieldSize
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *varintLengthField) reserveLength() int {
|
|
||||||
var tmp [binary.MaxVarintLen64]byte
|
|
||||||
return binary.PutVarint(tmp[:], l.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *varintLengthField) run(curOffset int, buf []byte) error {
|
|
||||||
binary.PutVarint(buf[l.startOffset:], l.length)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *varintLengthField) check(curOffset int, buf []byte) error {
|
|
||||||
if int64(curOffset-l.startOffset-l.reserveLength()) != l.length {
|
|
||||||
return PacketDecodingError{"length field invalid"}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user