Skip to content

Commit 5bf428a

Browse files
authored
topicctl get action to fetch controllerid and clusterid (#176)
* topicctl get action to fetch broker controller id * Minor format fixtures * get action controllerid, clusterid and tests * GetControllerID modify generic interface to struct * version bump
1 parent 368dc0a commit 5bf428a

File tree

10 files changed

+254
-1
lines changed

10 files changed

+254
-1
lines changed

cmd/topicctl/subcmd/get.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ func init() {
6060
getCmd.AddCommand(
6161
balanceCmd(),
6262
brokersCmd(),
63+
controllerCmd(),
64+
clusterIDCmd(),
6365
configCmd(),
6466
groupsCmd(),
6567
lagsCmd(),
@@ -134,6 +136,48 @@ func brokersCmd() *cobra.Command {
134136
}
135137
}
136138

139+
func controllerCmd() *cobra.Command {
140+
return &cobra.Command{
141+
Use: "controllerid",
142+
Short: "Displays active controller broker id.",
143+
Args: cobra.NoArgs,
144+
RunE: func(cmd *cobra.Command, args []string) error {
145+
ctx := context.Background()
146+
sess := session.Must(session.NewSession())
147+
148+
adminClient, err := getConfig.shared.getAdminClient(ctx, sess, true)
149+
if err != nil {
150+
return err
151+
}
152+
defer adminClient.Close()
153+
154+
cliRunner := cli.NewCLIRunner(adminClient, log.Infof, !noSpinner)
155+
return cliRunner.GetControllerID(ctx, getConfig.full)
156+
},
157+
}
158+
}
159+
160+
func clusterIDCmd() *cobra.Command {
161+
return &cobra.Command{
162+
Use: "clusterid",
163+
Short: "Displays cluster id.",
164+
Args: cobra.NoArgs,
165+
RunE: func(cmd *cobra.Command, args []string) error {
166+
ctx := context.Background()
167+
sess := session.Must(session.NewSession())
168+
169+
adminClient, err := getConfig.shared.getAdminClient(ctx, sess, true)
170+
if err != nil {
171+
return err
172+
}
173+
defer adminClient.Close()
174+
175+
cliRunner := cli.NewCLIRunner(adminClient, log.Infof, !noSpinner)
176+
return cliRunner.GetClusterID(ctx, getConfig.full)
177+
},
178+
}
179+
}
180+
137181
func configCmd() *cobra.Command {
138182
return &cobra.Command{
139183
Use: "config [broker or topic]",

pkg/admin/brokerclient.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,19 @@ func (c *BrokerAdminClient) GetBrokers(ctx context.Context, ids []int) (
232232
return brokerInfos, nil
233233
}
234234

235+
// GetControllerID gets ID of the active controller broker
236+
func (c *BrokerAdminClient) GetControllerID(ctx context.Context) (
237+
int,
238+
error,
239+
) {
240+
metadataResp, err := c.getMetadata(ctx, nil)
241+
if err != nil {
242+
return -1, err
243+
}
244+
245+
return metadataResp.Controller.ID, nil
246+
}
247+
235248
// GetBrokerIDs get the IDs of all brokers in the cluster.
236249
func (c *BrokerAdminClient) GetBrokerIDs(ctx context.Context) ([]int, error) {
237250
resp, err := c.getMetadata(ctx, nil)

pkg/admin/brokerclient_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,37 @@ import (
1313
"github.com/stretchr/testify/require"
1414
)
1515

16+
func TestBrokerClientControllerID(t *testing.T) {
17+
if !util.CanTestBrokerAdmin() {
18+
t.Skip("Skipping because KAFKA_TOPICS_TEST_BROKER_ADMIN is not set")
19+
}
20+
21+
ctx := context.Background()
22+
client, err := NewBrokerAdminClient(
23+
ctx,
24+
BrokerAdminClientConfig{
25+
ConnectorConfig: ConnectorConfig{
26+
BrokerAddr: util.TestKafkaAddr(),
27+
},
28+
},
29+
)
30+
require.NoError(t, err)
31+
32+
brokerIDs, err := client.GetBrokerIDs(ctx)
33+
require.NoError(t, err)
34+
assert.Equal(
35+
t,
36+
[]int{1, 2, 3, 4, 5, 6},
37+
brokerIDs,
38+
)
39+
40+
controllerID, err := client.GetControllerID(ctx)
41+
require.NoError(t, err)
42+
assert.Condition(t, func() bool {
43+
return controllerID >= 1 && controllerID <= 6
44+
}, fmt.Sprintf("Received %d, Broker Controller ID should be between 1 and 6.", controllerID))
45+
}
46+
1647
func TestBrokerClientGetClusterID(t *testing.T) {
1748
if !util.CanTestBrokerAdmin() {
1849
t.Skip("Skipping because KAFKA_TOPICS_TEST_BROKER_ADMIN is not set")

pkg/admin/client.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ type Client interface {
1515
// GetBrokers gets information about all brokers in the cluster.
1616
GetBrokers(ctx context.Context, ids []int) ([]BrokerInfo, error)
1717

18+
// GetControllerID get the active controller broker ID in the cluster.
19+
GetControllerID(ctx context.Context) (int, error)
20+
1821
// GetBrokerIDs get the IDs of all brokers in the cluster.
1922
GetBrokerIDs(ctx context.Context) ([]int, error)
2023

pkg/admin/format.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,64 @@ func FormatBrokers(brokers []BrokerInfo, full bool) string {
108108
return string(bytes.TrimRight(buf.Bytes(), "\n"))
109109
}
110110

111+
// FormatControllerID creates a pretty table for controller broker.
112+
func FormatControllerID(brokerID int) string {
113+
buf := &bytes.Buffer{}
114+
table := tablewriter.NewWriter(buf)
115+
headers := []string{"Active Controller"}
116+
table.SetHeader(headers)
117+
118+
table.SetColumnAlignment(
119+
[]int{
120+
tablewriter.ALIGN_LEFT,
121+
},
122+
)
123+
table.SetBorders(
124+
tablewriter.Border{
125+
Left: false,
126+
Top: true,
127+
Right: false,
128+
Bottom: true,
129+
},
130+
)
131+
132+
table.Append([]string{
133+
fmt.Sprintf("%d", brokerID),
134+
})
135+
136+
table.Render()
137+
return string(bytes.TrimRight(buf.Bytes(), "\n"))
138+
}
139+
140+
// FormatClusterID creates a pretty table for cluster ID.
141+
func FormatClusterID(clusterID string) string {
142+
buf := &bytes.Buffer{}
143+
table := tablewriter.NewWriter(buf)
144+
headers := []string{"Kafka Cluster ID"}
145+
table.SetHeader(headers)
146+
147+
table.SetColumnAlignment(
148+
[]int{
149+
tablewriter.ALIGN_LEFT,
150+
},
151+
)
152+
table.SetBorders(
153+
tablewriter.Border{
154+
Left: false,
155+
Top: true,
156+
Right: false,
157+
Bottom: true,
158+
},
159+
)
160+
161+
table.Append([]string{
162+
clusterID,
163+
})
164+
165+
table.Render()
166+
return string(bytes.TrimRight(buf.Bytes(), "\n"))
167+
}
168+
111169
// FormatBrokerReplicas creates a pretty table that shows how many replicas are in each
112170
// position (i.e., leader, second, third) by broker across all topics. Useful for showing
113171
// total-topic balance.

pkg/admin/types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,12 @@ type zkClusterID struct {
356356
ID string `json:"id"`
357357
}
358358

359+
type zkControllerInfo struct {
360+
Version int `json:"version"`
361+
BrokerID int `json:"brokerid"`
362+
Timestamp string `json:"timestamp"`
363+
}
364+
359365
type zkBrokerInfo struct {
360366
Endpoints []string `json:"endpoints"`
361367
Host string `json:"host"`

pkg/admin/zkclient.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const (
2424
assignmentPath = "/admin/reassign_partitions"
2525
electionPath = "/admin/preferred_replica_election"
2626
brokersPath = "/brokers/ids"
27+
controllerPath = "/controller"
2728
topicsPath = "/brokers/topics"
2829
clusterIDPath = "/cluster/id"
2930
brokerConfigsPath = "/config/brokers"
@@ -293,6 +294,28 @@ func (c *ZKAdminClient) GetBrokerIDs(ctx context.Context) ([]int, error) {
293294
return brokerIDs, nil
294295
}
295296

297+
// GetControllerID gets ID of the active controller broker
298+
func (c *ZKAdminClient) GetControllerID(
299+
ctx context.Context,
300+
) (int, error) {
301+
zkControllerInfo := zkControllerInfo{}
302+
zkControllerPath := c.zNode(controllerPath)
303+
304+
_, err := c.zkClient.GetJSON(
305+
ctx,
306+
zkControllerPath,
307+
&zkControllerInfo,
308+
)
309+
if err != nil {
310+
return -1, fmt.Errorf("Error getting zookeeper path %s: %+v",
311+
zkControllerPath,
312+
err,
313+
)
314+
}
315+
316+
return zkControllerInfo.BrokerID, nil
317+
}
318+
296319
// GetConnector returns the Connector instance associated with this client.
297320
func (c *ZKAdminClient) GetConnector() *Connector {
298321
return c.Connector

pkg/admin/zkclient_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,53 @@ import (
1515
"github.com/stretchr/testify/require"
1616
)
1717

18+
func TestZkClientControllerID(t *testing.T) {
19+
zkConn, _, err := szk.Connect(
20+
[]string{util.TestZKAddr()},
21+
5*time.Second,
22+
)
23+
require.NoError(t, err)
24+
require.NotNil(t, zkConn)
25+
defer zkConn.Close()
26+
27+
clusterName := testClusterID("clusterID")
28+
zk.CreateNodes(
29+
t,
30+
zkConn,
31+
[]zk.PathTuple{
32+
{
33+
Path: fmt.Sprintf("/%s", clusterName),
34+
Obj: nil,
35+
},
36+
{
37+
Path: fmt.Sprintf("/%s/controller", clusterName),
38+
Obj: map[string]interface{}{
39+
"version": 1,
40+
"brokerid": 3,
41+
"timestamp": "1589603217000",
42+
},
43+
},
44+
},
45+
)
46+
47+
ctx := context.Background()
48+
adminClient, err := NewZKAdminClient(
49+
ctx,
50+
ZKAdminClientConfig{
51+
ZKAddrs: []string{util.TestZKAddr()},
52+
ZKPrefix: clusterName,
53+
BootstrapAddrs: []string{util.TestKafkaAddr()},
54+
ReadOnly: true,
55+
},
56+
)
57+
require.NoError(t, err)
58+
defer adminClient.Close()
59+
60+
controllerID, err := adminClient.GetControllerID(ctx)
61+
assert.NoError(t, err)
62+
assert.Equal(t, 3, controllerID)
63+
}
64+
1865
func TestZkClientGetClusterID(t *testing.T) {
1966
zkConn, _, err := szk.Connect(
2067
[]string{util.TestZKAddr()},

pkg/cli/cli.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,34 @@ func (c *CLIRunner) GetBrokers(ctx context.Context, full bool) error {
7979
return nil
8080
}
8181

82+
// Get active controller broker ID
83+
func (c *CLIRunner) GetControllerID(ctx context.Context, full bool) error {
84+
c.startSpinner()
85+
86+
brokerID, err := c.adminClient.GetControllerID(ctx)
87+
c.stopSpinner()
88+
if err != nil {
89+
return err
90+
}
91+
92+
c.printer("Broker ID:\n%s", admin.FormatControllerID(brokerID))
93+
return nil
94+
}
95+
96+
// Get cluster ID
97+
func (c *CLIRunner) GetClusterID(ctx context.Context, full bool) error {
98+
c.startSpinner()
99+
100+
clusterID, err := c.adminClient.GetClusterID(ctx)
101+
c.stopSpinner()
102+
if err != nil {
103+
return err
104+
}
105+
106+
c.printer("Cluster ID:\n%s", admin.FormatClusterID(clusterID))
107+
return nil
108+
}
109+
82110
// ApplyTopic does an apply run according to the spec in the argument config.
83111
func (c *CLIRunner) ApplyTopic(
84112
ctx context.Context,

pkg/version/version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
package version
22

33
// Version is the current topicctl version.
4-
const Version = "1.13.0"
4+
const Version = "1.14.0"

0 commit comments

Comments
 (0)