github.com/cilium/cilium@v1.16.2/pkg/hubble/filters/labels_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Hubble 3 4 package filters 5 6 import ( 7 "context" 8 "reflect" 9 "testing" 10 11 flowpb "github.com/cilium/cilium/api/v1/flow" 12 v1 "github.com/cilium/cilium/pkg/hubble/api/v1" 13 ) 14 15 func TestLabelSelectorFilter(t *testing.T) { 16 type args struct { 17 f []*flowpb.FlowFilter 18 ev []*v1.Event 19 } 20 tests := []struct { 21 name string 22 args args 23 wantErr bool 24 want []bool 25 }{ 26 { 27 name: "label filter without value", 28 args: args{ 29 f: []*flowpb.FlowFilter{{SourceLabel: []string{"label1", "label2"}}}, 30 ev: []*v1.Event{ 31 { 32 Event: &flowpb.Flow{ 33 Source: &flowpb.Endpoint{ 34 Labels: []string{"label1"}, 35 }, 36 }, 37 }, 38 { 39 Event: &flowpb.Flow{ 40 Source: &flowpb.Endpoint{ 41 Labels: []string{"label1=val1"}, 42 }, 43 }, 44 }, 45 { 46 Event: &flowpb.Flow{ 47 Source: &flowpb.Endpoint{ 48 Labels: []string{"label2", "label3", "label4=val4"}, 49 }, 50 }, 51 }, 52 { 53 Event: &flowpb.Flow{ 54 Source: &flowpb.Endpoint{ 55 Labels: []string{"label3"}, 56 }, 57 }, 58 }, 59 }, 60 }, 61 want: []bool{ 62 true, 63 true, 64 true, 65 false, 66 }, 67 }, 68 { 69 name: "label filter with value", 70 args: args{ 71 f: []*flowpb.FlowFilter{{SourceLabel: []string{"label1=val1", "label2=val2"}}}, 72 ev: []*v1.Event{ 73 { 74 Event: &flowpb.Flow{ 75 Source: &flowpb.Endpoint{ 76 Labels: []string{"label1"}, 77 }, 78 }, 79 }, 80 { 81 Event: &flowpb.Flow{ 82 Source: &flowpb.Endpoint{ 83 Labels: []string{"label1=val1"}, 84 }, 85 }, 86 }, 87 { 88 Event: &flowpb.Flow{ 89 Source: &flowpb.Endpoint{ 90 Labels: []string{"label1=val2", "label2=val1", "label3"}, 91 }, 92 }, 93 }, 94 { 95 Event: &flowpb.Flow{ 96 Source: &flowpb.Endpoint{ 97 Labels: []string{"label2=val2", "label3"}, 98 }, 99 }, 100 }, 101 { 102 Event: &flowpb.Flow{ 103 Source: &flowpb.Endpoint{ 104 Labels: []string{"label3=val1"}, 105 }, 106 }, 107 }, 108 { 109 Event: &flowpb.Flow{ 110 Source: &flowpb.Endpoint{ 111 Labels: []string{""}, 112 }, 113 }, 114 }, 115 { 116 Event: &flowpb.Flow{ 117 Source: &flowpb.Endpoint{ 118 Labels: nil, 119 }, 120 }, 121 }, 122 { 123 Event: &flowpb.Flow{ 124 Source: &flowpb.Endpoint{ 125 Labels: []string{"label1=val1=toomuch"}, 126 }, 127 }, 128 }, 129 }, 130 }, 131 want: []bool{ 132 false, 133 true, 134 false, 135 true, 136 false, 137 false, 138 false, 139 false, 140 }, 141 }, 142 { 143 name: "complex label label filter", 144 args: args{ 145 f: []*flowpb.FlowFilter{{SourceLabel: []string{"label1 in (val1, val2), label3 notin ()"}}}, 146 ev: []*v1.Event{ 147 { 148 Event: &flowpb.Flow{ 149 Source: &flowpb.Endpoint{ 150 Labels: []string{"label1"}, 151 }, 152 }, 153 }, 154 { 155 Event: &flowpb.Flow{ 156 Source: &flowpb.Endpoint{ 157 Labels: []string{"label1=val1"}, 158 }, 159 }, 160 }, 161 { 162 Event: &flowpb.Flow{ 163 Source: &flowpb.Endpoint{ 164 Labels: []string{"label1=val2", "label2=val1", "label3=val3"}, 165 }, 166 }, 167 }, 168 { 169 Event: &flowpb.Flow{ 170 Source: &flowpb.Endpoint{ 171 Labels: []string{"label2=val2", "label3"}, 172 }, 173 }, 174 }, 175 { 176 Event: &flowpb.Flow{ 177 Source: &flowpb.Endpoint{ 178 Labels: []string{"label1=val1", "label3=val3"}, 179 }, 180 }, 181 }, 182 }, 183 }, 184 want: []bool{ 185 false, 186 true, 187 true, 188 false, 189 true, 190 }, 191 }, 192 { 193 name: "node label filter", 194 args: args{ 195 f: []*flowpb.FlowFilter{ 196 { 197 NodeLabels: []string{"src1, src2=val2"}, 198 }, 199 }, 200 ev: []*v1.Event{ 201 { 202 Event: &flowpb.Flow{ 203 NodeLabels: []string{"src1", "src2=val2"}, 204 }, 205 }, 206 { 207 Event: &flowpb.Flow{ 208 Source: &flowpb.Endpoint{ 209 Labels: []string{"src1", "src2=val2"}, 210 }, 211 }, 212 }, 213 { 214 Event: &flowpb.Flow{ 215 Destination: &flowpb.Endpoint{ 216 Labels: []string{"src1", "src2=val2"}, 217 }, 218 }, 219 }, 220 }, 221 }, 222 want: []bool{ 223 true, 224 false, 225 false, 226 }, 227 }, 228 { 229 name: "source and destination label filter", 230 args: args{ 231 f: []*flowpb.FlowFilter{ 232 { 233 SourceLabel: []string{"src1, src2=val2"}, 234 DestinationLabel: []string{"dst1, dst2=val2"}, 235 }, 236 }, 237 ev: []*v1.Event{ 238 { 239 Event: &flowpb.Flow{ 240 Source: &flowpb.Endpoint{ 241 Labels: []string{"src1", "src2=val2"}, 242 }, 243 Destination: &flowpb.Endpoint{ 244 Labels: []string{"dst1", "dst2=val2"}, 245 }, 246 }, 247 }, 248 { 249 Event: &flowpb.Flow{ 250 Source: &flowpb.Endpoint{ 251 Labels: []string{"label1=val1"}, 252 }, 253 }, 254 }, 255 { 256 Event: &flowpb.Flow{ 257 Destination: &flowpb.Endpoint{ 258 Labels: []string{"dst1", "dst2=val2"}, 259 }, 260 }, 261 }, 262 { 263 Event: &flowpb.Flow{ 264 Source: &flowpb.Endpoint{ 265 Labels: []string{"dst1", "dst2=val2"}, 266 }, 267 Destination: &flowpb.Endpoint{ 268 Labels: []string{"src1", "src2=val2"}, 269 }, 270 }, 271 }, 272 { 273 Event: &flowpb.Flow{ 274 Source: &flowpb.Endpoint{ 275 Labels: []string{"src1"}, 276 }, 277 Destination: &flowpb.Endpoint{ 278 Labels: []string{"dst1"}, 279 }, 280 }, 281 }, 282 }, 283 }, 284 want: []bool{ 285 true, 286 false, 287 false, 288 false, 289 false, 290 }, 291 }, 292 { 293 name: "matchall filter", 294 args: args{ 295 f: []*flowpb.FlowFilter{ 296 { 297 SourceLabel: []string{""}, 298 }, 299 }, 300 ev: []*v1.Event{ 301 { 302 Event: &flowpb.Flow{ 303 Source: &flowpb.Endpoint{ 304 Labels: []string{"src1", "src2=val2"}, 305 }, 306 }, 307 }, 308 { 309 Event: &flowpb.Flow{ 310 Source: &flowpb.Endpoint{ 311 Labels: nil, 312 }, 313 }, 314 }, 315 { 316 Event: &flowpb.Flow{ 317 Source: &flowpb.Endpoint{ 318 Labels: []string{""}, 319 }, 320 }, 321 }, 322 }, 323 }, 324 want: []bool{ 325 true, 326 true, 327 true, 328 }, 329 }, 330 { 331 name: "cilium fixed prefix filters", 332 args: args{ 333 f: []*flowpb.FlowFilter{ 334 { 335 SourceLabel: []string{"k8s:app=bar", "foo", "reserved:host"}, 336 }, 337 }, 338 ev: []*v1.Event{ 339 { 340 Event: &flowpb.Flow{ 341 Source: &flowpb.Endpoint{ 342 Labels: []string{"k8s:app=bar"}, 343 }, 344 }, 345 }, 346 { 347 Event: &flowpb.Flow{ 348 Source: &flowpb.Endpoint{ 349 Labels: []string{"k8s:foo=baz"}, 350 }, 351 }, 352 }, 353 { 354 Event: &flowpb.Flow{ 355 Source: &flowpb.Endpoint{ 356 Labels: []string{"k8s.app=bar"}, 357 }, 358 }, 359 }, 360 { 361 Event: &flowpb.Flow{ 362 Source: &flowpb.Endpoint{ 363 Labels: []string{"container:foo=bar", "reserved:host"}, 364 }, 365 }, 366 }, 367 }, 368 }, 369 want: []bool{ 370 true, 371 true, 372 false, 373 true, 374 }, 375 }, 376 { 377 name: "cilium fixed prefix not filter", 378 args: args{ 379 f: []*flowpb.FlowFilter{ 380 { 381 SourceLabel: []string{"!k8s:app"}, 382 }, 383 }, 384 ev: []*v1.Event{ 385 { 386 Event: &flowpb.Flow{ 387 Source: &flowpb.Endpoint{ 388 Labels: []string{"k8s:app=bar"}, 389 }, 390 }, 391 }, 392 { 393 Event: &flowpb.Flow{ 394 Source: &flowpb.Endpoint{ 395 Labels: []string{"k8s:app=baz"}, 396 }, 397 }, 398 }, 399 { 400 Event: &flowpb.Flow{ 401 Source: &flowpb.Endpoint{ 402 Labels: []string{"k8s.app=bar"}, 403 }, 404 }, 405 }, 406 { 407 Event: &flowpb.Flow{ 408 Source: &flowpb.Endpoint{ 409 Labels: []string{"container:foo=bar", "reserved:host"}, 410 }, 411 }, 412 }, 413 }, 414 }, 415 want: []bool{ 416 false, 417 false, 418 true, 419 true, 420 }, 421 }, 422 { 423 name: "cilium any prefix filters", 424 args: args{ 425 f: []*flowpb.FlowFilter{ 426 { 427 SourceLabel: []string{"any:key"}, 428 }, 429 }, 430 ev: []*v1.Event{ 431 { 432 Event: &flowpb.Flow{ 433 Source: &flowpb.Endpoint{ 434 Labels: []string{"key"}, 435 }, 436 }, 437 }, 438 { 439 Event: &flowpb.Flow{ 440 Source: &flowpb.Endpoint{ 441 Labels: []string{"reserved:key"}, 442 }, 443 }, 444 }, 445 { 446 Event: &flowpb.Flow{ 447 Source: &flowpb.Endpoint{ 448 Labels: []string{"any.key"}, 449 }, 450 }, 451 }, 452 }, 453 }, 454 want: []bool{ 455 true, 456 true, 457 false, 458 }, 459 }, 460 { 461 name: "cilium no prefix filters", 462 args: args{ 463 f: []*flowpb.FlowFilter{ 464 { 465 SourceLabel: []string{"key"}, 466 }, 467 }, 468 ev: []*v1.Event{ 469 { 470 Event: &flowpb.Flow{ 471 Source: &flowpb.Endpoint{ 472 Labels: []string{"key"}, 473 }, 474 }, 475 }, 476 { 477 Event: &flowpb.Flow{ 478 Source: &flowpb.Endpoint{ 479 Labels: []string{"reserved:key"}, 480 }, 481 }, 482 }, 483 { 484 Event: &flowpb.Flow{ 485 Source: &flowpb.Endpoint{ 486 Labels: []string{"any.key"}, 487 }, 488 }, 489 }, 490 }, 491 }, 492 want: []bool{ 493 true, 494 true, 495 false, 496 }, 497 }, 498 { 499 name: "cilium k8s label with dot", 500 args: args{ 501 f: []*flowpb.FlowFilter{ 502 { 503 SourceLabel: []string{"key.with.dot"}, 504 }, 505 }, 506 ev: []*v1.Event{ 507 { 508 Event: &flowpb.Flow{ 509 Source: &flowpb.Endpoint{ 510 Labels: []string{"k8s:key.with.dot"}, 511 }, 512 }, 513 }, 514 { 515 Event: &flowpb.Flow{ 516 Source: &flowpb.Endpoint{ 517 Labels: []string{"reserved:key.with.dot"}, 518 }, 519 }, 520 }, 521 { 522 Event: &flowpb.Flow{ 523 Source: &flowpb.Endpoint{ 524 Labels: []string{"any.key.with.dot"}, 525 }, 526 }, 527 }, 528 { 529 Event: &flowpb.Flow{ 530 Source: &flowpb.Endpoint{ 531 Labels: []string{"key:with.dot"}, 532 }, 533 }, 534 }, 535 }, 536 }, 537 want: []bool{ 538 true, 539 true, 540 false, 541 false, 542 }, 543 }, 544 { 545 name: "invalid source filter", 546 args: args{ 547 f: []*flowpb.FlowFilter{ 548 { 549 SourceLabel: []string{"()"}, 550 }, 551 }, 552 }, 553 wantErr: true, 554 }, 555 { 556 name: "invalid destination filter", 557 args: args{ 558 f: []*flowpb.FlowFilter{ 559 { 560 DestinationLabel: []string{"="}, 561 }, 562 }, 563 }, 564 wantErr: true, 565 }, 566 { 567 name: "invalid node filter", 568 args: args{ 569 f: []*flowpb.FlowFilter{ 570 { 571 NodeLabels: []string{"!"}, 572 }, 573 }, 574 }, 575 wantErr: true, 576 }, 577 } 578 for _, tt := range tests { 579 t.Run(tt.name, func(t *testing.T) { 580 fl, err := BuildFilterList(context.Background(), tt.args.f, []OnBuildFilter{&LabelsFilter{}}) 581 if (err != nil) != tt.wantErr { 582 t.Errorf("\"%s\" error = %v, wantErr %v", tt.name, err, tt.wantErr) 583 return 584 } 585 if err != nil { 586 return 587 } 588 for i, ev := range tt.args.ev { 589 if got := fl.MatchOne(ev); got != tt.want[i] { 590 t.Errorf("\"%s\" got %d = %v, want %v", tt.name, i, got, tt.want[i]) 591 } 592 } 593 }) 594 } 595 } 596 597 func Test_parseSelector(t *testing.T) { 598 type args struct { 599 selector string 600 } 601 tests := []struct { 602 name string 603 args args 604 want string 605 wantErr bool 606 }{ 607 { 608 name: "simple labels", 609 args: args{ 610 selector: "bar=baz,k8s:app=hubble,reserved:world", 611 }, 612 want: "any.bar=baz,k8s.app=hubble,reserved.world", 613 }, 614 { 615 name: "not label", 616 args: args{ 617 selector: "!k8s:app", 618 }, 619 want: "!k8s.app", 620 }, 621 { 622 name: "complex labels", 623 args: args{ 624 selector: "any:dash-label.com,k8s:io.cilium in (is-awesome,rocks)", 625 }, 626 want: "any.dash-label.com,k8s.io.cilium in (is-awesome,rocks)", 627 }, 628 { 629 name: "more complex labels", 630 args: args{ 631 selector: "any:dash-label.com,k8s:io.cilium in (is-awesome, andsoon, rocks), !foobar, io.cilium notin (), test:label.com/foobar", 632 }, 633 // NOTE: re-ordering and whitespace trimming is due to k8sLabels.Parse() 634 want: "any.dash-label.com,!any.foobar,any.io.cilium notin (),k8s.io.cilium in (andsoon,is-awesome,rocks),test.label.com/foobar", 635 }, 636 { 637 name: "too many colons", 638 args: args{ 639 selector: "any:k8s:bla", 640 }, 641 wantErr: true, 642 }, 643 } 644 for _, tt := range tests { 645 t.Run(tt.name, func(t *testing.T) { 646 got, err := parseSelector(tt.args.selector) 647 if (err != nil) != tt.wantErr { 648 t.Errorf("parseSelector() error = %v, wantErr %v", err, tt.wantErr) 649 return 650 } 651 if !tt.wantErr && !reflect.DeepEqual(got.String(), tt.want) { 652 t.Errorf("parseSelector() = %q, want %q", got, tt.want) 653 } 654 }) 655 } 656 }