Skip to content

Commit 590a134

Browse files
authored
csds client: add filter support to node id (#35)
This PR includes adding flag -filter_mode and -filter_pattern to support filter on node_id. #34 Filter modes currently supported: - prefix - suffix - regex Signed-off-by: Yutong Li <[email protected]>
1 parent 0afef27 commit 590a134

File tree

12 files changed

+655
-74
lines changed

12 files changed

+655
-74
lines changed

csds-client/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ Common options are exposed/controlled via command line flags, while control plan
6464
* If the browser fails to open due to os version issue, you can copy the content in `config_graph.dot`, and then paste it in the edit box on the left of [Graphviz Online](https://dreampuf.github.io/GraphvizOnline/) or any other tools for [Graphviz](https://graphviz.org/) to show the graph of the dot file.
6565
* Each xDS node shown in the graph is labelled by index (e.g. LDS0, RDS0, RDS1,...) to make the graph more clear. The real name of xDS resource in config will show when the user hovers the mouse over each node.
6666
* If **the visualization mode** and **the monitor mode** are enabled together, the client will only save graph dot data for the latest response without opening the browser to avoid frequent pop-ups of the browser due to short monitor interval.
67+
* ***-filter_mode***: the filter mode for the filter on Client ID to be returned (e.g. prefix, suffix, regex, ...)
68+
* If this flag is not specified, all Client ID will be returned.
69+
* ***-filter_pattern***: the filter pattern for the filter on Client ID to be returned
70+
* This flag works with ***-filter_mode*** together.
6771

6872
## Output
6973
```

csds-client/client/client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ type ClientOptions struct {
1717
ConfigFile string
1818
MonitorInterval time.Duration
1919
Visualization bool
20+
FilterMode string
21+
FilterPattern string
2022
}
2123

2224
// Client implements CSDS Client of a particular version. Upon creation of the new client it is

csds-client/client/util/util.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ import (
1313
"os"
1414
"os/exec"
1515
"path/filepath"
16+
"regexp"
1617
"runtime"
1718
"strconv"
19+
"strings"
1820

1921
"github.com/awalterschulze/gographviz"
2022
"github.com/emirpasic/gods/sets/treeset"
@@ -39,9 +41,9 @@ import (
3941
"google.golang.org/grpc/credentials/oauth"
4042
"google.golang.org/protobuf/encoding/protojson"
4143
"google.golang.org/protobuf/proto"
42-
"google.golang.org/protobuf/types/known/anypb"
4344
"google.golang.org/protobuf/reflect/protoreflect"
4445
"google.golang.org/protobuf/reflect/protoregistry"
46+
"google.golang.org/protobuf/types/known/anypb"
4547
)
4648

4749
// IsJson checks if str is a valid json format string
@@ -124,7 +126,7 @@ func (r *TypeResolver) FindMessageByURL(url string) (protoreflect.MessageType, e
124126
fileAccessLog := envoy_extensions_accesslog_v3.FileAccessLog{}
125127
return fileAccessLog.ProtoReflect().Type(), nil
126128
default:
127-
dummy := anypb.Any{}
129+
dummy := anypb.Any{}
128130
return dummy.ProtoReflect().Type(), nil
129131
}
130132
}
@@ -467,3 +469,23 @@ func ParseYamlStrToMap(yamlStr string) (map[string]interface{}, error) {
467469
}
468470
return data, nil
469471
}
472+
473+
func FilterNodeId(id string, filterMode string, filterPattern string) (bool, error) {
474+
switch filterMode {
475+
case "prefix":
476+
if strings.HasPrefix(id, filterPattern) {
477+
return true, nil
478+
}
479+
case "suffix":
480+
if strings.HasSuffix(id, filterPattern) {
481+
return true, nil
482+
}
483+
case "regex":
484+
matched, err := regexp.MatchString(filterPattern, id)
485+
if err != nil {
486+
return false, err
487+
}
488+
return matched, nil
489+
}
490+
return false, nil
491+
}

csds-client/client/v2/client.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ func (c *ClientV2) parseNodeMatcher() error {
6464
return fmt.Errorf("%s platform is not supported, list of supported platforms: gcp", c.opts.Platform)
6565
}
6666

67+
if c.opts.FilterMode != "" && c.opts.FilterMode != "prefix" && c.opts.FilterMode != "suffix" && c.opts.FilterMode != "regex" {
68+
return fmt.Errorf("%s filter mode is not supported, list of supported filter modes: prefix, suffix, regex", c.opts.FilterMode)
69+
}
70+
6771
return nil
6872
}
6973

@@ -230,6 +234,17 @@ func printOutResponse(response *csdspb_v2.ClientStatusResponse, opts client.Clie
230234
if metadata["XDS_STREAM_TYPE"] != nil {
231235
xdsType = metadata["XDS_STREAM_TYPE"].(string)
232236
}
237+
238+
// filter node id
239+
if opts.FilterPattern != "" {
240+
matched, err := clientutil.FilterNodeId(id, opts.FilterMode, opts.FilterPattern)
241+
if err != nil {
242+
return err
243+
}
244+
if !matched {
245+
continue
246+
}
247+
}
233248
}
234249

235250
if config.GetXdsConfig() == nil {

csds-client/client/v2/client_test.go

Lines changed: 132 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package client
33

44
import (
55
"envoy-tools/csds-client/client"
6-
clientutil "envoy-tools/csds-client/client/util"
6+
clientUtil "envoy-tools/csds-client/client/util"
77
"io/ioutil"
88
"path/filepath"
99
"testing"
@@ -32,7 +32,7 @@ func TestParseNodeMatcherWithFile(t *testing.T) {
3232
t.Errorf("Parse NodeMatcher Error: %v", err)
3333
}
3434

35-
if !clientutil.ShouldEqualJSON(t, string(get), want) {
35+
if !clientUtil.ShouldEqualJSON(t, string(get), want) {
3636
t.Errorf("NodeMatcher = \n%v\n, want: \n%v\n", string(get), want)
3737
}
3838
}
@@ -57,7 +57,7 @@ func TestParseNodeMatcherWithString(t *testing.T) {
5757
if err != nil {
5858
t.Errorf("Parse NodeMatcher Error: %v", err)
5959
}
60-
if !clientutil.ShouldEqualJSON(t, string(get), want) {
60+
if !clientUtil.ShouldEqualJSON(t, string(get), want) {
6161
t.Errorf("NodeMatcher = \n%v\n, want: \n%v\n", string(get), want)
6262
}
6363
}
@@ -82,7 +82,7 @@ func TestParseNodeMatcherWithFileAndString(t *testing.T) {
8282
if err != nil {
8383
t.Errorf("Parse NodeMatcher Error: %v", err)
8484
}
85-
if !clientutil.ShouldEqualJSON(t, string(get), want) {
85+
if !clientUtil.ShouldEqualJSON(t, string(get), want) {
8686
t.Errorf("NodeMatcher = \n%v\n, want: \n%v\n", string(get), want)
8787
}
8888
}
@@ -103,12 +103,16 @@ func TestParseResponseWithoutNodeId(t *testing.T) {
103103
if err = protojson.Unmarshal(responsejson, &response); err != nil {
104104
t.Errorf("Read From File Failure: %v", err)
105105
}
106-
out := clientutil.CaptureOutput(func() {
106+
out := clientUtil.CaptureOutput(func() {
107107
if err := printOutResponse(&response, c.opts); err != nil {
108108
t.Errorf("Print out response error: %v", err)
109109
}
110110
})
111-
want := "Client ID xDS stream type Config Status \ntest_node_1 test_stream_type1 N/A \ntest_node_2 test_stream_type2 N/A \ntest_node_3 test_stream_type3 N/A \n"
111+
want := `Client ID xDS stream type Config Status
112+
test_node_1 test_stream_type1 N/A
113+
test_node_2 test_stream_type2 N/A
114+
test_node_3 test_stream_type3 N/A
115+
`
112116
if out != want {
113117
t.Errorf("want\n%vout\n%v", want, out)
114118
}
@@ -131,12 +135,16 @@ func TestParseResponseWithNodeId(t *testing.T) {
131135
if err = protojson.Unmarshal(responsejson, &response); err != nil {
132136
t.Errorf("Read From File Failure: %v", err)
133137
}
134-
out := clientutil.CaptureOutput(func() {
138+
out := clientUtil.CaptureOutput(func() {
135139
if err := printOutResponse(&response, c.opts); err != nil {
136140
t.Errorf("Print out response error: %v", err)
137141
}
138142
})
139-
want := "Client ID xDS stream type Config Status \ntest_nodeid test_stream_type1 RDS STALE \n CDS STALE \nConfig has been saved to test_config.json\n"
143+
want := `Client ID xDS stream type Config Status
144+
test_nodeid test_stream_type1 RDS STALE
145+
CDS STALE
146+
Config has been saved to test_config.json
147+
`
140148
if out != want {
141149
t.Errorf("want\n%vout\n%v", want, out)
142150
}
@@ -146,7 +154,7 @@ func TestParseResponseWithNodeId(t *testing.T) {
146154
if err != nil {
147155
t.Errorf("Write config to file failure: %v", err)
148156
}
149-
ok, err := clientutil.EqualJSONBytes(outputjson, responsejson)
157+
ok, err := clientUtil.EqualJSONBytes(outputjson, responsejson)
150158
if err != nil {
151159
t.Errorf("failed to parse json")
152160
}
@@ -162,11 +170,123 @@ func TestVisualization(t *testing.T) {
162170
if err != nil {
163171
t.Errorf("Read From File Failure: %v", err)
164172
}
165-
if err := clientutil.Visualize(responsejson, false); err != nil {
173+
if err := clientUtil.Visualize(responsejson, false); err != nil {
166174
t.Errorf("Visualization Failure: %v", err)
167175
}
168-
want := "digraph G {\nrankdir=LR;\n\\\"test_lds_0\\\"->\\\"test_rds_0\\\"[ arrowsize=0.3, penwidth=0.3 ];\n\\\"test_lds_0\\\"->\\\"test_rds_1\\\"[ arrowsize=0.3, penwidth=0.3 ];\n\\\"test_rds_0\\\"->\\\"test_cds_0\\\"[ arrowsize=0.3, penwidth=0.3 ];\n\\\"test_rds_0\\\"->\\\"test_cds_1\\\"[ arrowsize=0.3, penwidth=0.3 ];\n\\\"test_rds_1\\\"->\\\"test_cds_1\\\"[ arrowsize=0.3, penwidth=0.3 ];\n\\\"test_cds_0\\\" [ color=\\\"#34A853\\\", fillcolor=\\\"#34A853\\\", fontcolor=white, fontname=Roboto, label=CDS0, shape=box, style=\\\"\"filled,rounded\"\\\" ];\n\\\"test_cds_1\\\" [ color=\\\"#34A853\\\", fillcolor=\\\"#34A853\\\", fontcolor=white, fontname=Roboto, label=CDS1, shape=box, style=\\\"\"filled,rounded\"\\\" ];\n\\\"test_lds_0\\\" [ color=\\\"#4285F4\\\", fillcolor=\\\"#4285F4\\\", fontcolor=white, fontname=Roboto, label=LDS0, shape=box, style=\\\"\"filled,rounded\"\\\" ];\n\\\"test_rds_0\\\" [ color=\\\"#FBBC04\\\", fillcolor=\\\"#FBBC04\\\", fontcolor=white, fontname=Roboto, label=RDS0, shape=box, style=\\\"\"filled,rounded\"\\\" ];\n\\\"test_rds_1\\\" [ color=\\\"#FBBC04\\\", fillcolor=\\\"#FBBC04\\\", fontcolor=white, fontname=Roboto, label=RDS1, shape=box, style=\\\"\"filled,rounded\"\\\" ];\n\n}\n"
169-
if err := clientutil.OpenBrowser("http://dreampuf.github.io/GraphvizOnline/#" + want); err != nil {
176+
want := `digraph G {
177+
rankdir=LR;
178+
"test_lds_0"->"test_rds_0"[ arrowsize=0.3, penwidth=0.3 ];
179+
"test_lds_0"->"test_rds_1"[ arrowsize=0.3, penwidth=0.3 ];
180+
"test_rds_0"->"test_cds_0"[ arrowsize=0.3, penwidth=0.3 ];
181+
"test_rds_0"->"test_cds_1"[ arrowsize=0.3, penwidth=0.3 ];
182+
"test_rds_1"->"test_cds_1"[ arrowsize=0.3, penwidth=0.3 ];
183+
"test_cds_0" [ color="#34A853", fillcolor="#34A853", fontcolor=white, fontname=Roboto, label=CDS0, shape=box, style="filled,rounded" ];
184+
"test_cds_1" [ color="#34A853", fillcolor="#34A853", fontcolor=white, fontname=Roboto, label=CDS1, shape=box, style="filled,rounded" ];
185+
"test_lds_0" [ color="#4285F4", fillcolor="#4285F4", fontcolor=white, fontname=Roboto, label=LDS0, shape=box, style="filled,rounded" ];
186+
"test_rds_0" [ color="#FBBC04", fillcolor="#FBBC04", fontcolor=white, fontname=Roboto, label=RDS0, shape=box, style="filled,rounded" ];
187+
"test_rds_1" [ color="#FBBC04", fillcolor="#FBBC04", fontcolor=white, fontname=Roboto, label=RDS1, shape=box, style="filled,rounded" ];
188+
189+
}
190+
`
191+
if err := clientUtil.OpenBrowser("http://dreampuf.github.io/GraphvizOnline/#" + want); err != nil {
170192
t.Errorf("Open want graph failure: %v", err)
171193
}
172194
}
195+
196+
// TestNodeIdPrefixFilter tests node_id prefix filter
197+
func TestNodeIdPrefixFilter(t *testing.T) {
198+
c := ClientV2{
199+
opts: client.ClientOptions{
200+
Platform: "gcp",
201+
FilterMode: "prefix",
202+
FilterPattern: "test",
203+
},
204+
}
205+
filename, _ := filepath.Abs("./response_for_filter.json")
206+
responsejson, err := ioutil.ReadFile(filename)
207+
if err != nil {
208+
t.Errorf("Read From File Failure: %v", err)
209+
}
210+
var response csdspb_v2.ClientStatusResponse
211+
if err = protojson.Unmarshal(responsejson, &response); err != nil {
212+
t.Errorf("Read From File Failure: %v", err)
213+
}
214+
out := clientUtil.CaptureOutput(func() {
215+
if err := printOutResponse(&response, c.opts); err != nil {
216+
t.Errorf("Print out response error: %v", err)
217+
}
218+
})
219+
want := `Client ID xDS stream type Config Status
220+
test_node_1 test_stream_type1 N/A
221+
test_node_2 test_stream_type2 N/A
222+
test_node_3 test_stream_type3 N/A
223+
`
224+
if out != want {
225+
t.Errorf("want\n%vout\n%v", want, out)
226+
}
227+
}
228+
229+
// TestNodeIdSuffixFilter tests node_id suffix filter
230+
func TestNodeIdSuffixFilter(t *testing.T) {
231+
c := ClientV2{
232+
opts: client.ClientOptions{
233+
Platform: "gcp",
234+
FilterMode: "suffix",
235+
FilterPattern: "3",
236+
},
237+
}
238+
filename, _ := filepath.Abs("./response_for_filter.json")
239+
responsejson, err := ioutil.ReadFile(filename)
240+
if err != nil {
241+
t.Errorf("Read From File Failure: %v", err)
242+
}
243+
var response csdspb_v2.ClientStatusResponse
244+
if err = protojson.Unmarshal(responsejson, &response); err != nil {
245+
t.Errorf("Read From File Failure: %v", err)
246+
}
247+
out := clientUtil.CaptureOutput(func() {
248+
if err := printOutResponse(&response, c.opts); err != nil {
249+
t.Errorf("Print out response error: %v", err)
250+
}
251+
})
252+
want := `Client ID xDS stream type Config Status
253+
test_node_3 test_stream_type3 N/A
254+
node_3 test_stream_type4 N/A
255+
`
256+
if out != want {
257+
t.Errorf("want\n%vout\n%v", want, out)
258+
}
259+
}
260+
261+
// TestNodeIdRegexFilter tests node_id regex filter
262+
func TestNodeIdRegexFilter(t *testing.T) {
263+
c := ClientV2{
264+
opts: client.ClientOptions{
265+
Platform: "gcp",
266+
FilterMode: "regex",
267+
FilterPattern: "test.*",
268+
},
269+
}
270+
filename, _ := filepath.Abs("./response_for_filter.json")
271+
responsejson, err := ioutil.ReadFile(filename)
272+
if err != nil {
273+
t.Errorf("Read From File Failure: %v", err)
274+
}
275+
var response csdspb_v2.ClientStatusResponse
276+
if err = protojson.Unmarshal(responsejson, &response); err != nil {
277+
t.Errorf("Read From File Failure: %v", err)
278+
}
279+
out := clientUtil.CaptureOutput(func() {
280+
if err := printOutResponse(&response, c.opts); err != nil {
281+
t.Errorf("Print out response error: %v", err)
282+
}
283+
})
284+
want := `Client ID xDS stream type Config Status
285+
test_node_1 test_stream_type1 N/A
286+
test_node_2 test_stream_type2 N/A
287+
test_node_3 test_stream_type3 N/A
288+
`
289+
if out != want {
290+
t.Errorf("want\n%vout\n%v", want, out)
291+
}
292+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"config": [
3+
{
4+
"node": {
5+
"id": "test_node_1",
6+
"metadata": {
7+
"XDS_STREAM_TYPE": "test_stream_type1"
8+
}
9+
}
10+
},
11+
{
12+
"node": {
13+
"id": "test_node_2",
14+
"metadata": {
15+
"XDS_STREAM_TYPE": "test_stream_type2"
16+
}
17+
}
18+
},
19+
{
20+
"node": {
21+
"id": "test_node_3",
22+
"metadata": {
23+
"XDS_STREAM_TYPE": "test_stream_type3"
24+
}
25+
}
26+
},
27+
{
28+
"node": {
29+
"id": "node_3",
30+
"metadata": {
31+
"XDS_STREAM_TYPE": "test_stream_type4"
32+
}
33+
}
34+
}
35+
]
36+
}

csds-client/client/v3/client.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ func (c *ClientV3) parseNodeMatcher() error {
6464
return fmt.Errorf("%s platform is not supported, list of supported platforms: gcp", c.opts.Platform)
6565
}
6666

67+
if c.opts.FilterMode != "" && c.opts.FilterMode != "prefix" && c.opts.FilterMode != "suffix" && c.opts.FilterMode != "regex" {
68+
return fmt.Errorf("%s filter mode is not supported, list of supported filter modes: prefix, suffix, regex", c.opts.FilterMode)
69+
}
70+
6771
return nil
6872
}
6973

@@ -232,6 +236,17 @@ func printOutResponse(response *csdspb_v3.ClientStatusResponse, opts client.Clie
232236
if metadata["XDS_STREAM_TYPE"] != nil {
233237
xdsType = metadata["XDS_STREAM_TYPE"].(string)
234238
}
239+
240+
// filter node id
241+
if opts.FilterPattern != "" {
242+
matched, err := clientutil.FilterNodeId(id, opts.FilterMode, opts.FilterPattern)
243+
if err != nil {
244+
return err
245+
}
246+
if !matched {
247+
continue
248+
}
249+
}
235250
}
236251

237252
if config.GetXdsConfig() == nil {

0 commit comments

Comments
 (0)