You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

255 lines
6.5 KiB

package kazoo
import (
"encoding/json"
"fmt"
"strconv"
"github.com/samuel/go-zookeeper/zk"
)
// Topic interacts with Kafka's topic metadata in Zookeeper.
type Topic struct {
Name string
kz *Kazoo
}
// TopicList is a type that implements the sortable interface for a list of Topic instances.
type TopicList []*Topic
// Partition interacts with Kafka's partition metadata in Zookeeper.
type Partition struct {
topic *Topic
ID int32
Replicas []int32
}
// PartitionList is a type that implements the sortable interface for a list of Partition instances
type PartitionList []*Partition
// Topics returns a list of all registered Kafka topics.
func (kz *Kazoo) Topics() (TopicList, error) {
root := fmt.Sprintf("%s/brokers/topics", kz.conf.Chroot)
children, _, err := kz.conn.Children(root)
if err != nil {
return nil, err
}
result := make(TopicList, 0, len(children))
for _, name := range children {
result = append(result, kz.Topic(name))
}
return result, nil
}
// WatchTopics returns a list of all registered Kafka topics, and
// watches that list for changes.
func (kz *Kazoo) WatchTopics() (TopicList, <-chan zk.Event, error) {
root := fmt.Sprintf("%s/brokers/topics", kz.conf.Chroot)
children, _, c, err := kz.conn.ChildrenW(root)
if err != nil {
return nil, nil, err
}
result := make(TopicList, 0, len(children))
for _, name := range children {
result = append(result, kz.Topic(name))
}
return result, c, nil
}
// Topic returns a Topic instance for a given topic name
func (kz *Kazoo) Topic(topic string) *Topic {
return &Topic{Name: topic, kz: kz}
}
// Exists returns true if the topic exists on the Kafka cluster.
func (t *Topic) Exists() (bool, error) {
return t.kz.exists(fmt.Sprintf("%s/brokers/topics/%s", t.kz.conf.Chroot, t.Name))
}
// Partitions returns a list of all partitions for the topic.
func (t *Topic) Partitions() (PartitionList, error) {
node := fmt.Sprintf("%s/brokers/topics/%s", t.kz.conf.Chroot, t.Name)
value, _, err := t.kz.conn.Get(node)
if err != nil {
return nil, err
}
return t.parsePartitions(value)
}
// WatchPartitions returns a list of all partitions for the topic, and watches the topic for changes.
func (t *Topic) WatchPartitions() (PartitionList, <-chan zk.Event, error) {
node := fmt.Sprintf("%s/brokers/topics/%s", t.kz.conf.Chroot, t.Name)
value, _, c, err := t.kz.conn.GetW(node)
if err != nil {
return nil, nil, err
}
list, err := t.parsePartitions(value)
return list, c, err
}
// parsePartitions pases the JSON representation of the partitions
// that is stored as data on the topic node in Zookeeper.
func (t *Topic) parsePartitions(value []byte) (PartitionList, error) {
type topicMetadata struct {
Partitions map[string][]int32 `json:"partitions"`
}
var tm topicMetadata
if err := json.Unmarshal(value, &tm); err != nil {
return nil, err
}
result := make(PartitionList, len(tm.Partitions))
for partitionNumber, replicas := range tm.Partitions {
partitionID, err := strconv.ParseInt(partitionNumber, 10, 32)
if err != nil {
return nil, err
}
replicaIDs := make([]int32, 0, len(replicas))
for _, r := range replicas {
replicaIDs = append(replicaIDs, int32(r))
}
result[partitionID] = t.Partition(int32(partitionID), replicaIDs)
}
return result, nil
}
// Partition returns a Partition instance for the topic.
func (t *Topic) Partition(id int32, replicas []int32) *Partition {
return &Partition{ID: id, Replicas: replicas, topic: t}
}
// Config returns topic-level configuration settings as a map.
func (t *Topic) Config() (map[string]string, error) {
value, _, err := t.kz.conn.Get(fmt.Sprintf("%s/config/topics/%s", t.kz.conf.Chroot, t.Name))
if err != nil {
return nil, err
}
var topicConfig struct {
ConfigMap map[string]string `json:"config"`
}
if err := json.Unmarshal(value, &topicConfig); err != nil {
return nil, err
}
return topicConfig.ConfigMap, nil
}
// Topic returns the Topic of this partition.
func (p *Partition) Topic() *Topic {
return p.topic
}
// Key returns a unique identifier for the partition, using the form "topic/partition".
func (p *Partition) Key() string {
return fmt.Sprintf("%s/%d", p.topic.Name, p.ID)
}
// PreferredReplica returns the preferred replica for this partition.
func (p *Partition) PreferredReplica() int32 {
if len(p.Replicas) > 0 {
return p.Replicas[0]
} else {
return -1
}
}
// Leader returns the broker ID of the broker that is currently the leader for the partition.
func (p *Partition) Leader() (int32, error) {
if state, err := p.state(); err != nil {
return -1, err
} else {
return state.Leader, nil
}
}
// ISR returns the broker IDs of the current in-sync replica set for the partition
func (p *Partition) ISR() ([]int32, error) {
if state, err := p.state(); err != nil {
return nil, err
} else {
return state.ISR, nil
}
}
func (p *Partition) UnderReplicated() (bool, error) {
if state, err := p.state(); err != nil {
return false, err
} else {
return len(state.ISR) < len(p.Replicas), nil
}
}
func (p *Partition) UsesPreferredReplica() (bool, error) {
if state, err := p.state(); err != nil {
return false, err
} else {
return len(state.ISR) > 0 && state.ISR[0] == p.Replicas[0], nil
}
}
// partitionState represents the partition state as it is stored as JSON
// in Zookeeper on the partition's state node.
type partitionState struct {
Leader int32 `json:"leader"`
ISR []int32 `json:"isr"`
}
// state retrieves and parses the partition State
func (p *Partition) state() (partitionState, error) {
var state partitionState
node := fmt.Sprintf("%s/brokers/topics/%s/partitions/%d/state", p.topic.kz.conf.Chroot, p.topic.Name, p.ID)
value, _, err := p.topic.kz.conn.Get(node)
if err != nil {
return state, err
}
if err := json.Unmarshal(value, &state); err != nil {
return state, err
}
return state, nil
}
// Find returns the topic with the given name if it exists in the topic list,
// and will return `nil` otherwise.
func (tl TopicList) Find(name string) *Topic {
for _, topic := range tl {
if topic.Name == name {
return topic
}
}
return nil
}
func (tl TopicList) Len() int {
return len(tl)
}
func (tl TopicList) Less(i, j int) bool {
return tl[i].Name < tl[j].Name
}
func (tl TopicList) Swap(i, j int) {
tl[i], tl[j] = tl[j], tl[i]
}
func (pl PartitionList) Len() int {
return len(pl)
}
func (pl PartitionList) Less(i, j int) bool {
return pl[i].topic.Name < pl[j].topic.Name || (pl[i].topic.Name == pl[j].topic.Name && pl[i].ID < pl[j].ID)
}
func (pl PartitionList) Swap(i, j int) {
pl[i], pl[j] = pl[j], pl[i]
}