github.com/cilium/cilium@v1.16.2/pkg/bgpv1/manager/reconcilerv2/service_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package reconcilerv2
     5  
     6  import (
     7  	"context"
     8  	"net/netip"
     9  	"testing"
    10  
    11  	"github.com/sirupsen/logrus"
    12  	"github.com/stretchr/testify/require"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  
    15  	"k8s.io/utils/ptr"
    16  
    17  	"github.com/cilium/cilium/pkg/bgpv1/manager/instance"
    18  	"github.com/cilium/cilium/pkg/bgpv1/manager/store"
    19  	"github.com/cilium/cilium/pkg/bgpv1/types"
    20  	cmtypes "github.com/cilium/cilium/pkg/clustermesh/types"
    21  	"github.com/cilium/cilium/pkg/k8s"
    22  	v2api "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    23  	"github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1"
    24  	"github.com/cilium/cilium/pkg/k8s/resource"
    25  	slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1"
    26  	slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1"
    27  )
    28  
    29  var (
    30  	serviceReconcilerTestLogger = logrus.WithField("unit_test", "reconcilerv2_service")
    31  )
    32  
    33  var (
    34  	redSvcKey           = resource.Key{Name: "red-svc", Namespace: "non-default"}
    35  	redSvcSelector      = &slim_metav1.LabelSelector{MatchLabels: map[string]string{"color": "red"}}
    36  	mismatchSvcSelector = &slim_metav1.LabelSelector{MatchLabels: map[string]string{"color": "blue"}}
    37  	ingressV4           = "192.168.0.1"
    38  	ingressV4Prefix     = "192.168.0.1/32"
    39  	externalV4          = "192.168.0.2"
    40  	externalV4Prefix    = "192.168.0.2/32"
    41  	clusterV4           = "192.168.0.3"
    42  	clusterV4Prefix     = "192.168.0.3/32"
    43  	ingressV6           = "2001:db8::1"
    44  	ingressV6Prefix     = "2001:db8::1/128"
    45  	externalV6          = "2001:db8::2"
    46  	externalV6Prefix    = "2001:db8::2/128"
    47  	clusterV6           = "2001:db8::3"
    48  	clusterV6Prefix     = "2001:db8::3/128"
    49  
    50  	redLBSvc = &slim_corev1.Service{
    51  		ObjectMeta: slim_metav1.ObjectMeta{
    52  			Name:      redSvcKey.Name,
    53  			Namespace: redSvcKey.Namespace,
    54  			Labels:    redSvcSelector.MatchLabels,
    55  		},
    56  		Spec: slim_corev1.ServiceSpec{
    57  			Type: slim_corev1.ServiceTypeLoadBalancer,
    58  		},
    59  		Status: slim_corev1.ServiceStatus{
    60  			LoadBalancer: slim_corev1.LoadBalancerStatus{
    61  				Ingress: []slim_corev1.LoadBalancerIngress{
    62  					{
    63  						IP: ingressV4,
    64  					},
    65  					{
    66  						IP: ingressV6,
    67  					},
    68  				},
    69  			},
    70  		},
    71  	}
    72  	redLBSvcWithETP = func(eTP slim_corev1.ServiceExternalTrafficPolicy) *slim_corev1.Service {
    73  		cp := redLBSvc.DeepCopy()
    74  		cp.Spec.ExternalTrafficPolicy = eTP
    75  		return cp
    76  	}
    77  
    78  	redPeer65001v4LBRPName = PolicyName("red-peer-65001", "ipv4", v2alpha1.BGPServiceAdvert, "red-svc-non-default-LoadBalancerIP")
    79  	redPeer65001v4LBRP     = &types.RoutePolicy{
    80  		Name: redPeer65001v4LBRPName,
    81  		Type: types.RoutePolicyTypeExport,
    82  		Statements: []*types.RoutePolicyStatement{
    83  			{
    84  				Conditions: types.RoutePolicyConditions{
    85  					MatchNeighbors: []string{"10.10.10.1/32"},
    86  					MatchPrefixes: []*types.RoutePolicyPrefixMatch{
    87  						{
    88  							CIDR:         netip.MustParsePrefix(ingressV4Prefix),
    89  							PrefixLenMin: 32,
    90  							PrefixLenMax: 32,
    91  						},
    92  					},
    93  				},
    94  				Actions: types.RoutePolicyActions{
    95  					RouteAction:    types.RoutePolicyActionAccept,
    96  					AddCommunities: []string{"65535:65281"},
    97  				},
    98  			},
    99  		},
   100  	}
   101  
   102  	redPeer65001v6LBRPName = PolicyName("red-peer-65001", "ipv6", v2alpha1.BGPServiceAdvert, "red-svc-non-default-LoadBalancerIP")
   103  	redPeer65001v6LBRP     = &types.RoutePolicy{
   104  		Name: redPeer65001v6LBRPName,
   105  		Type: types.RoutePolicyTypeExport,
   106  		Statements: []*types.RoutePolicyStatement{
   107  			{
   108  				Conditions: types.RoutePolicyConditions{
   109  					MatchNeighbors: []string{"10.10.10.1/32"},
   110  					MatchPrefixes: []*types.RoutePolicyPrefixMatch{
   111  						{
   112  							CIDR:         netip.MustParsePrefix(ingressV6Prefix),
   113  							PrefixLenMin: 128,
   114  							PrefixLenMax: 128,
   115  						},
   116  					},
   117  				},
   118  				Actions: types.RoutePolicyActions{
   119  					RouteAction:    types.RoutePolicyActionAccept,
   120  					AddCommunities: []string{"65535:65281"},
   121  				},
   122  			},
   123  		},
   124  	}
   125  
   126  	redExternalSvc = &slim_corev1.Service{
   127  		ObjectMeta: slim_metav1.ObjectMeta{
   128  			Name:      redSvcKey.Name,
   129  			Namespace: redSvcKey.Namespace,
   130  			Labels:    redSvcSelector.MatchLabels,
   131  		},
   132  		Spec: slim_corev1.ServiceSpec{
   133  			Type: slim_corev1.ServiceTypeClusterIP,
   134  			ExternalIPs: []string{
   135  				externalV4,
   136  				externalV6,
   137  			},
   138  		},
   139  	}
   140  
   141  	redExternalSvcWithETP = func(eTP slim_corev1.ServiceExternalTrafficPolicy) *slim_corev1.Service {
   142  		cp := redExternalSvc.DeepCopy()
   143  		cp.Spec.ExternalTrafficPolicy = eTP
   144  		return cp
   145  	}
   146  
   147  	redPeer65001v4ExtRPName = PolicyName("red-peer-65001", "ipv4", v2alpha1.BGPServiceAdvert, "red-svc-non-default-ExternalIP")
   148  	redPeer65001v4ExtRP     = &types.RoutePolicy{
   149  		Name: redPeer65001v4ExtRPName,
   150  		Type: types.RoutePolicyTypeExport,
   151  		Statements: []*types.RoutePolicyStatement{
   152  			{
   153  				Conditions: types.RoutePolicyConditions{
   154  					MatchNeighbors: []string{"10.10.10.1/32"},
   155  					MatchPrefixes: []*types.RoutePolicyPrefixMatch{
   156  						{
   157  							CIDR:         netip.MustParsePrefix(externalV4Prefix),
   158  							PrefixLenMin: 32,
   159  							PrefixLenMax: 32,
   160  						},
   161  					},
   162  				},
   163  				Actions: types.RoutePolicyActions{
   164  					RouteAction:    types.RoutePolicyActionAccept,
   165  					AddCommunities: []string{"65535:65281"},
   166  				},
   167  			},
   168  		},
   169  	}
   170  
   171  	redPeer65001v6ExtRPName = PolicyName("red-peer-65001", "ipv6", v2alpha1.BGPServiceAdvert, "red-svc-non-default-ExternalIP")
   172  	redPeer65001v6ExtRP     = &types.RoutePolicy{
   173  		Name: redPeer65001v6ExtRPName,
   174  		Type: types.RoutePolicyTypeExport,
   175  		Statements: []*types.RoutePolicyStatement{
   176  			{
   177  				Conditions: types.RoutePolicyConditions{
   178  					MatchNeighbors: []string{"10.10.10.1/32"},
   179  					MatchPrefixes: []*types.RoutePolicyPrefixMatch{
   180  						{
   181  							CIDR:         netip.MustParsePrefix(externalV6Prefix),
   182  							PrefixLenMin: 128,
   183  							PrefixLenMax: 128,
   184  						},
   185  					},
   186  				},
   187  				Actions: types.RoutePolicyActions{
   188  					RouteAction:    types.RoutePolicyActionAccept,
   189  					AddCommunities: []string{"65535:65281"},
   190  				},
   191  			},
   192  		},
   193  	}
   194  
   195  	redClusterSvc = &slim_corev1.Service{
   196  		ObjectMeta: slim_metav1.ObjectMeta{
   197  			Name:      redSvcKey.Name,
   198  			Namespace: redSvcKey.Namespace,
   199  			Labels:    redSvcSelector.MatchLabels,
   200  		},
   201  		Spec: slim_corev1.ServiceSpec{
   202  			Type:      slim_corev1.ServiceTypeClusterIP,
   203  			ClusterIP: clusterV4,
   204  			ClusterIPs: []string{
   205  				clusterV4,
   206  				clusterV6,
   207  			},
   208  		},
   209  	}
   210  
   211  	redClusterSvcWithITP = func(iTP slim_corev1.ServiceInternalTrafficPolicy) *slim_corev1.Service {
   212  		cp := redClusterSvc.DeepCopy()
   213  		cp.Spec.InternalTrafficPolicy = &iTP
   214  		return cp
   215  	}
   216  
   217  	redPeer65001v4ClusterRPName = PolicyName("red-peer-65001", "ipv4", v2alpha1.BGPServiceAdvert, "red-svc-non-default-ClusterIP")
   218  	redPeer65001v4ClusterRP     = &types.RoutePolicy{
   219  		Name: redPeer65001v4ClusterRPName,
   220  		Type: types.RoutePolicyTypeExport,
   221  		Statements: []*types.RoutePolicyStatement{
   222  			{
   223  				Conditions: types.RoutePolicyConditions{
   224  					MatchNeighbors: []string{"10.10.10.1/32"},
   225  					MatchPrefixes: []*types.RoutePolicyPrefixMatch{
   226  						{
   227  							CIDR:         netip.MustParsePrefix(clusterV4Prefix),
   228  							PrefixLenMin: 32,
   229  							PrefixLenMax: 32,
   230  						},
   231  					},
   232  				},
   233  				Actions: types.RoutePolicyActions{
   234  					RouteAction:    types.RoutePolicyActionAccept,
   235  					AddCommunities: []string{"65535:65281"},
   236  				},
   237  			},
   238  		},
   239  	}
   240  
   241  	redPeer65001v6ClusterRPName = PolicyName("red-peer-65001", "ipv6", v2alpha1.BGPServiceAdvert, "red-svc-non-default-ClusterIP")
   242  	redPeer65001v6ClusterRP     = &types.RoutePolicy{
   243  		Name: redPeer65001v6ClusterRPName,
   244  		Type: types.RoutePolicyTypeExport,
   245  		Statements: []*types.RoutePolicyStatement{
   246  			{
   247  				Conditions: types.RoutePolicyConditions{
   248  					MatchNeighbors: []string{"10.10.10.1/32"},
   249  					MatchPrefixes: []*types.RoutePolicyPrefixMatch{
   250  						{
   251  							CIDR:         netip.MustParsePrefix(clusterV6Prefix),
   252  							PrefixLenMin: 128,
   253  							PrefixLenMax: 128,
   254  						},
   255  					},
   256  				},
   257  				Actions: types.RoutePolicyActions{
   258  					RouteAction:    types.RoutePolicyActionAccept,
   259  					AddCommunities: []string{"65535:65281"},
   260  				},
   261  			},
   262  		},
   263  	}
   264  
   265  	redExternalAndClusterSvc = &slim_corev1.Service{
   266  		ObjectMeta: slim_metav1.ObjectMeta{
   267  			Name:      redSvcKey.Name,
   268  			Namespace: redSvcKey.Namespace,
   269  			Labels:    redSvcSelector.MatchLabels,
   270  		},
   271  		Spec: slim_corev1.ServiceSpec{
   272  			Type:      slim_corev1.ServiceTypeClusterIP,
   273  			ClusterIP: clusterV4,
   274  			ClusterIPs: []string{
   275  				clusterV4,
   276  				clusterV6,
   277  			},
   278  			ExternalIPs: []string{
   279  				externalV4,
   280  				externalV6,
   281  			},
   282  		},
   283  	}
   284  
   285  	redExternalAndClusterSvcWithITP = func(svc *slim_corev1.Service, iTP slim_corev1.ServiceInternalTrafficPolicy) *slim_corev1.Service {
   286  		cp := svc.DeepCopy()
   287  		cp.Spec.InternalTrafficPolicy = &iTP
   288  		return cp
   289  	}
   290  
   291  	redExternalAndClusterSvcWithETP = func(svc *slim_corev1.Service, eTP slim_corev1.ServiceExternalTrafficPolicy) *slim_corev1.Service {
   292  		cp := svc.DeepCopy()
   293  		cp.Spec.ExternalTrafficPolicy = eTP
   294  		return cp
   295  	}
   296  
   297  	redSvcAdvert = &v2alpha1.CiliumBGPAdvertisement{
   298  		ObjectMeta: metav1.ObjectMeta{
   299  			Name: "red-podCIDR-advertisement",
   300  			Labels: map[string]string{
   301  				"advertise": "red_bgp",
   302  			},
   303  		},
   304  	}
   305  
   306  	redSvcAdvertWithAdvertisements = func(adverts ...v2alpha1.BGPAdvertisement) *v2alpha1.CiliumBGPAdvertisement {
   307  		cp := redSvcAdvert.DeepCopy()
   308  		cp.Spec.Advertisements = adverts
   309  		return cp
   310  	}
   311  
   312  	lbSvcAdvert = v2alpha1.BGPAdvertisement{
   313  		AdvertisementType: v2alpha1.BGPServiceAdvert,
   314  		Service: &v2alpha1.BGPServiceOptions{
   315  			Addresses: []v2alpha1.BGPServiceAddressType{v2alpha1.BGPLoadBalancerIPAddr},
   316  		},
   317  		Attributes: &v2alpha1.BGPAttributes{
   318  			Communities: &v2alpha1.BGPCommunities{
   319  				Standard:  []v2alpha1.BGPStandardCommunity{"65535:65281"},
   320  				WellKnown: []v2alpha1.BGPWellKnownCommunity{"no-export"},
   321  			},
   322  		},
   323  	}
   324  	lbSvcAdvertWithSelector = func(selector *slim_metav1.LabelSelector) v2alpha1.BGPAdvertisement {
   325  		cp := lbSvcAdvert.DeepCopy()
   326  		cp.Selector = selector
   327  		return *cp
   328  	}
   329  
   330  	externalSvcAdvert = v2alpha1.BGPAdvertisement{
   331  		AdvertisementType: v2alpha1.BGPServiceAdvert,
   332  		Service: &v2alpha1.BGPServiceOptions{
   333  			Addresses: []v2alpha1.BGPServiceAddressType{v2alpha1.BGPExternalIPAddr},
   334  		},
   335  		Attributes: &v2alpha1.BGPAttributes{
   336  			Communities: &v2alpha1.BGPCommunities{
   337  				Standard:  []v2alpha1.BGPStandardCommunity{"65535:65281"},
   338  				WellKnown: []v2alpha1.BGPWellKnownCommunity{"no-export"},
   339  			},
   340  		},
   341  	}
   342  
   343  	externalSvcAdvertWithSelector = func(selector *slim_metav1.LabelSelector) v2alpha1.BGPAdvertisement {
   344  		cp := externalSvcAdvert.DeepCopy()
   345  		cp.Selector = selector
   346  		return *cp
   347  	}
   348  
   349  	clusterIPSvcAdvert = v2alpha1.BGPAdvertisement{
   350  		AdvertisementType: v2alpha1.BGPServiceAdvert,
   351  		Service: &v2alpha1.BGPServiceOptions{
   352  			Addresses: []v2alpha1.BGPServiceAddressType{v2alpha1.BGPClusterIPAddr},
   353  		},
   354  		Attributes: &v2alpha1.BGPAttributes{
   355  			Communities: &v2alpha1.BGPCommunities{
   356  				Standard:  []v2alpha1.BGPStandardCommunity{"65535:65281"},
   357  				WellKnown: []v2alpha1.BGPWellKnownCommunity{"no-export"},
   358  			},
   359  		},
   360  	}
   361  
   362  	clusterIPSvcAdvertWithSelector = func(selector *slim_metav1.LabelSelector) v2alpha1.BGPAdvertisement {
   363  		cp := clusterIPSvcAdvert.DeepCopy()
   364  		cp.Selector = selector
   365  		return *cp
   366  	}
   367  
   368  	testBGPInstanceConfig = &v2alpha1.CiliumBGPNodeInstance{
   369  		Name:     "bgp-65001",
   370  		LocalASN: ptr.To[int64](65001),
   371  		Peers: []v2alpha1.CiliumBGPNodePeer{
   372  			{
   373  				Name:        "red-peer-65001",
   374  				PeerAddress: ptr.To[string]("10.10.10.1"),
   375  				PeerConfigRef: &v2alpha1.PeerConfigReference{
   376  					Group: "cilium.io",
   377  					Kind:  "CiliumBGPPeerConfig",
   378  					Name:  "peer-config-red",
   379  				},
   380  			},
   381  		},
   382  	}
   383  
   384  	testCiliumNodeConfig = &v2api.CiliumNode{
   385  		ObjectMeta: metav1.ObjectMeta{
   386  			Name: "node1",
   387  		},
   388  	}
   389  
   390  	eps1Local = &k8s.Endpoints{
   391  		ObjectMeta: slim_metav1.ObjectMeta{
   392  			Name:      "svc-1",
   393  			Namespace: "non-default",
   394  		},
   395  		EndpointSliceID: k8s.EndpointSliceID{
   396  			ServiceID: k8s.ServiceID{
   397  				Name:      redSvcKey.Name,
   398  				Namespace: redSvcKey.Namespace,
   399  			},
   400  			EndpointSliceName: "svc-1",
   401  		},
   402  		Backends: map[cmtypes.AddrCluster]*k8s.Backend{
   403  			cmtypes.MustParseAddrCluster("10.0.0.1"): {
   404  				NodeName: "node1",
   405  			},
   406  			cmtypes.MustParseAddrCluster("2001:db8:1000::1"): {
   407  				NodeName: "node1",
   408  			},
   409  		},
   410  	}
   411  
   412  	eps1LocalTerminating = &k8s.Endpoints{
   413  		ObjectMeta: slim_metav1.ObjectMeta{
   414  			Name:      "svc-1",
   415  			Namespace: "non-default",
   416  		},
   417  		EndpointSliceID: k8s.EndpointSliceID{
   418  			ServiceID: k8s.ServiceID{
   419  				Name:      redSvcKey.Name,
   420  				Namespace: redSvcKey.Namespace,
   421  			},
   422  			EndpointSliceName: "svc-1",
   423  		},
   424  		Backends: map[cmtypes.AddrCluster]*k8s.Backend{
   425  			cmtypes.MustParseAddrCluster("10.0.0.1"): {
   426  				NodeName:    "node1",
   427  				Terminating: true,
   428  			},
   429  			cmtypes.MustParseAddrCluster("2001:db8:1000::1"): {
   430  				NodeName:    "node1",
   431  				Terminating: true,
   432  			},
   433  		},
   434  	}
   435  
   436  	eps1Remote = &k8s.Endpoints{
   437  		ObjectMeta: slim_metav1.ObjectMeta{
   438  			Name:      "svc-1",
   439  			Namespace: "default",
   440  		},
   441  		EndpointSliceID: k8s.EndpointSliceID{
   442  			ServiceID: k8s.ServiceID{
   443  				Name:      redSvcKey.Name,
   444  				Namespace: redSvcKey.Namespace,
   445  			},
   446  			EndpointSliceName: "svc-1",
   447  		},
   448  		Backends: map[cmtypes.AddrCluster]*k8s.Backend{
   449  			cmtypes.MustParseAddrCluster("10.0.0.2"): {
   450  				NodeName: "node2",
   451  			},
   452  			cmtypes.MustParseAddrCluster("2001:db8:1000::2"): {
   453  				NodeName: "node2",
   454  			},
   455  		},
   456  	}
   457  
   458  	eps1Mixed = &k8s.Endpoints{
   459  		ObjectMeta: slim_metav1.ObjectMeta{
   460  			Name:      "svc-1",
   461  			Namespace: "default",
   462  		},
   463  		EndpointSliceID: k8s.EndpointSliceID{
   464  			ServiceID: k8s.ServiceID{
   465  				Name:      redSvcKey.Name,
   466  				Namespace: redSvcKey.Namespace,
   467  			},
   468  			EndpointSliceName: "svc-1",
   469  		},
   470  		Backends: map[cmtypes.AddrCluster]*k8s.Backend{
   471  			cmtypes.MustParseAddrCluster("10.0.0.1"): {
   472  				NodeName: "node1",
   473  			},
   474  			cmtypes.MustParseAddrCluster("10.0.0.2"): {
   475  				NodeName: "node2",
   476  			},
   477  			cmtypes.MustParseAddrCluster("2001:db8:1000::1"): {
   478  				NodeName: "node1",
   479  			},
   480  			cmtypes.MustParseAddrCluster("2001:db8:1000::2"): {
   481  				NodeName: "node2",
   482  			},
   483  		},
   484  	}
   485  )
   486  
   487  // Test_ServiceLBReconciler tests reconciliation of service of type load-balancer
   488  func Test_ServiceLBReconciler(t *testing.T) {
   489  	logrus.SetLevel(logrus.DebugLevel)
   490  
   491  	tests := []struct {
   492  		name             string
   493  		peerConfig       []*v2alpha1.CiliumBGPPeerConfig
   494  		advertisements   []*v2alpha1.CiliumBGPAdvertisement
   495  		services         []*slim_corev1.Service
   496  		endpoints        []*k8s.Endpoints
   497  		expectedMetadata ServiceReconcilerMetadata
   498  	}{
   499  		{
   500  			name:           "Service (LB) with advertisement( empty )",
   501  			peerConfig:     []*v2alpha1.CiliumBGPPeerConfig{redPeerConfig},
   502  			services:       []*slim_corev1.Service{redLBSvc},
   503  			advertisements: nil,
   504  			expectedMetadata: ServiceReconcilerMetadata{
   505  				ServicePaths:         ResourceAFPathsMap{},
   506  				ServiceRoutePolicies: ResourceRoutePolicyMap{},
   507  				ServiceAdvertisements: PeerAdvertisements{
   508  					"red-peer-65001": PeerFamilyAdvertisements{
   509  						{Afi: "ipv4", Safi: "unicast"}: nil,
   510  						{Afi: "ipv6", Safi: "unicast"}: nil,
   511  					},
   512  				},
   513  			},
   514  		},
   515  		{
   516  			name:       "Service (LB) with advertisement(LB) - mismatch labels",
   517  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{redPeerConfig},
   518  			services:   []*slim_corev1.Service{redLBSvc},
   519  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
   520  				redSvcAdvertWithAdvertisements(lbSvcAdvertWithSelector(mismatchSvcSelector)),
   521  			},
   522  			expectedMetadata: ServiceReconcilerMetadata{
   523  				ServicePaths:         ResourceAFPathsMap{},
   524  				ServiceRoutePolicies: ResourceRoutePolicyMap{},
   525  				ServiceAdvertisements: PeerAdvertisements{
   526  					"red-peer-65001": PeerFamilyAdvertisements{
   527  						{Afi: "ipv4", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   528  							lbSvcAdvertWithSelector(mismatchSvcSelector),
   529  						},
   530  						{Afi: "ipv6", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   531  							lbSvcAdvertWithSelector(mismatchSvcSelector),
   532  						},
   533  					},
   534  				},
   535  			},
   536  		},
   537  		{
   538  			name:       "Service (LB) with advertisement(LB) - matching labels (eTP=cluster)",
   539  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{redPeerConfig},
   540  			services:   []*slim_corev1.Service{redLBSvcWithETP(slim_corev1.ServiceExternalTrafficPolicyCluster)},
   541  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
   542  				redSvcAdvertWithAdvertisements(lbSvcAdvertWithSelector(redSvcSelector)),
   543  			},
   544  			expectedMetadata: ServiceReconcilerMetadata{
   545  				ServicePaths: ResourceAFPathsMap{
   546  					redSvcKey: AFPathsMap{
   547  						{Afi: types.AfiIPv4, Safi: types.SafiUnicast}: {
   548  							ingressV4Prefix: types.NewPathForPrefix(netip.MustParsePrefix(ingressV4Prefix)),
   549  						},
   550  						{Afi: types.AfiIPv6, Safi: types.SafiUnicast}: {
   551  							ingressV6Prefix: types.NewPathForPrefix(netip.MustParsePrefix(ingressV6Prefix)),
   552  						},
   553  					},
   554  				},
   555  				ServiceRoutePolicies: ResourceRoutePolicyMap{
   556  					redSvcKey: RoutePolicyMap{
   557  						redPeer65001v4LBRPName: redPeer65001v4LBRP,
   558  						redPeer65001v6LBRPName: redPeer65001v6LBRP,
   559  					},
   560  				},
   561  				ServiceAdvertisements: PeerAdvertisements{
   562  					"red-peer-65001": PeerFamilyAdvertisements{
   563  						{Afi: "ipv4", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   564  							lbSvcAdvertWithSelector(redSvcSelector),
   565  						},
   566  						{Afi: "ipv6", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   567  							lbSvcAdvertWithSelector(redSvcSelector),
   568  						},
   569  					},
   570  				},
   571  			},
   572  		},
   573  		{
   574  			name:       "Service (LB) with advertisement(LB) - matching labels (eTP=local, ep on node)",
   575  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{redPeerConfig},
   576  			services:   []*slim_corev1.Service{redLBSvcWithETP(slim_corev1.ServiceExternalTrafficPolicyLocal)},
   577  			endpoints:  []*k8s.Endpoints{eps1Local},
   578  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
   579  				redSvcAdvertWithAdvertisements(lbSvcAdvertWithSelector(redSvcSelector)),
   580  			},
   581  			expectedMetadata: ServiceReconcilerMetadata{
   582  				ServicePaths: ResourceAFPathsMap{
   583  					redSvcKey: AFPathsMap{
   584  						{Afi: types.AfiIPv4, Safi: types.SafiUnicast}: {
   585  							ingressV4Prefix: types.NewPathForPrefix(netip.MustParsePrefix(ingressV4Prefix)),
   586  						},
   587  						{Afi: types.AfiIPv6, Safi: types.SafiUnicast}: {
   588  							ingressV6Prefix: types.NewPathForPrefix(netip.MustParsePrefix(ingressV6Prefix)),
   589  						},
   590  					},
   591  				},
   592  				ServiceRoutePolicies: ResourceRoutePolicyMap{
   593  					redSvcKey: RoutePolicyMap{
   594  						redPeer65001v4LBRPName: redPeer65001v4LBRP,
   595  						redPeer65001v6LBRPName: redPeer65001v6LBRP,
   596  					},
   597  				},
   598  				ServiceAdvertisements: PeerAdvertisements{
   599  					"red-peer-65001": PeerFamilyAdvertisements{
   600  						{Afi: "ipv4", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   601  							lbSvcAdvertWithSelector(redSvcSelector),
   602  						},
   603  						{Afi: "ipv6", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   604  							lbSvcAdvertWithSelector(redSvcSelector),
   605  						},
   606  					},
   607  				},
   608  			},
   609  		},
   610  		{
   611  			name:       "Service (LB) with advertisement(LB) - matching labels (eTP=local, mixed ep)",
   612  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{redPeerConfig},
   613  			services:   []*slim_corev1.Service{redLBSvcWithETP(slim_corev1.ServiceExternalTrafficPolicyLocal)},
   614  			endpoints:  []*k8s.Endpoints{eps1Mixed},
   615  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
   616  				redSvcAdvertWithAdvertisements(lbSvcAdvertWithSelector(redSvcSelector)),
   617  			},
   618  			expectedMetadata: ServiceReconcilerMetadata{
   619  				ServicePaths: ResourceAFPathsMap{
   620  					redSvcKey: AFPathsMap{
   621  						{Afi: types.AfiIPv4, Safi: types.SafiUnicast}: {
   622  							ingressV4Prefix: types.NewPathForPrefix(netip.MustParsePrefix(ingressV4Prefix)),
   623  						},
   624  						{Afi: types.AfiIPv6, Safi: types.SafiUnicast}: {
   625  							ingressV6Prefix: types.NewPathForPrefix(netip.MustParsePrefix(ingressV6Prefix)),
   626  						},
   627  					},
   628  				},
   629  				ServiceRoutePolicies: ResourceRoutePolicyMap{
   630  					redSvcKey: RoutePolicyMap{
   631  						redPeer65001v4LBRPName: redPeer65001v4LBRP,
   632  						redPeer65001v6LBRPName: redPeer65001v6LBRP,
   633  					},
   634  				},
   635  				ServiceAdvertisements: PeerAdvertisements{
   636  					"red-peer-65001": PeerFamilyAdvertisements{
   637  						{Afi: "ipv4", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   638  							lbSvcAdvertWithSelector(redSvcSelector),
   639  						},
   640  						{Afi: "ipv6", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   641  							lbSvcAdvertWithSelector(redSvcSelector),
   642  						},
   643  					},
   644  				},
   645  			},
   646  		},
   647  		{
   648  			name:       "Service (LB) with advertisement(LB) - matching labels (eTP=local, ep on remote)",
   649  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{redPeerConfig},
   650  			services:   []*slim_corev1.Service{redLBSvcWithETP(slim_corev1.ServiceExternalTrafficPolicyLocal)},
   651  			endpoints:  []*k8s.Endpoints{eps1Remote},
   652  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
   653  				redSvcAdvertWithAdvertisements(lbSvcAdvertWithSelector(redSvcSelector)),
   654  			},
   655  			expectedMetadata: ServiceReconcilerMetadata{
   656  				ServicePaths:         ResourceAFPathsMap{},
   657  				ServiceRoutePolicies: ResourceRoutePolicyMap{},
   658  				ServiceAdvertisements: PeerAdvertisements{
   659  					"red-peer-65001": PeerFamilyAdvertisements{
   660  						{Afi: "ipv4", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   661  							lbSvcAdvertWithSelector(redSvcSelector),
   662  						},
   663  						{Afi: "ipv6", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   664  							lbSvcAdvertWithSelector(redSvcSelector),
   665  						},
   666  					},
   667  				},
   668  			},
   669  		},
   670  		{
   671  			name:       "Service (LB) with advertisement(LB) - matching labels (eTP=local, backends are terminating)",
   672  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{redPeerConfig},
   673  			services:   []*slim_corev1.Service{redLBSvcWithETP(slim_corev1.ServiceExternalTrafficPolicyLocal)},
   674  			endpoints:  []*k8s.Endpoints{eps1LocalTerminating},
   675  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
   676  				redSvcAdvertWithAdvertisements(lbSvcAdvertWithSelector(redSvcSelector)),
   677  			},
   678  			expectedMetadata: ServiceReconcilerMetadata{
   679  				ServicePaths:         ResourceAFPathsMap{},
   680  				ServiceRoutePolicies: ResourceRoutePolicyMap{},
   681  				ServiceAdvertisements: PeerAdvertisements{
   682  					"red-peer-65001": PeerFamilyAdvertisements{
   683  						{Afi: "ipv4", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   684  							lbSvcAdvertWithSelector(redSvcSelector),
   685  						},
   686  						{Afi: "ipv6", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   687  							lbSvcAdvertWithSelector(redSvcSelector),
   688  						},
   689  					},
   690  				},
   691  			},
   692  		},
   693  	}
   694  
   695  	for _, tt := range tests {
   696  		t.Run(tt.name, func(t *testing.T) {
   697  			req := require.New(t)
   698  
   699  			params := ServiceReconcilerIn{
   700  				Logger: serviceReconcilerTestLogger,
   701  				PeerAdvert: NewCiliumPeerAdvertisement(
   702  					PeerAdvertisementIn{
   703  						Logger:          podCIDRTestLogger,
   704  						PeerConfigStore: store.InitMockStore[*v2alpha1.CiliumBGPPeerConfig](tt.peerConfig),
   705  						AdvertStore:     store.InitMockStore[*v2alpha1.CiliumBGPAdvertisement](tt.advertisements),
   706  					}),
   707  				SvcDiffStore: store.InitFakeDiffStore[*slim_corev1.Service](tt.services),
   708  				EPDiffStore:  store.InitFakeDiffStore[*k8s.Endpoints](tt.endpoints),
   709  			}
   710  
   711  			svcReconciler := NewServiceReconciler(params).Reconciler.(*ServiceReconciler)
   712  			testBGPInstance := instance.NewFakeBGPInstance()
   713  			svcReconciler.Init(testBGPInstance)
   714  			defer svcReconciler.Cleanup(testBGPInstance)
   715  
   716  			// reconcile twice to validate idempotency
   717  			for i := 0; i < 2; i++ {
   718  				err := svcReconciler.Reconcile(context.Background(), ReconcileParams{
   719  					BGPInstance:   testBGPInstance,
   720  					DesiredConfig: testBGPInstanceConfig,
   721  					CiliumNode:    testCiliumNodeConfig,
   722  				})
   723  				req.NoError(err)
   724  			}
   725  
   726  			// validate new metadata
   727  			serviceMetadataEqual(req, tt.expectedMetadata, svcReconciler.getMetadata(testBGPInstance))
   728  		})
   729  	}
   730  }
   731  
   732  // Test_ServiceExternalIPReconciler tests reconciliation of cluster service with external IP
   733  func Test_ServiceExternalIPReconciler(t *testing.T) {
   734  	logrus.SetLevel(logrus.DebugLevel)
   735  
   736  	tests := []struct {
   737  		name             string
   738  		peerConfig       []*v2alpha1.CiliumBGPPeerConfig
   739  		advertisements   []*v2alpha1.CiliumBGPAdvertisement
   740  		services         []*slim_corev1.Service
   741  		endpoints        []*k8s.Endpoints
   742  		expectedMetadata ServiceReconcilerMetadata
   743  	}{
   744  		{
   745  			name:           "Service (External) with advertisement( empty )",
   746  			peerConfig:     []*v2alpha1.CiliumBGPPeerConfig{redPeerConfig},
   747  			services:       []*slim_corev1.Service{redExternalSvc},
   748  			advertisements: nil,
   749  			expectedMetadata: ServiceReconcilerMetadata{
   750  				ServicePaths:         ResourceAFPathsMap{},
   751  				ServiceRoutePolicies: ResourceRoutePolicyMap{},
   752  				ServiceAdvertisements: PeerAdvertisements{
   753  					"red-peer-65001": PeerFamilyAdvertisements{
   754  						{Afi: "ipv4", Safi: "unicast"}: nil,
   755  						{Afi: "ipv6", Safi: "unicast"}: nil,
   756  					},
   757  				},
   758  			},
   759  		},
   760  		{
   761  			name:       "Service (External) with advertisement(External) - mismatch labels",
   762  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{redPeerConfig},
   763  			services:   []*slim_corev1.Service{redExternalSvc},
   764  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
   765  				redSvcAdvertWithAdvertisements(externalSvcAdvertWithSelector(mismatchSvcSelector)),
   766  			},
   767  			expectedMetadata: ServiceReconcilerMetadata{
   768  				ServicePaths:         ResourceAFPathsMap{},
   769  				ServiceRoutePolicies: ResourceRoutePolicyMap{},
   770  				ServiceAdvertisements: PeerAdvertisements{
   771  					"red-peer-65001": PeerFamilyAdvertisements{
   772  						{Afi: "ipv4", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   773  							externalSvcAdvertWithSelector(mismatchSvcSelector),
   774  						},
   775  						{Afi: "ipv6", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   776  							externalSvcAdvertWithSelector(mismatchSvcSelector),
   777  						},
   778  					},
   779  				},
   780  			},
   781  		},
   782  		{
   783  			name:       "Service (External) with advertisement(External) - matching labels (eTP=cluster)",
   784  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{redPeerConfig},
   785  			services:   []*slim_corev1.Service{redExternalSvcWithETP(slim_corev1.ServiceExternalTrafficPolicyCluster)},
   786  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
   787  				redSvcAdvertWithAdvertisements(externalSvcAdvertWithSelector(redSvcSelector)),
   788  			},
   789  			expectedMetadata: ServiceReconcilerMetadata{
   790  				ServicePaths: ResourceAFPathsMap{
   791  					redSvcKey: AFPathsMap{
   792  						{Afi: types.AfiIPv4, Safi: types.SafiUnicast}: {
   793  							externalV4Prefix: types.NewPathForPrefix(netip.MustParsePrefix(externalV4Prefix)),
   794  						},
   795  						{Afi: types.AfiIPv6, Safi: types.SafiUnicast}: {
   796  							externalV6Prefix: types.NewPathForPrefix(netip.MustParsePrefix(externalV6Prefix)),
   797  						},
   798  					},
   799  				},
   800  				ServiceRoutePolicies: ResourceRoutePolicyMap{
   801  					redSvcKey: RoutePolicyMap{
   802  						redPeer65001v4ExtRPName: redPeer65001v4ExtRP,
   803  						redPeer65001v6ExtRPName: redPeer65001v6ExtRP,
   804  					},
   805  				},
   806  				ServiceAdvertisements: PeerAdvertisements{
   807  					"red-peer-65001": PeerFamilyAdvertisements{
   808  						{Afi: "ipv4", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   809  							externalSvcAdvertWithSelector(redSvcSelector),
   810  						},
   811  						{Afi: "ipv6", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   812  							externalSvcAdvertWithSelector(redSvcSelector),
   813  						},
   814  					},
   815  				},
   816  			},
   817  		},
   818  		{
   819  			name:       "Service (External) with advertisement(External) - matching labels (eTP=local, ep on node)",
   820  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{redPeerConfig},
   821  			services:   []*slim_corev1.Service{redExternalSvcWithETP(slim_corev1.ServiceExternalTrafficPolicyLocal)},
   822  			endpoints:  []*k8s.Endpoints{eps1Local},
   823  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
   824  				redSvcAdvertWithAdvertisements(externalSvcAdvertWithSelector(redSvcSelector)),
   825  			},
   826  			expectedMetadata: ServiceReconcilerMetadata{
   827  				ServicePaths: ResourceAFPathsMap{
   828  					redSvcKey: AFPathsMap{
   829  						{Afi: types.AfiIPv4, Safi: types.SafiUnicast}: {
   830  							externalV4Prefix: types.NewPathForPrefix(netip.MustParsePrefix(externalV4Prefix)),
   831  						},
   832  						{Afi: types.AfiIPv6, Safi: types.SafiUnicast}: {
   833  							externalV6Prefix: types.NewPathForPrefix(netip.MustParsePrefix(externalV6Prefix)),
   834  						},
   835  					},
   836  				},
   837  				ServiceRoutePolicies: ResourceRoutePolicyMap{
   838  					redSvcKey: RoutePolicyMap{
   839  						redPeer65001v4ExtRPName: redPeer65001v4ExtRP,
   840  						redPeer65001v6ExtRPName: redPeer65001v6ExtRP,
   841  					},
   842  				},
   843  				ServiceAdvertisements: PeerAdvertisements{
   844  					"red-peer-65001": PeerFamilyAdvertisements{
   845  						{Afi: "ipv4", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   846  							externalSvcAdvertWithSelector(redSvcSelector),
   847  						},
   848  						{Afi: "ipv6", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   849  							externalSvcAdvertWithSelector(redSvcSelector),
   850  						},
   851  					},
   852  				},
   853  			},
   854  		},
   855  		{
   856  			name:       "Service (External) with advertisement(External) - matching labels (eTP=local, mixed ep)",
   857  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{redPeerConfig},
   858  			services:   []*slim_corev1.Service{redExternalSvcWithETP(slim_corev1.ServiceExternalTrafficPolicyLocal)},
   859  			endpoints:  []*k8s.Endpoints{eps1Mixed},
   860  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
   861  				redSvcAdvertWithAdvertisements(externalSvcAdvertWithSelector(redSvcSelector)),
   862  			},
   863  			expectedMetadata: ServiceReconcilerMetadata{
   864  				ServicePaths: ResourceAFPathsMap{
   865  					redSvcKey: AFPathsMap{
   866  						{Afi: types.AfiIPv4, Safi: types.SafiUnicast}: {
   867  							externalV4Prefix: types.NewPathForPrefix(netip.MustParsePrefix(externalV4Prefix)),
   868  						},
   869  						{Afi: types.AfiIPv6, Safi: types.SafiUnicast}: {
   870  							externalV6Prefix: types.NewPathForPrefix(netip.MustParsePrefix(externalV6Prefix)),
   871  						},
   872  					},
   873  				},
   874  				ServiceRoutePolicies: ResourceRoutePolicyMap{
   875  					redSvcKey: RoutePolicyMap{
   876  						redPeer65001v4ExtRPName: redPeer65001v4ExtRP,
   877  						redPeer65001v6ExtRPName: redPeer65001v6ExtRP,
   878  					},
   879  				},
   880  				ServiceAdvertisements: PeerAdvertisements{
   881  					"red-peer-65001": PeerFamilyAdvertisements{
   882  						{Afi: "ipv4", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   883  							externalSvcAdvertWithSelector(redSvcSelector),
   884  						},
   885  						{Afi: "ipv6", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   886  							externalSvcAdvertWithSelector(redSvcSelector),
   887  						},
   888  					},
   889  				},
   890  			},
   891  		},
   892  		{
   893  			name:       "Service (External) with advertisement(External) - matching labels (eTP=local, ep on remote)",
   894  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{redPeerConfig},
   895  			services:   []*slim_corev1.Service{redExternalSvcWithETP(slim_corev1.ServiceExternalTrafficPolicyLocal)},
   896  			endpoints:  []*k8s.Endpoints{eps1Remote},
   897  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
   898  				redSvcAdvertWithAdvertisements(externalSvcAdvertWithSelector(redSvcSelector)),
   899  			},
   900  			expectedMetadata: ServiceReconcilerMetadata{
   901  				ServicePaths:         ResourceAFPathsMap{},
   902  				ServiceRoutePolicies: ResourceRoutePolicyMap{},
   903  				ServiceAdvertisements: PeerAdvertisements{
   904  					"red-peer-65001": PeerFamilyAdvertisements{
   905  						{Afi: "ipv4", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   906  							externalSvcAdvertWithSelector(redSvcSelector),
   907  						},
   908  						{Afi: "ipv6", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   909  							externalSvcAdvertWithSelector(redSvcSelector),
   910  						},
   911  					},
   912  				},
   913  			},
   914  		},
   915  	}
   916  
   917  	for _, tt := range tests {
   918  		t.Run(tt.name, func(t *testing.T) {
   919  			req := require.New(t)
   920  
   921  			params := ServiceReconcilerIn{
   922  				Logger: serviceReconcilerTestLogger,
   923  				PeerAdvert: NewCiliumPeerAdvertisement(
   924  					PeerAdvertisementIn{
   925  						Logger:          podCIDRTestLogger,
   926  						PeerConfigStore: store.InitMockStore[*v2alpha1.CiliumBGPPeerConfig](tt.peerConfig),
   927  						AdvertStore:     store.InitMockStore[*v2alpha1.CiliumBGPAdvertisement](tt.advertisements),
   928  					}),
   929  				SvcDiffStore: store.InitFakeDiffStore[*slim_corev1.Service](tt.services),
   930  				EPDiffStore:  store.InitFakeDiffStore[*k8s.Endpoints](tt.endpoints),
   931  			}
   932  
   933  			svcReconciler := NewServiceReconciler(params).Reconciler.(*ServiceReconciler)
   934  			testBGPInstance := instance.NewFakeBGPInstance()
   935  			svcReconciler.Init(testBGPInstance)
   936  			defer svcReconciler.Cleanup(testBGPInstance)
   937  
   938  			// reconcile twice to validate idempotency
   939  			for i := 0; i < 2; i++ {
   940  				err := svcReconciler.Reconcile(context.Background(), ReconcileParams{
   941  					BGPInstance:   testBGPInstance,
   942  					DesiredConfig: testBGPInstanceConfig,
   943  					CiliumNode:    testCiliumNodeConfig,
   944  				})
   945  				req.NoError(err)
   946  			}
   947  
   948  			// validate new metadata
   949  			serviceMetadataEqual(req, tt.expectedMetadata, svcReconciler.getMetadata(testBGPInstance))
   950  		})
   951  	}
   952  }
   953  
   954  // Test_ServiceClusterIPReconciler tests reconciliation of cluster service
   955  func Test_ServiceClusterIPReconciler(t *testing.T) {
   956  	logrus.SetLevel(logrus.DebugLevel)
   957  
   958  	tests := []struct {
   959  		name             string
   960  		peerConfig       []*v2alpha1.CiliumBGPPeerConfig
   961  		advertisements   []*v2alpha1.CiliumBGPAdvertisement
   962  		services         []*slim_corev1.Service
   963  		endpoints        []*k8s.Endpoints
   964  		expectedMetadata ServiceReconcilerMetadata
   965  	}{
   966  		{
   967  			name:           "Service (Cluster) with advertisement( empty )",
   968  			peerConfig:     []*v2alpha1.CiliumBGPPeerConfig{redPeerConfig},
   969  			services:       []*slim_corev1.Service{redClusterSvc},
   970  			advertisements: nil,
   971  			expectedMetadata: ServiceReconcilerMetadata{
   972  				ServicePaths:         ResourceAFPathsMap{},
   973  				ServiceRoutePolicies: ResourceRoutePolicyMap{},
   974  				ServiceAdvertisements: PeerAdvertisements{
   975  					"red-peer-65001": PeerFamilyAdvertisements{
   976  						{Afi: "ipv4", Safi: "unicast"}: nil,
   977  						{Afi: "ipv6", Safi: "unicast"}: nil,
   978  					},
   979  				},
   980  			},
   981  		},
   982  		{
   983  			name:       "Service (Cluster) with advertisement(Cluster) - mismatch labels",
   984  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{redPeerConfig},
   985  			services:   []*slim_corev1.Service{redClusterSvc},
   986  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
   987  				redSvcAdvertWithAdvertisements(clusterIPSvcAdvertWithSelector(mismatchSvcSelector)),
   988  			},
   989  			expectedMetadata: ServiceReconcilerMetadata{
   990  				ServicePaths:         ResourceAFPathsMap{},
   991  				ServiceRoutePolicies: ResourceRoutePolicyMap{},
   992  				ServiceAdvertisements: PeerAdvertisements{
   993  					"red-peer-65001": PeerFamilyAdvertisements{
   994  						{Afi: "ipv4", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   995  							clusterIPSvcAdvertWithSelector(mismatchSvcSelector),
   996  						},
   997  						{Afi: "ipv6", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
   998  							clusterIPSvcAdvertWithSelector(mismatchSvcSelector),
   999  						},
  1000  					},
  1001  				},
  1002  			},
  1003  		},
  1004  		{
  1005  			name:       "Service (Cluster) with advertisement(Cluster) - matching labels (iTP=cluster)",
  1006  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{redPeerConfig},
  1007  			services:   []*slim_corev1.Service{redClusterSvcWithITP(slim_corev1.ServiceInternalTrafficPolicyCluster)},
  1008  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
  1009  				redSvcAdvertWithAdvertisements(clusterIPSvcAdvertWithSelector(redSvcSelector)),
  1010  			},
  1011  			expectedMetadata: ServiceReconcilerMetadata{
  1012  				ServicePaths: ResourceAFPathsMap{
  1013  					redSvcKey: AFPathsMap{
  1014  						{Afi: types.AfiIPv4, Safi: types.SafiUnicast}: {
  1015  							clusterV4Prefix: types.NewPathForPrefix(netip.MustParsePrefix(clusterV4Prefix)),
  1016  						},
  1017  						{Afi: types.AfiIPv6, Safi: types.SafiUnicast}: {
  1018  							clusterV6Prefix: types.NewPathForPrefix(netip.MustParsePrefix(clusterV6Prefix)),
  1019  						},
  1020  					},
  1021  				},
  1022  				ServiceRoutePolicies: ResourceRoutePolicyMap{
  1023  					redSvcKey: RoutePolicyMap{
  1024  						redPeer65001v4ClusterRPName: redPeer65001v4ClusterRP,
  1025  						redPeer65001v6ClusterRPName: redPeer65001v6ClusterRP,
  1026  					},
  1027  				},
  1028  				ServiceAdvertisements: PeerAdvertisements{
  1029  					"red-peer-65001": PeerFamilyAdvertisements{
  1030  						{Afi: "ipv4", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
  1031  							clusterIPSvcAdvertWithSelector(redSvcSelector),
  1032  						},
  1033  						{Afi: "ipv6", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
  1034  							clusterIPSvcAdvertWithSelector(redSvcSelector),
  1035  						},
  1036  					},
  1037  				},
  1038  			},
  1039  		},
  1040  		{
  1041  			name:       "Service (Cluster) with advertisement(Cluster) - matching labels (eTP=local, ep on node)",
  1042  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{redPeerConfig},
  1043  			services:   []*slim_corev1.Service{redClusterSvcWithITP(slim_corev1.ServiceInternalTrafficPolicyLocal)},
  1044  			endpoints:  []*k8s.Endpoints{eps1Local},
  1045  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
  1046  				redSvcAdvertWithAdvertisements(clusterIPSvcAdvertWithSelector(redSvcSelector)),
  1047  			},
  1048  			expectedMetadata: ServiceReconcilerMetadata{
  1049  				ServicePaths: ResourceAFPathsMap{
  1050  					redSvcKey: AFPathsMap{
  1051  						{Afi: types.AfiIPv4, Safi: types.SafiUnicast}: {
  1052  							clusterV4Prefix: types.NewPathForPrefix(netip.MustParsePrefix(clusterV4Prefix)),
  1053  						},
  1054  						{Afi: types.AfiIPv6, Safi: types.SafiUnicast}: {
  1055  							clusterV6Prefix: types.NewPathForPrefix(netip.MustParsePrefix(clusterV6Prefix)),
  1056  						},
  1057  					},
  1058  				},
  1059  				ServiceRoutePolicies: ResourceRoutePolicyMap{
  1060  					redSvcKey: RoutePolicyMap{
  1061  						redPeer65001v4ClusterRPName: redPeer65001v4ClusterRP,
  1062  						redPeer65001v6ClusterRPName: redPeer65001v6ClusterRP,
  1063  					},
  1064  				},
  1065  				ServiceAdvertisements: PeerAdvertisements{
  1066  					"red-peer-65001": PeerFamilyAdvertisements{
  1067  						{Afi: "ipv4", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
  1068  							clusterIPSvcAdvertWithSelector(redSvcSelector),
  1069  						},
  1070  						{Afi: "ipv6", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
  1071  							clusterIPSvcAdvertWithSelector(redSvcSelector),
  1072  						},
  1073  					},
  1074  				},
  1075  			},
  1076  		},
  1077  		{
  1078  			name:       "Service (Cluster) with advertisement(Cluster) - matching labels (eTP=local, mixed ep)",
  1079  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{redPeerConfig},
  1080  			services:   []*slim_corev1.Service{redClusterSvcWithITP(slim_corev1.ServiceInternalTrafficPolicyLocal)},
  1081  			endpoints:  []*k8s.Endpoints{eps1Mixed},
  1082  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
  1083  				redSvcAdvertWithAdvertisements(clusterIPSvcAdvertWithSelector(redSvcSelector)),
  1084  			},
  1085  			expectedMetadata: ServiceReconcilerMetadata{
  1086  				ServicePaths: ResourceAFPathsMap{
  1087  					redSvcKey: AFPathsMap{
  1088  						{Afi: types.AfiIPv4, Safi: types.SafiUnicast}: {
  1089  							clusterV4Prefix: types.NewPathForPrefix(netip.MustParsePrefix(clusterV4Prefix)),
  1090  						},
  1091  						{Afi: types.AfiIPv6, Safi: types.SafiUnicast}: {
  1092  							clusterV6Prefix: types.NewPathForPrefix(netip.MustParsePrefix(clusterV6Prefix)),
  1093  						},
  1094  					},
  1095  				},
  1096  				ServiceRoutePolicies: ResourceRoutePolicyMap{
  1097  					redSvcKey: RoutePolicyMap{
  1098  						redPeer65001v4ClusterRPName: redPeer65001v4ClusterRP,
  1099  						redPeer65001v6ClusterRPName: redPeer65001v6ClusterRP,
  1100  					},
  1101  				},
  1102  				ServiceAdvertisements: PeerAdvertisements{
  1103  					"red-peer-65001": PeerFamilyAdvertisements{
  1104  						{Afi: "ipv4", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
  1105  							clusterIPSvcAdvertWithSelector(redSvcSelector),
  1106  						},
  1107  						{Afi: "ipv6", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
  1108  							clusterIPSvcAdvertWithSelector(redSvcSelector),
  1109  						},
  1110  					},
  1111  				},
  1112  			},
  1113  		},
  1114  		{
  1115  			name:       "Service (Cluster) with advertisement(Cluster) - matching labels (eTP=local, ep on remote)",
  1116  			peerConfig: []*v2alpha1.CiliumBGPPeerConfig{redPeerConfig},
  1117  			services:   []*slim_corev1.Service{redClusterSvcWithITP(slim_corev1.ServiceInternalTrafficPolicyLocal)},
  1118  			endpoints:  []*k8s.Endpoints{eps1Remote},
  1119  			advertisements: []*v2alpha1.CiliumBGPAdvertisement{
  1120  				redSvcAdvertWithAdvertisements(clusterIPSvcAdvertWithSelector(redSvcSelector)),
  1121  			},
  1122  			expectedMetadata: ServiceReconcilerMetadata{
  1123  				ServicePaths:         ResourceAFPathsMap{},
  1124  				ServiceRoutePolicies: ResourceRoutePolicyMap{},
  1125  				ServiceAdvertisements: PeerAdvertisements{
  1126  					"red-peer-65001": PeerFamilyAdvertisements{
  1127  						{Afi: "ipv4", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
  1128  							clusterIPSvcAdvertWithSelector(redSvcSelector),
  1129  						},
  1130  						{Afi: "ipv6", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
  1131  							clusterIPSvcAdvertWithSelector(redSvcSelector),
  1132  						},
  1133  					},
  1134  				},
  1135  			},
  1136  		},
  1137  	}
  1138  
  1139  	for _, tt := range tests {
  1140  		t.Run(tt.name, func(t *testing.T) {
  1141  			req := require.New(t)
  1142  
  1143  			params := ServiceReconcilerIn{
  1144  				Logger: serviceReconcilerTestLogger,
  1145  				PeerAdvert: NewCiliumPeerAdvertisement(
  1146  					PeerAdvertisementIn{
  1147  						Logger:          podCIDRTestLogger,
  1148  						PeerConfigStore: store.InitMockStore[*v2alpha1.CiliumBGPPeerConfig](tt.peerConfig),
  1149  						AdvertStore:     store.InitMockStore[*v2alpha1.CiliumBGPAdvertisement](tt.advertisements),
  1150  					}),
  1151  				SvcDiffStore: store.InitFakeDiffStore[*slim_corev1.Service](tt.services),
  1152  				EPDiffStore:  store.InitFakeDiffStore[*k8s.Endpoints](tt.endpoints),
  1153  			}
  1154  
  1155  			svcReconciler := NewServiceReconciler(params).Reconciler.(*ServiceReconciler)
  1156  			testBGPInstance := instance.NewFakeBGPInstance()
  1157  			svcReconciler.Init(testBGPInstance)
  1158  			defer svcReconciler.Cleanup(testBGPInstance)
  1159  
  1160  			// reconcile twice to validate idempotency
  1161  			for i := 0; i < 2; i++ {
  1162  				err := svcReconciler.Reconcile(context.Background(), ReconcileParams{
  1163  					BGPInstance:   testBGPInstance,
  1164  					DesiredConfig: testBGPInstanceConfig,
  1165  					CiliumNode:    testCiliumNodeConfig,
  1166  				})
  1167  				req.NoError(err)
  1168  			}
  1169  
  1170  			// validate new metadata
  1171  			serviceMetadataEqual(req, tt.expectedMetadata, svcReconciler.getMetadata(testBGPInstance))
  1172  		})
  1173  	}
  1174  }
  1175  
  1176  // Test_ServiceAndAdvertisementModifications is a step test, in which each step modifies the advertisement or service parameters.
  1177  func Test_ServiceAndAdvertisementModifications(t *testing.T) {
  1178  	logrus.SetLevel(logrus.DebugLevel)
  1179  
  1180  	peerConfigs := []*v2alpha1.CiliumBGPPeerConfig{redPeerConfig}
  1181  
  1182  	steps := []struct {
  1183  		name             string
  1184  		upsertAdverts    []*v2alpha1.CiliumBGPAdvertisement
  1185  		upsertServices   []*slim_corev1.Service
  1186  		upsertEPs        []*k8s.Endpoints
  1187  		expectedMetadata ServiceReconcilerMetadata
  1188  	}{
  1189  		{
  1190  			name:           "Initial setup - Service (nil) with advertisement( empty )",
  1191  			upsertAdverts:  nil,
  1192  			upsertServices: nil,
  1193  			upsertEPs:      nil,
  1194  			expectedMetadata: ServiceReconcilerMetadata{
  1195  				ServicePaths:         ResourceAFPathsMap{},
  1196  				ServiceRoutePolicies: ResourceRoutePolicyMap{},
  1197  				ServiceAdvertisements: PeerAdvertisements{
  1198  					"red-peer-65001": PeerFamilyAdvertisements{
  1199  						{Afi: "ipv4", Safi: "unicast"}: nil,
  1200  						{Afi: "ipv6", Safi: "unicast"}: nil,
  1201  					},
  1202  				},
  1203  			},
  1204  		},
  1205  		{
  1206  			name: "Add service (Cluster, External) with advertisement(Cluster) - matching labels",
  1207  			upsertAdverts: []*v2alpha1.CiliumBGPAdvertisement{
  1208  				redSvcAdvertWithAdvertisements(v2alpha1.BGPAdvertisement{
  1209  					AdvertisementType: v2alpha1.BGPServiceAdvert,
  1210  					Service: &v2alpha1.BGPServiceOptions{
  1211  						Addresses: []v2alpha1.BGPServiceAddressType{v2alpha1.BGPClusterIPAddr},
  1212  					},
  1213  					Selector: redSvcSelector,
  1214  					Attributes: &v2alpha1.BGPAttributes{
  1215  						Communities: &v2alpha1.BGPCommunities{
  1216  							Standard:  []v2alpha1.BGPStandardCommunity{"65535:65281"},
  1217  							WellKnown: []v2alpha1.BGPWellKnownCommunity{"no-export"},
  1218  						},
  1219  					},
  1220  				}),
  1221  			},
  1222  			upsertServices: []*slim_corev1.Service{redExternalAndClusterSvc},
  1223  			expectedMetadata: ServiceReconcilerMetadata{
  1224  				// Only cluster IPs are advertised
  1225  				ServicePaths: ResourceAFPathsMap{
  1226  					redSvcKey: AFPathsMap{
  1227  						{Afi: types.AfiIPv4, Safi: types.SafiUnicast}: {
  1228  							clusterV4Prefix: types.NewPathForPrefix(netip.MustParsePrefix(clusterV4Prefix)),
  1229  						},
  1230  						{Afi: types.AfiIPv6, Safi: types.SafiUnicast}: {
  1231  							clusterV6Prefix: types.NewPathForPrefix(netip.MustParsePrefix(clusterV6Prefix)),
  1232  						},
  1233  					},
  1234  				},
  1235  				ServiceRoutePolicies: ResourceRoutePolicyMap{
  1236  					redSvcKey: RoutePolicyMap{
  1237  						redPeer65001v4ClusterRPName: redPeer65001v4ClusterRP,
  1238  						redPeer65001v6ClusterRPName: redPeer65001v6ClusterRP,
  1239  					},
  1240  				},
  1241  				ServiceAdvertisements: PeerAdvertisements{
  1242  					"red-peer-65001": PeerFamilyAdvertisements{
  1243  						{Afi: "ipv4", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
  1244  							{
  1245  								AdvertisementType: v2alpha1.BGPServiceAdvert,
  1246  								Service: &v2alpha1.BGPServiceOptions{
  1247  									Addresses: []v2alpha1.BGPServiceAddressType{v2alpha1.BGPClusterIPAddr},
  1248  								},
  1249  								Selector: redSvcSelector,
  1250  								Attributes: &v2alpha1.BGPAttributes{
  1251  									Communities: &v2alpha1.BGPCommunities{
  1252  										Standard:  []v2alpha1.BGPStandardCommunity{"65535:65281"},
  1253  										WellKnown: []v2alpha1.BGPWellKnownCommunity{"no-export"},
  1254  									},
  1255  								},
  1256  							},
  1257  						},
  1258  						{Afi: "ipv6", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
  1259  							{
  1260  								AdvertisementType: v2alpha1.BGPServiceAdvert,
  1261  								Service: &v2alpha1.BGPServiceOptions{
  1262  									Addresses: []v2alpha1.BGPServiceAddressType{v2alpha1.BGPClusterIPAddr},
  1263  								},
  1264  								Selector: redSvcSelector,
  1265  								Attributes: &v2alpha1.BGPAttributes{
  1266  									Communities: &v2alpha1.BGPCommunities{
  1267  										Standard:  []v2alpha1.BGPStandardCommunity{"65535:65281"},
  1268  										WellKnown: []v2alpha1.BGPWellKnownCommunity{"no-export"},
  1269  									},
  1270  								},
  1271  							},
  1272  						},
  1273  					},
  1274  				},
  1275  			},
  1276  		},
  1277  		{
  1278  			name: "Update advertisement(Cluster, External) - matching labels",
  1279  			upsertAdverts: []*v2alpha1.CiliumBGPAdvertisement{
  1280  				redSvcAdvertWithAdvertisements(v2alpha1.BGPAdvertisement{
  1281  					AdvertisementType: v2alpha1.BGPServiceAdvert,
  1282  					Service: &v2alpha1.BGPServiceOptions{
  1283  						Addresses: []v2alpha1.BGPServiceAddressType{
  1284  							v2alpha1.BGPClusterIPAddr,
  1285  							v2alpha1.BGPExternalIPAddr,
  1286  						},
  1287  					},
  1288  					Selector: redSvcSelector,
  1289  					Attributes: &v2alpha1.BGPAttributes{
  1290  						Communities: &v2alpha1.BGPCommunities{
  1291  							Standard:  []v2alpha1.BGPStandardCommunity{"65535:65281"},
  1292  							WellKnown: []v2alpha1.BGPWellKnownCommunity{"no-export"},
  1293  						},
  1294  					},
  1295  				}),
  1296  			},
  1297  			expectedMetadata: ServiceReconcilerMetadata{
  1298  				// Both cluster and external IPs are advertised
  1299  				ServicePaths: ResourceAFPathsMap{
  1300  					redSvcKey: AFPathsMap{
  1301  						{Afi: types.AfiIPv4, Safi: types.SafiUnicast}: {
  1302  							clusterV4Prefix:  types.NewPathForPrefix(netip.MustParsePrefix(clusterV4Prefix)),
  1303  							externalV4Prefix: types.NewPathForPrefix(netip.MustParsePrefix(externalV4Prefix)),
  1304  						},
  1305  						{Afi: types.AfiIPv6, Safi: types.SafiUnicast}: {
  1306  							clusterV6Prefix:  types.NewPathForPrefix(netip.MustParsePrefix(clusterV6Prefix)),
  1307  							externalV6Prefix: types.NewPathForPrefix(netip.MustParsePrefix(externalV6Prefix)),
  1308  						},
  1309  					},
  1310  				},
  1311  				ServiceRoutePolicies: ResourceRoutePolicyMap{
  1312  					redSvcKey: RoutePolicyMap{
  1313  						redPeer65001v4ClusterRPName: redPeer65001v4ClusterRP,
  1314  						redPeer65001v4ExtRPName:     redPeer65001v4ExtRP,
  1315  						redPeer65001v6ClusterRPName: redPeer65001v6ClusterRP,
  1316  						redPeer65001v6ExtRPName:     redPeer65001v6ExtRP,
  1317  					},
  1318  				},
  1319  				ServiceAdvertisements: PeerAdvertisements{
  1320  					"red-peer-65001": PeerFamilyAdvertisements{
  1321  						{Afi: "ipv4", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
  1322  							{
  1323  								AdvertisementType: v2alpha1.BGPServiceAdvert,
  1324  								Service: &v2alpha1.BGPServiceOptions{
  1325  									Addresses: []v2alpha1.BGPServiceAddressType{
  1326  										v2alpha1.BGPClusterIPAddr,
  1327  										v2alpha1.BGPExternalIPAddr,
  1328  									},
  1329  								},
  1330  								Selector: redSvcSelector,
  1331  								Attributes: &v2alpha1.BGPAttributes{
  1332  									Communities: &v2alpha1.BGPCommunities{
  1333  										Standard:  []v2alpha1.BGPStandardCommunity{"65535:65281"},
  1334  										WellKnown: []v2alpha1.BGPWellKnownCommunity{"no-export"},
  1335  									},
  1336  								},
  1337  							},
  1338  						},
  1339  						{Afi: "ipv6", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
  1340  							{
  1341  								AdvertisementType: v2alpha1.BGPServiceAdvert,
  1342  								Service: &v2alpha1.BGPServiceOptions{
  1343  									Addresses: []v2alpha1.BGPServiceAddressType{
  1344  										v2alpha1.BGPClusterIPAddr,
  1345  										v2alpha1.BGPExternalIPAddr,
  1346  									},
  1347  								},
  1348  								Selector: redSvcSelector,
  1349  								Attributes: &v2alpha1.BGPAttributes{
  1350  									Communities: &v2alpha1.BGPCommunities{
  1351  										Standard:  []v2alpha1.BGPStandardCommunity{"65535:65281"},
  1352  										WellKnown: []v2alpha1.BGPWellKnownCommunity{"no-export"},
  1353  									},
  1354  								},
  1355  							},
  1356  						},
  1357  					},
  1358  				},
  1359  			},
  1360  		},
  1361  		{
  1362  			name: "Update service (Cluster, External) traffic policy local",
  1363  			upsertServices: []*slim_corev1.Service{
  1364  				redExternalAndClusterSvcWithITP(
  1365  					redExternalAndClusterSvcWithETP(redExternalAndClusterSvc, slim_corev1.ServiceExternalTrafficPolicyLocal),
  1366  					slim_corev1.ServiceInternalTrafficPolicyLocal),
  1367  			},
  1368  			expectedMetadata: ServiceReconcilerMetadata{
  1369  				// Both cluster and external IPs are withdrawn, since traffic policy is local and there are no endpoints.
  1370  				ServicePaths:         ResourceAFPathsMap{},
  1371  				ServiceRoutePolicies: ResourceRoutePolicyMap{},
  1372  				ServiceAdvertisements: PeerAdvertisements{
  1373  					"red-peer-65001": PeerFamilyAdvertisements{
  1374  						{Afi: "ipv4", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
  1375  							{
  1376  								AdvertisementType: v2alpha1.BGPServiceAdvert,
  1377  								Service: &v2alpha1.BGPServiceOptions{
  1378  									Addresses: []v2alpha1.BGPServiceAddressType{
  1379  										v2alpha1.BGPClusterIPAddr,
  1380  										v2alpha1.BGPExternalIPAddr,
  1381  									},
  1382  								},
  1383  								Selector: redSvcSelector,
  1384  								Attributes: &v2alpha1.BGPAttributes{
  1385  									Communities: &v2alpha1.BGPCommunities{
  1386  										Standard:  []v2alpha1.BGPStandardCommunity{"65535:65281"},
  1387  										WellKnown: []v2alpha1.BGPWellKnownCommunity{"no-export"},
  1388  									},
  1389  								},
  1390  							},
  1391  						},
  1392  						{Afi: "ipv6", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
  1393  							{
  1394  								AdvertisementType: v2alpha1.BGPServiceAdvert,
  1395  								Service: &v2alpha1.BGPServiceOptions{
  1396  									Addresses: []v2alpha1.BGPServiceAddressType{
  1397  										v2alpha1.BGPClusterIPAddr,
  1398  										v2alpha1.BGPExternalIPAddr,
  1399  									},
  1400  								},
  1401  								Selector: redSvcSelector,
  1402  								Attributes: &v2alpha1.BGPAttributes{
  1403  									Communities: &v2alpha1.BGPCommunities{
  1404  										Standard:  []v2alpha1.BGPStandardCommunity{"65535:65281"},
  1405  										WellKnown: []v2alpha1.BGPWellKnownCommunity{"no-export"},
  1406  									},
  1407  								},
  1408  							},
  1409  						},
  1410  					},
  1411  				},
  1412  			},
  1413  		},
  1414  		{
  1415  			name:      "Update local endpoints (Cluster, External)",
  1416  			upsertEPs: []*k8s.Endpoints{eps1Mixed},
  1417  			expectedMetadata: ServiceReconcilerMetadata{
  1418  				// Both cluster and external IPs are advertised since there is local endpoint.
  1419  				ServicePaths: ResourceAFPathsMap{
  1420  					redSvcKey: AFPathsMap{
  1421  						{Afi: types.AfiIPv4, Safi: types.SafiUnicast}: {
  1422  							clusterV4Prefix:  types.NewPathForPrefix(netip.MustParsePrefix(clusterV4Prefix)),
  1423  							externalV4Prefix: types.NewPathForPrefix(netip.MustParsePrefix(externalV4Prefix)),
  1424  						},
  1425  						{Afi: types.AfiIPv6, Safi: types.SafiUnicast}: {
  1426  							clusterV6Prefix:  types.NewPathForPrefix(netip.MustParsePrefix(clusterV6Prefix)),
  1427  							externalV6Prefix: types.NewPathForPrefix(netip.MustParsePrefix(externalV6Prefix)),
  1428  						},
  1429  					},
  1430  				},
  1431  				ServiceRoutePolicies: ResourceRoutePolicyMap{
  1432  					redSvcKey: RoutePolicyMap{
  1433  						redPeer65001v4ClusterRPName: redPeer65001v4ClusterRP,
  1434  						redPeer65001v4ExtRPName:     redPeer65001v4ExtRP,
  1435  						redPeer65001v6ClusterRPName: redPeer65001v6ClusterRP,
  1436  						redPeer65001v6ExtRPName:     redPeer65001v6ExtRP,
  1437  					},
  1438  				},
  1439  				ServiceAdvertisements: PeerAdvertisements{
  1440  					"red-peer-65001": PeerFamilyAdvertisements{
  1441  						{Afi: "ipv4", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
  1442  							{
  1443  								AdvertisementType: v2alpha1.BGPServiceAdvert,
  1444  								Service: &v2alpha1.BGPServiceOptions{
  1445  									Addresses: []v2alpha1.BGPServiceAddressType{
  1446  										v2alpha1.BGPClusterIPAddr,
  1447  										v2alpha1.BGPExternalIPAddr,
  1448  									},
  1449  								},
  1450  								Selector: redSvcSelector,
  1451  								Attributes: &v2alpha1.BGPAttributes{
  1452  									Communities: &v2alpha1.BGPCommunities{
  1453  										Standard:  []v2alpha1.BGPStandardCommunity{"65535:65281"},
  1454  										WellKnown: []v2alpha1.BGPWellKnownCommunity{"no-export"},
  1455  									},
  1456  								},
  1457  							},
  1458  						},
  1459  						{Afi: "ipv6", Safi: "unicast"}: []v2alpha1.BGPAdvertisement{
  1460  							{
  1461  								AdvertisementType: v2alpha1.BGPServiceAdvert,
  1462  								Service: &v2alpha1.BGPServiceOptions{
  1463  									Addresses: []v2alpha1.BGPServiceAddressType{
  1464  										v2alpha1.BGPClusterIPAddr,
  1465  										v2alpha1.BGPExternalIPAddr,
  1466  									},
  1467  								},
  1468  								Selector: redSvcSelector,
  1469  								Attributes: &v2alpha1.BGPAttributes{
  1470  									Communities: &v2alpha1.BGPCommunities{
  1471  										Standard:  []v2alpha1.BGPStandardCommunity{"65535:65281"},
  1472  										WellKnown: []v2alpha1.BGPWellKnownCommunity{"no-export"},
  1473  									},
  1474  								},
  1475  							},
  1476  						},
  1477  					},
  1478  				},
  1479  			},
  1480  		},
  1481  	}
  1482  
  1483  	req := require.New(t)
  1484  	advertStore := store.NewMockBGPCPResourceStore[*v2alpha1.CiliumBGPAdvertisement]()
  1485  	serviceStore := store.NewFakeDiffStore[*slim_corev1.Service]()
  1486  	epStore := store.NewFakeDiffStore[*k8s.Endpoints]()
  1487  
  1488  	params := ServiceReconcilerIn{
  1489  		Logger: serviceReconcilerTestLogger,
  1490  		PeerAdvert: NewCiliumPeerAdvertisement(
  1491  			PeerAdvertisementIn{
  1492  				Logger:          podCIDRTestLogger,
  1493  				PeerConfigStore: store.InitMockStore[*v2alpha1.CiliumBGPPeerConfig](peerConfigs),
  1494  				AdvertStore:     advertStore,
  1495  			}),
  1496  		SvcDiffStore: serviceStore,
  1497  		EPDiffStore:  epStore,
  1498  	}
  1499  
  1500  	svcReconciler := NewServiceReconciler(params).Reconciler.(*ServiceReconciler)
  1501  	testBGPInstance := instance.NewFakeBGPInstance()
  1502  	svcReconciler.Init(testBGPInstance)
  1503  	defer svcReconciler.Cleanup(testBGPInstance)
  1504  
  1505  	for _, tt := range steps {
  1506  		t.Logf("Running step - %s", tt.name)
  1507  		for _, advert := range tt.upsertAdverts {
  1508  			advertStore.Upsert(advert)
  1509  		}
  1510  
  1511  		for _, svc := range tt.upsertServices {
  1512  			serviceStore.Upsert(svc)
  1513  		}
  1514  
  1515  		for _, ep := range tt.upsertEPs {
  1516  			epStore.Upsert(ep)
  1517  		}
  1518  
  1519  		err := svcReconciler.Reconcile(context.Background(), ReconcileParams{
  1520  			BGPInstance:   testBGPInstance,
  1521  			DesiredConfig: testBGPInstanceConfig,
  1522  			CiliumNode:    testCiliumNodeConfig,
  1523  		})
  1524  		req.NoError(err)
  1525  
  1526  		// validate new metadata
  1527  		serviceMetadataEqual(req, tt.expectedMetadata, svcReconciler.getMetadata(testBGPInstance))
  1528  	}
  1529  }
  1530  
  1531  func serviceMetadataEqual(req *require.Assertions, expectedMetadata, runningMetadata ServiceReconcilerMetadata) {
  1532  	req.Truef(PeerAdvertisementsEqual(expectedMetadata.ServiceAdvertisements, runningMetadata.ServiceAdvertisements),
  1533  		"ServiceAdvertisements mismatch, expected: %v, got: %v", expectedMetadata.ServiceAdvertisements, runningMetadata.ServiceAdvertisements)
  1534  
  1535  	req.Equalf(len(expectedMetadata.ServicePaths), len(runningMetadata.ServicePaths),
  1536  		"ServicePaths length mismatch, expected: %v, got: %v", expectedMetadata.ServicePaths, runningMetadata.ServicePaths)
  1537  
  1538  	for svc, expectedSvcPaths := range expectedMetadata.ServicePaths {
  1539  		runningSvcPaths, exists := runningMetadata.ServicePaths[svc]
  1540  		req.Truef(exists, "Service not found in running: %v", svc)
  1541  
  1542  		runningFamilyPaths := make(map[types.Family]map[string]struct{})
  1543  		for family, paths := range runningSvcPaths {
  1544  			pathSet := make(map[string]struct{})
  1545  
  1546  			for pathKey := range paths {
  1547  				pathSet[pathKey] = struct{}{}
  1548  			}
  1549  			runningFamilyPaths[family] = pathSet
  1550  		}
  1551  
  1552  		expectedFamilyPaths := make(map[types.Family]map[string]struct{})
  1553  		for family, paths := range expectedSvcPaths {
  1554  			pathSet := make(map[string]struct{})
  1555  
  1556  			for pathKey := range paths {
  1557  				pathSet[pathKey] = struct{}{}
  1558  			}
  1559  			expectedFamilyPaths[family] = pathSet
  1560  		}
  1561  
  1562  		req.Equal(expectedFamilyPaths, runningFamilyPaths)
  1563  	}
  1564  
  1565  	req.Equalf(expectedMetadata.ServiceRoutePolicies, runningMetadata.ServiceRoutePolicies,
  1566  		"ServiceRoutePolicies mismatch, expected: %v, got: %v", expectedMetadata.ServiceRoutePolicies, runningMetadata.ServiceRoutePolicies)
  1567  }