github.com/cilium/cilium@v1.16.2/pkg/policy/k8s/service_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package k8s 5 6 import ( 7 "cmp" 8 "context" 9 "net/netip" 10 "slices" 11 "testing" 12 13 "github.com/cilium/stream" 14 "github.com/sirupsen/logrus" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 18 19 cmtypes "github.com/cilium/cilium/pkg/clustermesh/types" 20 "github.com/cilium/cilium/pkg/k8s" 21 cilium_v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" 22 "github.com/cilium/cilium/pkg/k8s/resource" 23 slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1" 24 k8sSynced "github.com/cilium/cilium/pkg/k8s/synced" 25 "github.com/cilium/cilium/pkg/k8s/types" 26 "github.com/cilium/cilium/pkg/labels" 27 "github.com/cilium/cilium/pkg/loadbalancer" 28 "github.com/cilium/cilium/pkg/option" 29 "github.com/cilium/cilium/pkg/policy" 30 "github.com/cilium/cilium/pkg/policy/api" 31 ) 32 33 type fakePolicyManager struct { 34 OnPolicyAdd func(rules api.Rules, opts *policy.AddOptions) (newRev uint64, err error) 35 OnPolicyDelete func(labels labels.LabelArray, opts *policy.DeleteOptions) (newRev uint64, err error) 36 } 37 38 func (f *fakePolicyManager) PolicyAdd(rules api.Rules, opts *policy.AddOptions) (newRev uint64, err error) { 39 if f.OnPolicyAdd != nil { 40 return f.OnPolicyAdd(rules, opts) 41 } 42 panic("OnPolicyAdd(api.Rules, *policy.AddOptions) (uint64, error) was called and is not set!") 43 } 44 45 func (f *fakePolicyManager) PolicyDelete(labels labels.LabelArray, opts *policy.DeleteOptions) (newRev uint64, err error) { 46 if f.OnPolicyDelete != nil { 47 return f.OnPolicyDelete(labels, opts) 48 } 49 panic("OnPolicyDelete(labels.LabelArray, *policy.DeleteOptions) (uint64, error) was called and is not set!") 50 } 51 52 type fakeService struct { 53 svc *k8s.Service 54 eps *k8s.Endpoints 55 } 56 57 type fakeServiceCache map[k8s.ServiceID]fakeService 58 59 func (f fakeServiceCache) ForEachService(yield func(svcID k8s.ServiceID, svc *k8s.Service, eps *k8s.Endpoints) bool) { 60 for svcID, s := range f { 61 if !yield(svcID, s.svc, s.eps) { 62 break 63 } 64 } 65 } 66 67 func addrToCIDRRule(addr netip.Addr) api.CIDRRule { 68 return api.CIDRRule{ 69 Cidr: api.CIDR(netip.PrefixFrom(addr, addr.BitLen()).String()), 70 Generated: true, 71 } 72 } 73 74 func sortCIDRSet(s api.CIDRRuleSlice) api.CIDRRuleSlice { 75 slices.SortFunc(s, func(a, b api.CIDRRule) int { 76 return cmp.Compare(a.Cidr, b.Cidr) 77 }) 78 return s 79 } 80 81 func TestPolicyWatcher_updateToServicesPolicies(t *testing.T) { 82 policyAdd := make(chan api.Rules, 3) 83 policyManager := &fakePolicyManager{ 84 OnPolicyAdd: func(rules api.Rules, opts *policy.AddOptions) (newRev uint64, err error) { 85 policyAdd <- rules 86 return 0, nil 87 }, 88 } 89 90 barSvcLabels := map[string]string{ 91 "app": "bar", 92 } 93 barSvcSelector := api.ServiceSelector(api.NewESFromMatchRequirements(barSvcLabels, nil)) 94 95 svcByNameCNP := &types.SlimCNP{ 96 CiliumNetworkPolicy: &cilium_v2.CiliumNetworkPolicy{ 97 TypeMeta: metav1.TypeMeta{ 98 APIVersion: "cilium.io/v2", 99 Kind: "CiliumNetworkPolicy", 100 }, 101 ObjectMeta: metav1.ObjectMeta{ 102 Name: "svc-by-name", 103 Namespace: "test", 104 }, 105 Spec: &api.Rule{ 106 EndpointSelector: api.NewESFromLabels(), 107 Egress: []api.EgressRule{ 108 { 109 EgressCommonRule: api.EgressCommonRule{ 110 ToServices: []api.Service{ 111 { 112 // Selects foo service by name 113 K8sService: &api.K8sServiceNamespace{ 114 ServiceName: "foo-svc", 115 Namespace: "foo-ns", 116 }, 117 }, 118 { 119 // Selects bar service by name 120 K8sService: &api.K8sServiceNamespace{ 121 ServiceName: "bar-svc", 122 Namespace: "bar-ns", 123 }, 124 }, 125 }, 126 }, 127 }, 128 }, 129 }, 130 Specs: api.Rules{ 131 { 132 EndpointSelector: api.NewESFromLabels(), 133 Egress: []api.EgressRule{ 134 { 135 EgressCommonRule: api.EgressCommonRule{ 136 ToServices: []api.Service{ 137 { 138 // Selects foo service by name 139 K8sService: &api.K8sServiceNamespace{ 140 ServiceName: "foo-svc", 141 Namespace: "foo-ns", 142 }, 143 }, 144 }, 145 }, 146 }, 147 }, 148 }, 149 }, 150 }, 151 } 152 svcByNameLbl := labels.NewLabel("io.cilium.k8s.policy.name", svcByNameCNP.Name, "k8s") 153 svcByNameKey := resource.NewKey(svcByNameCNP) 154 svcByNameResourceID := resourceIDForCiliumNetworkPolicy(svcByNameKey, svcByNameCNP) 155 156 svcByLabelCNP := &types.SlimCNP{ 157 CiliumNetworkPolicy: &cilium_v2.CiliumNetworkPolicy{ 158 TypeMeta: metav1.TypeMeta{ 159 APIVersion: "cilium.io/v2", 160 Kind: "ClusterwideCiliumNetworkPolicy", 161 }, 162 ObjectMeta: metav1.ObjectMeta{ 163 Name: "svc-by-label", 164 Namespace: "", 165 }, 166 Spec: &api.Rule{ 167 EndpointSelector: api.NewESFromLabels(), 168 Egress: []api.EgressRule{ 169 { 170 EgressCommonRule: api.EgressCommonRule{ 171 ToServices: []api.Service{ 172 { 173 // Selects bar service by label selector 174 K8sServiceSelector: &api.K8sServiceSelectorNamespace{ 175 Selector: barSvcSelector, 176 }, 177 }, 178 }, 179 }, 180 }, 181 }, 182 }, 183 }, 184 } 185 svcByLabelLbl := labels.NewLabel("io.cilium.k8s.policy.name", svcByLabelCNP.Name, "k8s") 186 svcByLabelKey := resource.NewKey(svcByLabelCNP) 187 svcByLabelResourceID := resourceIDForCiliumNetworkPolicy(svcByLabelKey, svcByLabelCNP) 188 189 fooEpAddr1 := cmtypes.MustParseAddrCluster("10.1.1.1") 190 fooEpAddr2 := cmtypes.MustParseAddrCluster("10.1.1.2") 191 fooSvcID := k8s.ServiceID{ 192 Name: "foo-svc", 193 Namespace: "foo-ns", 194 } 195 fooSvc := &k8s.Service{} 196 fooEps := &k8s.Endpoints{ 197 Backends: map[cmtypes.AddrCluster]*k8s.Backend{ 198 fooEpAddr1: { 199 Ports: map[string]*loadbalancer.L4Addr{ 200 "port": { 201 Protocol: loadbalancer.TCP, 202 Port: 80, 203 }, 204 }, 205 }, 206 fooEpAddr2: { 207 Ports: map[string]*loadbalancer.L4Addr{ 208 "port": { 209 Protocol: loadbalancer.TCP, 210 Port: 80, 211 }, 212 }, 213 }, 214 }, 215 } 216 217 barEpAddr := cmtypes.MustParseAddrCluster("192.168.1.1") 218 barSvcID := k8s.ServiceID{ 219 Name: "bar-svc", 220 Namespace: "bar-ns", 221 } 222 barSvc := &k8s.Service{ 223 Labels: barSvcLabels, 224 } 225 barEps := &k8s.Endpoints{ 226 Backends: map[cmtypes.AddrCluster]*k8s.Backend{ 227 barEpAddr: { 228 Ports: map[string]*loadbalancer.L4Addr{ 229 "port": { 230 Protocol: loadbalancer.UDP, 231 Port: 53, 232 }, 233 }, 234 }, 235 }, 236 } 237 238 // baz is similar to bar, but not an external service (thus not selectable) 239 bazSvcID := k8s.ServiceID{ 240 Name: "baz-svc", 241 Namespace: "baz-ns", 242 } 243 bazSvc := &k8s.Service{ 244 Labels: barSvcLabels, 245 Selector: map[string]string{ 246 "app": "baz", 247 }, 248 } 249 bazEps := barEps.DeepCopy() 250 251 logger := logrus.New() 252 logger.SetLevel(logrus.DebugLevel) 253 254 svcCache := fakeServiceCache{} 255 p := &policyWatcher{ 256 log: logrus.NewEntry(logger), 257 config: &option.DaemonConfig{}, 258 k8sResourceSynced: &k8sSynced.Resources{CacheStatus: make(k8sSynced.CacheStatus)}, 259 k8sAPIGroups: &k8sSynced.APIGroups{}, 260 policyManager: policyManager, 261 svcCache: svcCache, 262 cnpCache: map[resource.Key]*types.SlimCNP{}, 263 toServicesPolicies: map[resource.Key]struct{}{}, 264 cnpByServiceID: map[k8s.ServiceID]map[resource.Key]struct{}{}, 265 } 266 267 // Upsert policies. No services are known, so generated ToCIDRSet should be empty 268 err := p.onUpsert(svcByNameCNP, svcByNameKey, k8sAPIGroupCiliumNetworkPolicyV2, svcByNameResourceID) 269 assert.NoError(t, err) 270 rules := <-policyAdd 271 assert.Len(t, rules, 2) 272 assert.Len(t, rules[0].Egress, 1) 273 assert.Empty(t, rules[0].Egress[0].ToCIDRSet) 274 assert.Len(t, rules[1].Egress, 1) 275 assert.Empty(t, rules[1].Egress[0].ToCIDRSet) 276 277 err = p.onUpsert(svcByLabelCNP, svcByLabelKey, k8sAPIGroupCiliumNetworkPolicyV2, svcByLabelResourceID) 278 assert.NoError(t, err) 279 rules = <-policyAdd 280 assert.Len(t, rules, 1) 281 assert.Len(t, rules[0].Egress, 1) 282 assert.Empty(t, rules[0].Egress[0].ToCIDRSet) 283 284 // Check that policies are recognized as ToServices policies 285 assert.Equal(t, p.toServicesPolicies, map[resource.Key]struct{}{ 286 svcByNameKey: {}, 287 svcByLabelKey: {}, 288 }) 289 290 // Add foo-svc, which is selected by svcByNameCNP twice 291 svcCache[fooSvcID] = fakeService{ 292 svc: fooSvc, 293 eps: fooEps, 294 } 295 err = p.updateToServicesPolicies(fooSvcID, fooSvc, nil) 296 assert.NoError(t, err) 297 rules = <-policyAdd 298 assert.Len(t, rules, 2) 299 300 // Check that Spec was translated 301 assert.Len(t, rules[0].Egress, 1) 302 assert.Contains(t, rules[0].Labels, svcByNameLbl) 303 assert.Equal(t, svcByNameCNP.Spec.Egress[0].ToServices, rules[0].Egress[0].ToServices) 304 assert.Equal(t, sortCIDRSet(rules[0].Egress[0].ToCIDRSet), api.CIDRRuleSlice{ 305 addrToCIDRRule(fooEpAddr1.Addr()), 306 addrToCIDRRule(fooEpAddr2.Addr()), 307 }) 308 309 // Check that Specs was translated 310 assert.Len(t, rules[1].Egress, 1) 311 assert.Contains(t, rules[1].Labels, svcByNameLbl) 312 assert.Equal(t, svcByNameCNP.Specs[0].Egress[0].ToServices, rules[1].Egress[0].ToServices) 313 assert.Equal(t, sortCIDRSet(rules[1].Egress[0].ToCIDRSet), api.CIDRRuleSlice{ 314 addrToCIDRRule(fooEpAddr1.Addr()), 315 addrToCIDRRule(fooEpAddr2.Addr()), 316 }) 317 318 // Check that policy has been marked 319 assert.Equal(t, p.cnpByServiceID, map[k8s.ServiceID]map[resource.Key]struct{}{ 320 fooSvcID: { 321 svcByNameKey: {}, 322 }, 323 }) 324 325 // Add bar-svc, which is selected by both policies 326 svcCache[barSvcID] = fakeService{ 327 svc: barSvc, 328 eps: barEps, 329 } 330 err = p.updateToServicesPolicies(barSvcID, barSvc, nil) 331 assert.NoError(t, err) 332 333 // Expect two policies to be updated (in any order) 334 var policies [2]api.Rules 335 policies[0] = <-policyAdd 336 policies[1] = <-policyAdd 337 slices.SortFunc(policies[:], func(a, b api.Rules) int { 338 return cmp.Compare(a.String(), b.String()) 339 }) 340 byNameRules, byLabelRules := policies[0], policies[1] 341 342 // Check that svcByNameCNP Spec (matching foo and bar) was translated 343 assert.Len(t, byNameRules, 2) 344 assert.Len(t, byNameRules[0].Egress, 1) 345 assert.Contains(t, byNameRules[0].Labels, svcByNameLbl) 346 assert.Equal(t, svcByNameCNP.Spec.Egress[0].ToServices, byNameRules[0].Egress[0].ToServices) 347 assert.Equal(t, sortCIDRSet(byNameRules[0].Egress[0].ToCIDRSet), api.CIDRRuleSlice{ 348 addrToCIDRRule(fooEpAddr1.Addr()), 349 addrToCIDRRule(fooEpAddr2.Addr()), 350 addrToCIDRRule(barEpAddr.Addr()), 351 }) 352 353 // Check that svcByNameCNP Specs (matching only foo) was translated 354 assert.Len(t, byNameRules[1].Egress, 1) 355 assert.Contains(t, byNameRules[1].Labels, svcByNameLbl) 356 assert.Equal(t, svcByNameCNP.Specs[0].Egress[0].ToServices, byNameRules[1].Egress[0].ToServices) 357 assert.Equal(t, sortCIDRSet(byNameRules[1].Egress[0].ToCIDRSet), api.CIDRRuleSlice{ 358 addrToCIDRRule(fooEpAddr1.Addr()), 359 addrToCIDRRule(fooEpAddr2.Addr()), 360 }) 361 362 // Check that svcByLabelCNP Spec (matching only bar) was translated 363 assert.Len(t, byLabelRules, 1) 364 assert.Len(t, byLabelRules[0].Egress, 1) 365 assert.Contains(t, byLabelRules[0].Labels, svcByLabelLbl) 366 assert.Equal(t, svcByLabelCNP.Spec.Egress[0].ToServices, byLabelRules[0].Egress[0].ToServices) 367 assert.Equal(t, byLabelRules[0].Egress[0].ToCIDRSet, api.CIDRRuleSlice{ 368 addrToCIDRRule(barEpAddr.Addr()), 369 }) 370 371 // Check that policies have been marked 372 assert.Equal(t, p.cnpByServiceID, map[k8s.ServiceID]map[resource.Key]struct{}{ 373 fooSvcID: { 374 svcByNameKey: {}, 375 }, 376 barSvcID: { 377 svcByNameKey: {}, 378 svcByLabelKey: {}, 379 }, 380 }) 381 382 // Change foo-svc endpoints, which is selected by svcByNameCNP twice 383 delete(fooEps.Backends, fooEpAddr2) 384 err = p.updateToServicesPolicies(fooSvcID, fooSvc, fooSvc) 385 assert.NoError(t, err) 386 byNameRules = <-policyAdd 387 assert.Len(t, byNameRules, 2) 388 389 // Check that svcByNameCNP Spec (matching foo and bar) was translated 390 assert.Len(t, byNameRules[0].Egress, 1) 391 assert.Contains(t, byNameRules[0].Labels, svcByNameLbl) 392 assert.Equal(t, svcByNameCNP.Spec.Egress[0].ToServices, byNameRules[0].Egress[0].ToServices) 393 assert.Equal(t, sortCIDRSet(byNameRules[0].Egress[0].ToCIDRSet), api.CIDRRuleSlice{ 394 addrToCIDRRule(fooEpAddr1.Addr()), 395 addrToCIDRRule(barEpAddr.Addr()), 396 }) 397 398 // Check that Specs was translated (matching only foo) was translated 399 assert.Len(t, byNameRules[1].Egress, 1) 400 assert.Contains(t, byNameRules[1].Labels, svcByNameLbl) 401 assert.Equal(t, svcByNameCNP.Specs[0].Egress[0].ToServices, byNameRules[1].Egress[0].ToServices) 402 assert.Equal(t, sortCIDRSet(byNameRules[1].Egress[0].ToCIDRSet), api.CIDRRuleSlice{ 403 addrToCIDRRule(fooEpAddr1.Addr()), 404 }) 405 406 // Delete bar-svc labels. This should remove all CIDRs from svcByLabelCNP 407 oldBarSvc := barSvc.DeepCopy() 408 barSvc.Labels = nil 409 err = p.updateToServicesPolicies(barSvcID, barSvc, oldBarSvc) 410 assert.NoError(t, err) 411 412 // Expect two policies to be updated (in any order) 413 oldByNameRules := byNameRules.DeepCopy() 414 policies[0] = <-policyAdd 415 policies[1] = <-policyAdd 416 slices.SortFunc(policies[:], func(a, b api.Rules) int { 417 return cmp.Compare(a.String(), b.String()) 418 }) 419 byNameRules, byLabelRules = policies[0], policies[1] 420 421 // Check that svcByNameCNP has not changed 422 assert.Equal(t, 423 sortCIDRSet(byNameRules[0].Egress[0].ToCIDRSet), 424 sortCIDRSet(oldByNameRules[0].Egress[0].ToCIDRSet)) 425 assert.Equal(t, 426 sortCIDRSet(byNameRules[1].Egress[0].ToCIDRSet), 427 sortCIDRSet(oldByNameRules[1].Egress[0].ToCIDRSet)) 428 429 // Check that svcByLabelCNP Spec no longer matches anything 430 assert.Len(t, byLabelRules, 1) 431 assert.Len(t, byLabelRules[0].Egress, 1) 432 assert.Contains(t, byLabelRules[0].Labels, svcByLabelLbl) 433 assert.Equal(t, svcByLabelCNP.Spec.Egress[0].ToServices, byLabelRules[0].Egress[0].ToServices) 434 assert.Empty(t, byLabelRules[0].Egress[0].ToCIDRSet) 435 436 // Check that policies have been cleared 437 assert.Equal(t, p.cnpByServiceID, map[k8s.ServiceID]map[resource.Key]struct{}{ 438 fooSvcID: { 439 svcByNameKey: {}, 440 }, 441 barSvcID: { 442 svcByNameKey: {}, 443 }, 444 }) 445 446 // Add baz-svc, which is not selectable and thus must not trigger a policyAdd 447 svcCache[bazSvcID] = fakeService{ 448 svc: bazSvc, 449 eps: bazEps, 450 } 451 err = p.updateToServicesPolicies(bazSvcID, bazSvc, nil) 452 assert.NoError(t, err) 453 assert.Empty(t, policyAdd) 454 } 455 456 func Test_hasMatchingToServices(t *testing.T) { 457 type args struct { 458 spec *api.Rule 459 svcID k8s.ServiceID 460 svc *k8s.Service 461 } 462 tests := []struct { 463 name string 464 args args 465 want bool 466 }{ 467 { 468 name: "nil rule", 469 args: args{ 470 spec: nil, 471 svcID: k8s.ServiceID{Name: "test-svc", Namespace: "test-ns"}, 472 svc: &k8s.Service{}, 473 }, 474 want: false, 475 }, 476 { 477 name: "by name and namespace", 478 args: args{ 479 spec: &api.Rule{Egress: []api.EgressRule{ 480 { 481 EgressCommonRule: api.EgressCommonRule{ 482 ToServices: []api.Service{ 483 {K8sService: &api.K8sServiceNamespace{ 484 ServiceName: "test-svc", 485 Namespace: "test-ns", 486 }}, 487 }, 488 }, 489 }, 490 }}, 491 svcID: k8s.ServiceID{Name: "test-svc", Namespace: "test-ns"}, 492 svc: &k8s.Service{}, 493 }, 494 want: true, 495 }, 496 { 497 name: "by name without namespace", 498 args: args{ 499 spec: &api.Rule{Egress: []api.EgressRule{ 500 { 501 EgressCommonRule: api.EgressCommonRule{ 502 ToServices: []api.Service{ 503 {K8sService: &api.K8sServiceNamespace{ 504 ServiceName: "test-svc", 505 Namespace: "", 506 }}, 507 }, 508 }, 509 }, 510 }}, 511 svcID: k8s.ServiceID{Name: "test-svc", Namespace: "test-ns"}, 512 svc: &k8s.Service{}, 513 }, 514 want: true, 515 }, 516 { 517 name: "by name with wrong namespace", 518 args: args{ 519 spec: &api.Rule{Egress: []api.EgressRule{ 520 { 521 EgressCommonRule: api.EgressCommonRule{ 522 ToServices: []api.Service{ 523 { 524 K8sService: &api.K8sServiceNamespace{ 525 ServiceName: "test-svc", 526 Namespace: "test-ns", 527 }, 528 }, 529 }, 530 }, 531 }, 532 }}, 533 svcID: k8s.ServiceID{Name: "test-svc", Namespace: "not-test-ns"}, 534 svc: &k8s.Service{}, 535 }, 536 want: false, 537 }, 538 { 539 name: "invalid namespace-only selector", 540 args: args{ 541 spec: &api.Rule{Egress: []api.EgressRule{ 542 { 543 EgressCommonRule: api.EgressCommonRule{ 544 ToServices: []api.Service{ 545 { 546 K8sService: &api.K8sServiceNamespace{ 547 ServiceName: "", 548 Namespace: "test-ns", 549 }, 550 }, 551 }, 552 }, 553 }, 554 }}, 555 svcID: k8s.ServiceID{Name: "test-svc", Namespace: "test-ns"}, 556 svc: &k8s.Service{}, 557 }, 558 want: false, 559 }, 560 { 561 name: "empty selector", 562 args: args{ 563 spec: &api.Rule{Egress: []api.EgressRule{ 564 { 565 EgressCommonRule: api.EgressCommonRule{ 566 ToServices: []api.Service{ 567 { 568 K8sService: &api.K8sServiceNamespace{ 569 ServiceName: "", 570 Namespace: "", 571 }, 572 }, 573 }, 574 }, 575 }, 576 }}, 577 svcID: k8s.ServiceID{Name: "test-svc", Namespace: "test-ns"}, 578 svc: &k8s.Service{}, 579 }, 580 want: false, 581 }, 582 { 583 name: "second selector", 584 args: args{ 585 spec: &api.Rule{Egress: []api.EgressRule{ 586 { 587 EgressCommonRule: api.EgressCommonRule{ 588 ToServices: []api.Service{ 589 { 590 K8sService: &api.K8sServiceNamespace{ 591 ServiceName: "foo-svc", 592 Namespace: "", 593 }, 594 }, 595 { 596 K8sService: &api.K8sServiceNamespace{ 597 ServiceName: "test-svc", 598 Namespace: "test-ns", 599 }, 600 }, 601 }, 602 }, 603 }, 604 }}, 605 svcID: k8s.ServiceID{Name: "test-svc", Namespace: "test-ns"}, 606 svc: &k8s.Service{}, 607 }, 608 want: true, 609 }, 610 { 611 name: "by label", 612 args: args{ 613 spec: &api.Rule{Egress: []api.EgressRule{ 614 { 615 EgressCommonRule: api.EgressCommonRule{ 616 ToServices: []api.Service{ 617 { 618 K8sServiceSelector: &api.K8sServiceSelectorNamespace{ 619 Selector: api.ServiceSelector(api.NewESFromMatchRequirements(map[string]string{ 620 "foo": "bar", 621 }, nil)), 622 }, 623 }, 624 }, 625 }, 626 }, 627 }}, 628 svcID: k8s.ServiceID{Name: "test-svc", Namespace: "test-ns"}, 629 svc: &k8s.Service{Labels: map[string]string{"foo": "bar", "baz": "qux"}}, 630 }, 631 want: true, 632 }, 633 { 634 name: "by label requirements", 635 args: args{ 636 spec: &api.Rule{Egress: []api.EgressRule{ 637 { 638 EgressCommonRule: api.EgressCommonRule{ 639 ToServices: []api.Service{ 640 { 641 K8sServiceSelector: &api.K8sServiceSelectorNamespace{ 642 Selector: api.ServiceSelector(api.NewESFromMatchRequirements(nil, []slim_metav1.LabelSelectorRequirement{ 643 {Key: "foo", Operator: "Exists"}, 644 })), 645 }, 646 }, 647 }, 648 }, 649 }, 650 }}, 651 svcID: k8s.ServiceID{Name: "test-svc", Namespace: "test-ns"}, 652 svc: &k8s.Service{Labels: map[string]string{"foo": "bar", "baz": "qux"}}, 653 }, 654 want: true, 655 }, 656 { 657 name: "overspecific label selector", 658 args: args{ 659 spec: &api.Rule{Egress: []api.EgressRule{ 660 { 661 EgressCommonRule: api.EgressCommonRule{ 662 ToServices: []api.Service{ 663 { 664 K8sServiceSelector: &api.K8sServiceSelectorNamespace{ 665 Selector: api.ServiceSelector(api.NewESFromMatchRequirements(map[string]string{ 666 "foo": "bar", 667 "not": "present", 668 }, nil)), 669 }, 670 }, 671 }, 672 }, 673 }, 674 }}, 675 svcID: k8s.ServiceID{Name: "test-svc", Namespace: "test-ns"}, 676 svc: &k8s.Service{Labels: map[string]string{"foo": "bar", "baz": "qux"}}, 677 }, 678 want: false, 679 }, 680 { 681 name: "by label with wrong namespace", 682 args: args{ 683 spec: &api.Rule{Egress: []api.EgressRule{ 684 { 685 EgressCommonRule: api.EgressCommonRule{ 686 ToServices: []api.Service{ 687 { 688 K8sServiceSelector: &api.K8sServiceSelectorNamespace{ 689 Selector: api.ServiceSelector(api.NewESFromMatchRequirements(map[string]string{ 690 "foo": "bar", 691 }, nil)), 692 Namespace: "not-test-ns", 693 }, 694 }, 695 }, 696 }, 697 }, 698 }}, 699 svcID: k8s.ServiceID{Name: "test-svc", Namespace: "test-ns"}, 700 svc: &k8s.Service{Labels: map[string]string{"foo": "bar", "baz": "qux"}}, 701 }, 702 want: false, 703 }, 704 { 705 name: "by label takes precedence over by name", 706 args: args{ 707 spec: &api.Rule{Egress: []api.EgressRule{ 708 { 709 EgressCommonRule: api.EgressCommonRule{ 710 ToServices: []api.Service{ 711 { 712 K8sService: &api.K8sServiceNamespace{ 713 ServiceName: "test-svc", 714 Namespace: "test-ns", 715 }, 716 K8sServiceSelector: &api.K8sServiceSelectorNamespace{ 717 Selector: api.ServiceSelector(api.NewESFromMatchRequirements(map[string]string{ 718 "no": "match", 719 }, nil)), 720 }, 721 }, 722 }, 723 }, 724 }, 725 }}, 726 svcID: k8s.ServiceID{Name: "test-svc", Namespace: "test-ns"}, 727 svc: &k8s.Service{Labels: map[string]string{"foo": "bar", "baz": "qux"}}, 728 }, 729 want: false, 730 }, 731 } 732 for _, tt := range tests { 733 t.Run(tt.name, func(t *testing.T) { 734 assert.Equalf(t, tt.want, hasMatchingToServices(tt.args.spec, tt.args.svcID, tt.args.svc), "hasMatchingToServices(%v, %v, %v)", tt.args.spec, tt.args.svcID, tt.args.svc) 735 }) 736 } 737 } 738 739 func Test_serviceNotificationsQueue(t *testing.T) { 740 ctx, cancel := context.WithCancel(context.Background()) 741 defer cancel() 742 743 upstream := make(chan k8s.ServiceNotification) 744 downstream := serviceNotificationsQueue(ctx, stream.FromChannel(upstream)) 745 746 // Test that sending events in upstream does not block on unbuffered channel 747 upstream <- k8s.ServiceNotification{ID: k8s.ServiceID{Name: "svc1"}} 748 upstream <- k8s.ServiceNotification{ID: k8s.ServiceID{Name: "svc2"}} 749 upstream <- k8s.ServiceNotification{ID: k8s.ServiceID{Name: "svc3"}} 750 751 // Test that events are received in order 752 require.Equal(t, k8s.ServiceNotification{ID: k8s.ServiceID{Name: "svc1"}}, <-downstream) 753 require.Equal(t, k8s.ServiceNotification{ID: k8s.ServiceID{Name: "svc2"}}, <-downstream) 754 require.Equal(t, k8s.ServiceNotification{ID: k8s.ServiceID{Name: "svc3"}}, <-downstream) 755 require.Empty(t, downstream) 756 757 // Test that Go routine exits on empty upstream if ctx is cancelled 758 cancel() 759 _, ok := <-downstream 760 require.False(t, ok, "service notification channel was not closed on cancellation") 761 762 // Test that Go routine exits on upstream close 763 ctx, cancel = context.WithCancel(context.Background()) 764 defer cancel() 765 upstream = make(chan k8s.ServiceNotification) 766 downstream = serviceNotificationsQueue(ctx, stream.FromChannel(upstream)) 767 768 upstream <- k8s.ServiceNotification{ID: k8s.ServiceID{Name: "svc4"}} 769 require.Equal(t, k8s.ServiceNotification{ID: k8s.ServiceID{Name: "svc4"}}, <-downstream) 770 771 close(upstream) 772 _, ok = <-downstream 773 require.False(t, ok, "service notification channel was not closed on upstream close") 774 }