Skip to content

Commit 13c7989

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

File tree

2 files changed

+218
-74
lines changed

2 files changed

+218
-74
lines changed

etcdctl/ctlv3/command/printer_json.go

+173-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,187 @@ 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+
HexStatusResponse pb.StatusResponse
39+
HexHashKVResponse pb.HashKVResponse
40+
HexResponseHeader pb.ResponseHeader
41+
HexMember pb.Member
42+
)
4243

43-
func (p *jsonPrinter) MemberList(r clientv3.MemberListResponse) {
44-
if p.isHex {
45-
printMemberListWithHexJSON(r)
46-
} else {
47-
printJSON(r)
48-
}
44+
func (sr *HexStatusResponse) MarshalJSON() ([]byte, error) {
45+
type Alias pb.StatusResponse
46+
47+
return json.Marshal(&struct {
48+
Header HexResponseHeader `json:"header"`
49+
Leader string `json:"leader"`
50+
Alias
51+
}{
52+
Header: (HexResponseHeader)(*sr.Header),
53+
Leader: fmt.Sprintf("%x", sr.Leader),
54+
Alias: (Alias)(*sr),
55+
})
56+
}
57+
58+
func (hr *HexHashKVResponse) MarshalJSON() ([]byte, error) {
59+
type Alias pb.HashKVResponse
60+
61+
return json.Marshal(&struct {
62+
Header HexResponseHeader `json:"header"`
63+
Alias
64+
}{
65+
Header: (HexResponseHeader)(*hr.Header),
66+
Alias: (Alias)(*hr),
67+
})
68+
}
69+
70+
func (h *HexResponseHeader) MarshalJSON() ([]byte, error) {
71+
type Alias pb.ResponseHeader
72+
73+
return json.Marshal(&struct {
74+
ClusterID string `json:"cluster_id"`
75+
MemberID string `json:"member_id"`
76+
Alias
77+
}{
78+
ClusterID: fmt.Sprintf("%x", h.ClusterId),
79+
MemberID: fmt.Sprintf("%x", h.MemberId),
80+
Alias: (Alias)(*h),
81+
})
4982
}
5083

84+
func (m *HexMember) MarshalJSON() ([]byte, error) {
85+
type Alias pb.Member
86+
87+
return json.Marshal(&struct {
88+
ID string `json:"ID"`
89+
Alias
90+
}{
91+
ID: fmt.Sprintf("%x", m.ID),
92+
Alias: (Alias)(*m),
93+
})
94+
}
95+
96+
func (p *jsonPrinter) EndpointHealth(r []epHealth) { p.printJSON(r) }
97+
func (p *jsonPrinter) EndpointStatus(r []epStatus) { p.printJSON(r) }
98+
func (p *jsonPrinter) EndpointHashKV(r []epHashKV) { p.printJSON(r) }
99+
100+
func (p *jsonPrinter) MemberAdd(r v3.MemberAddResponse) { p.printJSON(r) }
101+
func (p *jsonPrinter) MemberList(r v3.MemberListResponse) { p.printJSON(r) }
102+
func (p *jsonPrinter) MemberPromote(id uint64, r v3.MemberPromoteResponse) { p.printJSON(r) }
103+
func (p *jsonPrinter) MemberRemove(id uint64, r v3.MemberRemoveResponse) { p.printJSON(r) }
104+
func (p *jsonPrinter) MemberUpdate(id uint64, r v3.MemberUpdateResponse) { p.printJSON(r) }
105+
51106
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-
}
107+
b, _ := json.Marshal(v)
57108
fmt.Println(string(b))
58109
}
59110

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\":\"")
111+
func (p *jsonPrinter) printJSON(v any) {
112+
var data any
113+
if !p.isHex {
114+
printJSON(v)
115+
return
116+
}
117+
118+
switch r := v.(type) {
119+
case []epStatus:
120+
type Alias epStatus
121+
122+
data = make([]any, len(r))
123+
for i, status := range r {
124+
data.([]any)[i] = &struct {
125+
Resp *HexStatusResponse `json:"Status"`
126+
*Alias
127+
}{
128+
Resp: (*HexStatusResponse)(status.Resp),
129+
Alias: (*Alias)(&status),
130+
}
131+
}
132+
case []epHashKV:
133+
type Alias epHashKV
134+
135+
data = make([]any, len(r))
136+
for i, status := range r {
137+
data.([]any)[i] = &struct {
138+
Resp *HexHashKVResponse `json:"HashKV"`
139+
*Alias
140+
}{
141+
Resp: (*HexHashKVResponse)(status.Resp),
142+
Alias: (*Alias)(&status),
143+
}
144+
}
145+
case v3.MemberAddResponse:
146+
type Alias v3.MemberAddResponse
147+
148+
data = &struct {
149+
Header *HexResponseHeader `json:"header"`
150+
Member *HexMember `json:"member"`
151+
Members []*HexMember `json:"members"`
152+
*Alias
153+
}{
154+
Header: (*HexResponseHeader)(r.Header),
155+
Member: (*HexMember)(r.Member),
156+
Members: toHexMembers(r.Members),
157+
Alias: (*Alias)(&r),
158+
}
159+
case v3.MemberListResponse:
160+
type Alias v3.MemberListResponse
161+
162+
data = &struct {
163+
Header *HexResponseHeader `json:"header"`
164+
Members []*HexMember `json:"members"`
165+
*Alias
166+
}{
167+
Header: (*HexResponseHeader)(r.Header),
168+
Members: toHexMembers(r.Members),
169+
Alias: (*Alias)(&r),
78170
}
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
171+
case v3.MemberPromoteResponse:
172+
type Alias v3.MemberPromoteResponse
173+
174+
data = &struct {
175+
Header *HexResponseHeader `json:"header"`
176+
Members []*HexMember `json:"members"`
177+
*Alias
178+
}{
179+
Header: (*HexResponseHeader)(r.Header),
180+
Members: toHexMembers(r.Members),
181+
Alias: (*Alias)(&r),
85182
}
86-
buffer.Write(b)
87-
buffer.WriteString(",\"clientURLs\":")
88-
b, err = json.Marshal(r.Members[i].ClientURLs)
89-
if err != nil {
90-
return
183+
case v3.MemberRemoveResponse:
184+
type Alias v3.MemberRemoveResponse
185+
186+
data = &struct {
187+
Header *HexResponseHeader `json:"header"`
188+
Members []*HexMember `json:"members"`
189+
*Alias
190+
}{
191+
Header: (*HexResponseHeader)(r.Header),
192+
Members: toHexMembers(r.Members),
193+
Alias: (*Alias)(&r),
91194
}
92-
buffer.Write(b)
93-
buffer.WriteByte('}')
94-
if i == len(r.Members)-1 {
95-
buffer.WriteString("]")
195+
case v3.MemberUpdateResponse:
196+
type Alias v3.MemberUpdateResponse
197+
198+
data = &struct {
199+
Header *HexResponseHeader `json:"header"`
200+
Members []*HexMember `json:"members"`
201+
*Alias
202+
}{
203+
Header: (*HexResponseHeader)(r.Header),
204+
Members: toHexMembers(r.Members),
205+
Alias: (*Alias)(&r),
96206
}
207+
default:
208+
data = v
209+
}
210+
211+
printJSON(data)
212+
}
213+
214+
func toHexMembers(members []*pb.Member) []*HexMember {
215+
hexMembers := make([]*HexMember, len(members))
216+
for i, member := range members {
217+
hexMembers[i] = (*HexMember)(member)
97218
}
98-
buffer.WriteString("}")
99-
fmt.Println(buffer.String())
219+
return hexMembers
100220
}

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)