Skip to content

Commit 8d4f395

Browse files
committed
* rewrite JSON printer
* extend e2e test Signed-off-by: Kirill Trifonov <[email protected]>
1 parent 37eaafa commit 8d4f395

File tree

2 files changed

+161
-74
lines changed

2 files changed

+161
-74
lines changed

etcdctl/ctlv3/command/printer_json.go

+116-53
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@ package command
1717
import (
1818
"encoding/json"
1919
"fmt"
20-
"os"
21-
"strconv"
22-
"strings"
2320

24-
clientv3 "go.etcd.io/etcd/client/v3"
21+
pb "go.etcd.io/etcd/api/v3/etcdserverpb"
22+
v3 "go.etcd.io/etcd/client/v3"
2523
)
2624

2725
type jsonPrinter struct {
@@ -36,65 +34,130 @@ func newJSONPrinter(isHex bool) printer {
3634
}
3735
}
3836

39-
func (p *jsonPrinter) EndpointHealth(r []epHealth) { printJSON(r) }
40-
func (p *jsonPrinter) EndpointStatus(r []epStatus) { printJSON(r) }
41-
func (p *jsonPrinter) EndpointHashKV(r []epHashKV) { printJSON(r) }
37+
type (
38+
HexResponseHeader pb.ResponseHeader
39+
HexMember pb.Member
40+
)
4241

43-
func (p *jsonPrinter) MemberList(r clientv3.MemberListResponse) {
44-
if p.isHex {
45-
printMemberListWithHexJSON(r)
46-
} else {
47-
printJSON(r)
48-
}
42+
func (h *HexResponseHeader) MarshalJSON() ([]byte, error) {
43+
type Alias pb.ResponseHeader
44+
45+
return json.Marshal(&struct {
46+
ClusterID string `json:"cluster_id"`
47+
MemberID string `json:"member_id"`
48+
Alias
49+
}{
50+
ClusterID: fmt.Sprintf("%x", h.ClusterId),
51+
MemberID: fmt.Sprintf("%x", h.MemberId),
52+
Alias: (Alias)(*h),
53+
})
54+
}
55+
56+
func (m *HexMember) MarshalJSON() ([]byte, error) {
57+
type Alias pb.Member
58+
59+
return json.Marshal(&struct {
60+
ID string `json:"ID"`
61+
Alias
62+
}{
63+
ID: fmt.Sprintf("%x", m.ID),
64+
Alias: (Alias)(*m),
65+
})
4966
}
5067

68+
func (p *jsonPrinter) MemberAdd(r v3.MemberAddResponse) { p.printJSON(r) }
69+
func (p *jsonPrinter) MemberList(r v3.MemberListResponse) { p.printJSON(r) }
70+
func (p *jsonPrinter) MemberPromote(id uint64, r v3.MemberPromoteResponse) { p.printJSON(r) }
71+
func (p *jsonPrinter) MemberRemove(id uint64, r v3.MemberRemoveResponse) { p.printJSON(r) }
72+
func (p *jsonPrinter) MemberUpdate(id uint64, r v3.MemberUpdateResponse) { p.printJSON(r) }
73+
5174
func printJSON(v any) {
52-
b, err := json.Marshal(v)
53-
if err != nil {
54-
fmt.Fprintf(os.Stderr, "%v\n", err)
55-
return
56-
}
75+
b, _ := json.Marshal(v)
5776
fmt.Println(string(b))
5877
}
5978

60-
func printMemberListWithHexJSON(r clientv3.MemberListResponse) {
61-
var buffer strings.Builder
62-
var b []byte
63-
buffer.WriteString("{\"header\":{\"cluster_id\":\"")
64-
b = strconv.AppendUint(nil, r.Header.ClusterId, 16)
65-
buffer.Write(b)
66-
buffer.WriteString("\",\"member_id\":\"")
67-
b = strconv.AppendUint(nil, r.Header.MemberId, 16)
68-
buffer.Write(b)
69-
buffer.WriteString("\",\"raft_term\":")
70-
b = strconv.AppendUint(nil, r.Header.RaftTerm, 10)
71-
buffer.Write(b)
72-
buffer.WriteByte('}')
73-
for i := 0; i < len(r.Members); i++ {
74-
if i == 0 {
75-
buffer.WriteString(",\"members\":[{\"ID\":\"")
76-
} else {
77-
buffer.WriteString(",{\"ID\":\"")
79+
func (p *jsonPrinter) printJSON(v any) {
80+
var data any
81+
82+
if !p.isHex {
83+
printJSON(v)
84+
return
85+
}
86+
87+
switch r := v.(type) {
88+
case v3.MemberAddResponse:
89+
type Alias v3.MemberAddResponse
90+
91+
data = &struct {
92+
Header *HexResponseHeader `json:"header"`
93+
Member *HexMember `json:"member"`
94+
Members []*HexMember `json:"members"`
95+
*Alias
96+
}{
97+
Header: (*HexResponseHeader)(r.Header),
98+
Member: (*HexMember)(r.Member),
99+
Members: toHexMembers(r.Members),
100+
Alias: (*Alias)(&r),
78101
}
79-
b = strconv.AppendUint(nil, r.Members[i].ID, 16)
80-
buffer.Write(b)
81-
buffer.WriteString("\",\"name\":\"" + r.Members[i].Name + "\"," + "\"peerURLs\":")
82-
b, err := json.Marshal(r.Members[i].PeerURLs)
83-
if err != nil {
84-
return
102+
case v3.MemberListResponse:
103+
type Alias v3.MemberListResponse
104+
105+
data = &struct {
106+
Header *HexResponseHeader `json:"header"`
107+
Members []*HexMember `json:"members"`
108+
*Alias
109+
}{
110+
Header: (*HexResponseHeader)(r.Header),
111+
Members: toHexMembers(r.Members),
112+
Alias: (*Alias)(&r),
85113
}
86-
buffer.Write(b)
87-
buffer.WriteString(",\"clientURLs\":")
88-
b, err = json.Marshal(r.Members[i].ClientURLs)
89-
if err != nil {
90-
return
114+
case v3.MemberPromoteResponse:
115+
type Alias v3.MemberPromoteResponse
116+
117+
data = &struct {
118+
Header *HexResponseHeader `json:"header"`
119+
Members []*HexMember `json:"members"`
120+
*Alias
121+
}{
122+
Header: (*HexResponseHeader)(r.Header),
123+
Members: toHexMembers(r.Members),
124+
Alias: (*Alias)(&r),
91125
}
92-
buffer.Write(b)
93-
buffer.WriteByte('}')
94-
if i == len(r.Members)-1 {
95-
buffer.WriteString("]")
126+
case v3.MemberRemoveResponse:
127+
type Alias v3.MemberRemoveResponse
128+
129+
data = &struct {
130+
Header *HexResponseHeader `json:"header"`
131+
Members []*HexMember `json:"members"`
132+
*Alias
133+
}{
134+
Header: (*HexResponseHeader)(r.Header),
135+
Members: toHexMembers(r.Members),
136+
Alias: (*Alias)(&r),
96137
}
138+
case v3.MemberUpdateResponse:
139+
type Alias v3.MemberUpdateResponse
140+
141+
data = &struct {
142+
Header *HexResponseHeader `json:"header"`
143+
Members []*HexMember `json:"members"`
144+
*Alias
145+
}{
146+
Header: (*HexResponseHeader)(r.Header),
147+
Members: toHexMembers(r.Members),
148+
Alias: (*Alias)(&r),
149+
}
150+
default:
151+
data = v
152+
}
153+
154+
printJSON(data)
155+
}
156+
157+
func toHexMembers(members []*pb.Member) []*HexMember {
158+
hexMembers := make([]*HexMember, len(members))
159+
for i, member := range members {
160+
hexMembers[i] = (*HexMember)(member)
97161
}
98-
buffer.WriteString("}")
99-
fmt.Println(buffer.String())
162+
return hexMembers
100163
}

tests/e2e/ctl_v3_member_test.go

+45-21
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"fmt"
2121
"io"
2222
"reflect"
23+
"strconv"
2324
"strings"
2425
"sync"
2526
"testing"
@@ -207,7 +208,9 @@ func memberListWithHexTest(cx ctlCtx) {
207208
if err != nil {
208209
cx.t.Fatalf("getMemberList error (%v)", err)
209210
}
210-
211+
if len(resp.Members) == 0 {
212+
cx.t.Fatal("member number is 0")
213+
}
211214
cmdArgs := append(cx.PrefixArgs(), "--write-out", "json", "--hex", "member", "list")
212215

213216
proc, err := e2e.SpawnCmd(cmdArgs, cx.envMap)
@@ -222,33 +225,54 @@ func memberListWithHexTest(cx ctlCtx) {
222225
if err = proc.Close(); err != nil {
223226
cx.t.Fatalf("memberListWithHexTest error (%v)", err)
224227
}
225-
hexResp := etcdserverpb.MemberListResponse{}
226-
dec := json.NewDecoder(strings.NewReader(txt))
227-
if err := dec.Decode(&hexResp); errors.Is(err, io.EOF) {
228-
cx.t.Fatalf("memberListWithHexTest error (%v)", err)
229-
}
230-
num := len(resp.Members)
231-
hexNum := len(hexResp.Members)
232-
if num != hexNum {
233-
cx.t.Fatalf("member number,expected %d,got %d", num, hexNum)
228+
229+
decoder := json.NewDecoder(strings.NewReader(txt))
230+
hexResponse := struct {
231+
Header map[string]any `json:"header"`
232+
Members []map[string]any `json:"members"`
233+
}{}
234+
if decodeErr := decoder.Decode(&hexResponse); errors.Is(decodeErr, io.EOF) {
235+
cx.t.Fatalf("memberListWithHexTest error (%v)", decodeErr)
234236
}
235-
if num == 0 {
236-
cx.t.Fatal("member number is 0")
237+
238+
if len(resp.Members) != len(hexResponse.Members) {
239+
cx.t.Fatalf("member number, expected %d,got %d", len(resp.Members), len(hexResponse.Members))
237240
}
238241

239-
if resp.Header.RaftTerm != hexResp.Header.RaftTerm {
240-
cx.t.Fatalf("Unexpected raft_term, expected %d, got %d", resp.Header.RaftTerm, hexResp.Header.RaftTerm)
242+
if clusterID, _ := strconv.ParseUint(hexResponse.Header["cluster_id"].(string), 16, 64); resp.Header.ClusterId != clusterID {
243+
cx.t.Fatalf("Unexpected Cluster ID in response header: expected %x, got %x", resp.Header.ClusterId, clusterID)
244+
}
245+
if memberID, _ := strconv.ParseUint(hexResponse.Header["member_id"].(string), 16, 64); resp.Header.MemberId != memberID {
246+
cx.t.Fatalf("Unexpected Member ID in response header: expected %x, got %x", resp.Header.MemberId, memberID)
241247
}
248+
if raftTerm := uint64(hexResponse.Header["raft_term"].(float64)); resp.Header.RaftTerm != raftTerm {
249+
cx.t.Fatalf("Unexpected Raft Term in response header: expected %d, got %d", resp.Header.RaftTerm, raftTerm)
250+
}
251+
252+
for i := 0; i < len(resp.Members); i++ {
253+
if id, _ := strconv.ParseUint(hexResponse.Members[i]["ID"].(string), 16, 64); resp.Members[i].ID != id {
254+
cx.t.Fatalf("Unexpected Member ID: expected %x, got %x", resp.Members[i].ID, id)
255+
}
256+
if name := hexResponse.Members[i]["name"]; resp.Members[i].Name != name {
257+
cx.t.Fatalf("Unexpected Member Name: expected %s, got %s", resp.Members[i].Name, name)
258+
}
242259

243-
for i := 0; i < num; i++ {
244-
if resp.Members[i].Name != hexResp.Members[i].Name {
245-
cx.t.Fatalf("Unexpected member name,expected %v, got %v", resp.Members[i].Name, hexResp.Members[i].Name)
260+
urls := hexResponse.Members[i]["peerURLs"].([]any)
261+
peerURLs := make([]string, len(urls))
262+
for j, url := range urls {
263+
peerURLs[j] = url.(string)
246264
}
247-
if !reflect.DeepEqual(resp.Members[i].PeerURLs, hexResp.Members[i].PeerURLs) {
248-
cx.t.Fatalf("Unexpected member peerURLs, expected %v, got %v", resp.Members[i].PeerURLs, hexResp.Members[i].PeerURLs)
265+
if !reflect.DeepEqual(resp.Members[i].PeerURLs, peerURLs) {
266+
cx.t.Fatalf("Unexpected Member peerURLs: expected %v, got %v", resp.Members[i].ClientURLs, peerURLs)
267+
}
268+
269+
urls = hexResponse.Members[i]["clientURLs"].([]any)
270+
clientURLs := make([]string, len(urls))
271+
for j, url := range urls {
272+
clientURLs[j] = url.(string)
249273
}
250-
if !reflect.DeepEqual(resp.Members[i].ClientURLs, hexResp.Members[i].ClientURLs) {
251-
cx.t.Fatalf("Unexpected member clientURLs, expected %v, got %v", resp.Members[i].ClientURLs, hexResp.Members[i].ClientURLs)
274+
if !reflect.DeepEqual(resp.Members[i].ClientURLs, clientURLs) {
275+
cx.t.Fatalf("Unexpected Member clientURLs: expected %v, got %v", resp.Members[i].ClientURLs, clientURLs)
252276
}
253277
}
254278
}

0 commit comments

Comments
 (0)