k8s.io/kubernetes@v1.29.3/pkg/registry/core/service/ipallocator/controller/repairip_test.go (about)

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package controller
    18  
    19  import (
    20  	"fmt"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	v1 "k8s.io/api/core/v1"
    26  	networkingv1alpha1 "k8s.io/api/networking/v1alpha1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/util/wait"
    30  	"k8s.io/client-go/informers"
    31  	"k8s.io/client-go/kubernetes/fake"
    32  	k8stesting "k8s.io/client-go/testing"
    33  	"k8s.io/client-go/tools/cache"
    34  	"k8s.io/client-go/tools/events"
    35  	"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
    36  )
    37  
    38  var (
    39  	serviceCIDRv4 = "10.0.0.0/16"
    40  	serviceCIDRv6 = "2001:db8::/64"
    41  )
    42  
    43  type fakeRepair struct {
    44  	*RepairIPAddress
    45  	serviceStore     cache.Store
    46  	ipAddressStore   cache.Store
    47  	serviceCIDRStore cache.Store
    48  }
    49  
    50  func newFakeRepair() (*fake.Clientset, *fakeRepair) {
    51  	fakeClient := fake.NewSimpleClientset()
    52  
    53  	informerFactory := informers.NewSharedInformerFactory(fakeClient, 0*time.Second)
    54  	serviceInformer := informerFactory.Core().V1().Services()
    55  	serviceIndexer := serviceInformer.Informer().GetIndexer()
    56  
    57  	serviceCIDRInformer := informerFactory.Networking().V1alpha1().ServiceCIDRs()
    58  	serviceCIDRIndexer := serviceCIDRInformer.Informer().GetIndexer()
    59  
    60  	ipInformer := informerFactory.Networking().V1alpha1().IPAddresses()
    61  	ipIndexer := ipInformer.Informer().GetIndexer()
    62  
    63  	fakeClient.PrependReactor("create", "ipaddresses", k8stesting.ReactionFunc(func(action k8stesting.Action) (bool, runtime.Object, error) {
    64  		ip := action.(k8stesting.CreateAction).GetObject().(*networkingv1alpha1.IPAddress)
    65  		err := ipIndexer.Add(ip)
    66  		return false, ip, err
    67  	}))
    68  	fakeClient.PrependReactor("update", "ipaddresses", k8stesting.ReactionFunc(func(action k8stesting.Action) (bool, runtime.Object, error) {
    69  		ip := action.(k8stesting.UpdateAction).GetObject().(*networkingv1alpha1.IPAddress)
    70  		return false, ip, fmt.Errorf("IPAddress is inmutable after creation")
    71  	}))
    72  	fakeClient.PrependReactor("delete", "ipaddresses", k8stesting.ReactionFunc(func(action k8stesting.Action) (bool, runtime.Object, error) {
    73  		ip := action.(k8stesting.DeleteAction).GetName()
    74  		err := ipIndexer.Delete(ip)
    75  		return false, &networkingv1alpha1.IPAddress{}, err
    76  	}))
    77  
    78  	r := NewRepairIPAddress(0*time.Second,
    79  		fakeClient,
    80  		serviceInformer,
    81  		serviceCIDRInformer,
    82  		ipInformer,
    83  	)
    84  	return fakeClient, &fakeRepair{r, serviceIndexer, ipIndexer, serviceCIDRIndexer}
    85  }
    86  
    87  func TestRepairServiceIP(t *testing.T) {
    88  	tests := []struct {
    89  		name        string
    90  		svcs        []*v1.Service
    91  		ipAddresses []*networkingv1alpha1.IPAddress
    92  		cidrs       []*networkingv1alpha1.ServiceCIDR
    93  		expectedIPs []string
    94  		actions     [][]string // verb and resource
    95  		events      []string
    96  	}{
    97  		{
    98  			name: "no changes needed single stack",
    99  			svcs: []*v1.Service{newService("test-svc", []string{"10.0.1.1"})},
   100  			ipAddresses: []*networkingv1alpha1.IPAddress{
   101  				newIPAddress("10.0.1.1", newService("test-svc", []string{"10.0.1.1"})),
   102  			},
   103  			cidrs: []*networkingv1alpha1.ServiceCIDR{
   104  				newServiceCIDR("kubernetes", serviceCIDRv4, serviceCIDRv6),
   105  			},
   106  			expectedIPs: []string{"10.0.1.1"},
   107  			actions:     [][]string{},
   108  			events:      []string{},
   109  		},
   110  		{
   111  			name: "no changes needed dual stack",
   112  			svcs: []*v1.Service{newService("test-svc", []string{"10.0.1.1", "2001:db8::10"})},
   113  			ipAddresses: []*networkingv1alpha1.IPAddress{
   114  				newIPAddress("10.0.1.1", newService("test-svc", []string{"10.0.1.1"})),
   115  				newIPAddress("2001:db8::10", newService("test-svc", []string{"2001:db8::10"})),
   116  			},
   117  			cidrs: []*networkingv1alpha1.ServiceCIDR{
   118  				newServiceCIDR("kubernetes", serviceCIDRv4, serviceCIDRv6),
   119  			},
   120  			expectedIPs: []string{"10.0.1.1", "2001:db8::10"},
   121  			actions:     [][]string{},
   122  			events:      []string{},
   123  		},
   124  		{
   125  			name: "no changes needed dual stack multiple cidrs",
   126  			svcs: []*v1.Service{newService("test-svc", []string{"192.168.0.1", "2001:db8:a:b::10"})},
   127  			ipAddresses: []*networkingv1alpha1.IPAddress{
   128  				newIPAddress("192.168.0.1", newService("test-svc", []string{"192.168.0.1"})),
   129  				newIPAddress("2001:db8:a:b::10", newService("test-svc", []string{"2001:db8:a:b::10"})),
   130  			},
   131  			cidrs: []*networkingv1alpha1.ServiceCIDR{
   132  				newServiceCIDR("kubernetes", serviceCIDRv4, serviceCIDRv6),
   133  				newServiceCIDR("custom", "192.168.0.0/24", "2001:db8:a:b::/64"),
   134  			},
   135  			expectedIPs: []string{"192.168.0.1", "2001:db8:a:b::10"},
   136  			actions:     [][]string{},
   137  			events:      []string{},
   138  		},
   139  		// these two cases simulate migrating from bitmaps to IPAddress objects
   140  		{
   141  			name: "create IPAddress single stack",
   142  			svcs: []*v1.Service{newService("test-svc", []string{"10.0.1.1"})},
   143  			cidrs: []*networkingv1alpha1.ServiceCIDR{
   144  				newServiceCIDR("kubernetes", serviceCIDRv4, serviceCIDRv6),
   145  			},
   146  			expectedIPs: []string{"10.0.1.1"},
   147  			actions:     [][]string{{"create", "ipaddresses"}},
   148  			events:      []string{"Warning ClusterIPNotAllocated Cluster IP [IPv4]: 10.0.1.1 is not allocated; repairing"},
   149  		},
   150  		{
   151  			name: "create IPAddresses dual stack",
   152  			svcs: []*v1.Service{newService("test-svc", []string{"10.0.1.1", "2001:db8::10"})},
   153  			cidrs: []*networkingv1alpha1.ServiceCIDR{
   154  				newServiceCIDR("kubernetes", serviceCIDRv4, serviceCIDRv6),
   155  			},
   156  			expectedIPs: []string{"10.0.1.1", "2001:db8::10"},
   157  			actions:     [][]string{{"create", "ipaddresses"}, {"create", "ipaddresses"}},
   158  			events: []string{
   159  				"Warning ClusterIPNotAllocated Cluster IP [IPv4]: 10.0.1.1 is not allocated; repairing",
   160  				"Warning ClusterIPNotAllocated Cluster IP [IPv6]: 2001:db8::10 is not allocated; repairing",
   161  			},
   162  		},
   163  		{
   164  			name: "create IPAddress single stack from secondary",
   165  			svcs: []*v1.Service{newService("test-svc", []string{"192.168.1.1"})},
   166  			cidrs: []*networkingv1alpha1.ServiceCIDR{
   167  				newServiceCIDR("kubernetes", serviceCIDRv4, serviceCIDRv6),
   168  				newServiceCIDR("custom", "192.168.1.0/24", ""),
   169  			},
   170  			expectedIPs: []string{"192.168.1.1"},
   171  			actions:     [][]string{{"create", "ipaddresses"}},
   172  			events:      []string{"Warning ClusterIPNotAllocated Cluster IP [IPv4]: 192.168.1.1 is not allocated; repairing"},
   173  		},
   174  		{
   175  			name: "reconcile IPAddress single stack wrong reference",
   176  			svcs: []*v1.Service{newService("test-svc", []string{"10.0.1.1"})},
   177  			ipAddresses: []*networkingv1alpha1.IPAddress{
   178  				newIPAddress("10.0.1.1", newService("test-svc2", []string{"10.0.1.1"})),
   179  			},
   180  			cidrs: []*networkingv1alpha1.ServiceCIDR{
   181  				newServiceCIDR("kubernetes", serviceCIDRv4, serviceCIDRv6),
   182  			},
   183  			expectedIPs: []string{"10.0.1.1"},
   184  			actions:     [][]string{{"delete", "ipaddresses"}, {"create", "ipaddresses"}},
   185  			events:      []string{"Warning ClusterIPNotAllocated the ClusterIP [IPv4]: 10.0.1.1 for Service bar/test-svc has a wrong reference; repairing"},
   186  		},
   187  		{
   188  			name: "reconcile IPAddresses dual stack",
   189  			svcs: []*v1.Service{newService("test-svc", []string{"10.0.1.1", "2001:db8::10"})},
   190  			ipAddresses: []*networkingv1alpha1.IPAddress{
   191  				newIPAddress("10.0.1.1", newService("test-svc2", []string{"10.0.1.1"})),
   192  				newIPAddress("2001:db8::10", newService("test-svc2", []string{"2001:db8::10"})),
   193  			},
   194  			cidrs: []*networkingv1alpha1.ServiceCIDR{
   195  				newServiceCIDR("kubernetes", serviceCIDRv4, serviceCIDRv6),
   196  			},
   197  			expectedIPs: []string{"10.0.1.1", "2001:db8::10"},
   198  			actions:     [][]string{{"delete", "ipaddresses"}, {"create", "ipaddresses"}, {"delete", "ipaddresses"}, {"create", "ipaddresses"}},
   199  			events: []string{
   200  				"Warning ClusterIPNotAllocated the ClusterIP [IPv4]: 10.0.1.1 for Service bar/test-svc has a wrong reference; repairing",
   201  				"Warning ClusterIPNotAllocated the ClusterIP [IPv6]: 2001:db8::10 for Service bar/test-svc has a wrong reference; repairing",
   202  			},
   203  		},
   204  		{
   205  			name: "one IP out of range",
   206  			svcs: []*v1.Service{newService("test-svc", []string{"192.168.1.1", "2001:db8::10"})},
   207  			ipAddresses: []*networkingv1alpha1.IPAddress{
   208  				newIPAddress("192.168.1.1", newService("test-svc", []string{"192.168.1.1"})),
   209  				newIPAddress("2001:db8::10", newService("test-svc", []string{"2001:db8::10"})),
   210  			},
   211  			cidrs: []*networkingv1alpha1.ServiceCIDR{
   212  				newServiceCIDR("kubernetes", serviceCIDRv4, serviceCIDRv6),
   213  			},
   214  			expectedIPs: []string{"2001:db8::10"},
   215  			actions:     [][]string{},
   216  			events:      []string{"Warning ClusterIPOutOfRange Cluster IP [IPv4]: 192.168.1.1 is not within any configured Service CIDR; please recreate service"},
   217  		},
   218  		{
   219  			name: "one IP orphan",
   220  			ipAddresses: []*networkingv1alpha1.IPAddress{
   221  				newIPAddress("10.0.1.1", newService("test-svc", []string{"10.0.1.1"})),
   222  			},
   223  			cidrs: []*networkingv1alpha1.ServiceCIDR{
   224  				newServiceCIDR("kubernetes", serviceCIDRv4, serviceCIDRv6),
   225  			},
   226  			actions: [][]string{{"delete", "ipaddresses"}},
   227  			events:  []string{"Warning IPAddressNotAllocated IPAddress: 10.0.1.1 for Service bar/test-svc appears to have leaked: cleaning up"},
   228  		},
   229  		{
   230  			name: "one IP out of range matching the network address",
   231  			svcs: []*v1.Service{newService("test-svc", []string{"10.0.0.0"})},
   232  			ipAddresses: []*networkingv1alpha1.IPAddress{
   233  				newIPAddress("10.0.0.0", newService("test-svc", []string{"10.0.0.0"})),
   234  			},
   235  			cidrs: []*networkingv1alpha1.ServiceCIDR{
   236  				newServiceCIDR("kubernetes", serviceCIDRv4, serviceCIDRv6),
   237  			},
   238  			expectedIPs: []string{"10.0.0.0"},
   239  			actions:     [][]string{},
   240  			events:      []string{"Warning ClusterIPOutOfRange Cluster IP [IPv4]: 10.0.0.0 is not within any configured Service CIDR; please recreate service"},
   241  		},
   242  		{
   243  			name: "one IP out of range matching the broadcast address",
   244  			svcs: []*v1.Service{newService("test-svc", []string{"10.0.255.255"})},
   245  			ipAddresses: []*networkingv1alpha1.IPAddress{
   246  				newIPAddress("10.0.255.255", newService("test-svc", []string{"10.0.255.255"})),
   247  			},
   248  			cidrs: []*networkingv1alpha1.ServiceCIDR{
   249  				newServiceCIDR("kubernetes", serviceCIDRv4, serviceCIDRv6),
   250  			},
   251  			expectedIPs: []string{"10.0.255.255"},
   252  			actions:     [][]string{},
   253  			events:      []string{"Warning ClusterIPOutOfRange Cluster IP [IPv4]: 10.0.255.255 is not within any configured Service CIDR; please recreate service"},
   254  		},
   255  		{
   256  			name: "one IPv6 out of range matching the subnet address",
   257  			svcs: []*v1.Service{newService("test-svc", []string{"2001:db8::"})},
   258  			ipAddresses: []*networkingv1alpha1.IPAddress{
   259  				newIPAddress("2001:db8::", newService("test-svc", []string{"2001:db8::"})),
   260  			},
   261  			cidrs: []*networkingv1alpha1.ServiceCIDR{
   262  				newServiceCIDR("kubernetes", serviceCIDRv4, serviceCIDRv6),
   263  			},
   264  			expectedIPs: []string{"2001:db8::"},
   265  			actions:     [][]string{},
   266  			events:      []string{"Warning ClusterIPOutOfRange Cluster IP [IPv6]: 2001:db8:: is not within any configured Service CIDR; please recreate service"},
   267  		},
   268  		{
   269  			name: "one IPv6 matching the broadcast address",
   270  			svcs: []*v1.Service{newService("test-svc", []string{"2001:db8::ffff:ffff:ffff:ffff"})},
   271  			ipAddresses: []*networkingv1alpha1.IPAddress{
   272  				newIPAddress("2001:db8::ffff:ffff:ffff:ffff", newService("test-svc", []string{"2001:db8::ffff:ffff:ffff:ffff"})),
   273  			},
   274  			cidrs: []*networkingv1alpha1.ServiceCIDR{
   275  				newServiceCIDR("kubernetes", serviceCIDRv4, serviceCIDRv6),
   276  			},
   277  			expectedIPs: []string{"2001:db8::ffff:ffff:ffff:ffff"},
   278  		},
   279  		{
   280  			name: "one IP orphan matching the broadcast address",
   281  			ipAddresses: []*networkingv1alpha1.IPAddress{
   282  				newIPAddress("10.0.255.255", newService("test-svc", []string{"10.0.255.255"})),
   283  			},
   284  			cidrs: []*networkingv1alpha1.ServiceCIDR{
   285  				newServiceCIDR("kubernetes", serviceCIDRv4, serviceCIDRv6),
   286  			},
   287  			actions: [][]string{{"delete", "ipaddresses"}},
   288  			events:  []string{"Warning IPAddressNotAllocated IPAddress: 10.0.255.255 for Service bar/test-svc appears to have leaked: cleaning up"},
   289  		},
   290  		{
   291  			name: "Two IPAddresses referencing the same service",
   292  			svcs: []*v1.Service{newService("test-svc", []string{"10.0.1.1"})},
   293  			ipAddresses: []*networkingv1alpha1.IPAddress{
   294  				newIPAddress("10.0.1.1", newService("test-svc", []string{"10.0.1.1"})),
   295  				newIPAddress("10.0.1.2", newService("test-svc", []string{"10.0.1.1"})),
   296  			},
   297  			cidrs: []*networkingv1alpha1.ServiceCIDR{
   298  				newServiceCIDR("kubernetes", serviceCIDRv4, serviceCIDRv6),
   299  			},
   300  			actions: [][]string{{"delete", "ipaddresses"}},
   301  			events:  []string{"Warning IPAddressWrongReference IPAddress: 10.0.1.2 for Service bar/test-svc has a wrong reference; cleaning up"},
   302  		},
   303  		{
   304  			name: "Two Services with same ClusterIP",
   305  			svcs: []*v1.Service{
   306  				newService("test-svc", []string{"10.0.1.1"}),
   307  				newService("test-svc2", []string{"10.0.1.1"}),
   308  			},
   309  			ipAddresses: []*networkingv1alpha1.IPAddress{
   310  				newIPAddress("10.0.1.1", newService("test-svc2", []string{"10.0.1.1"})),
   311  			},
   312  			cidrs: []*networkingv1alpha1.ServiceCIDR{
   313  				newServiceCIDR("kubernetes", serviceCIDRv4, serviceCIDRv6),
   314  			},
   315  			events: []string{"Warning ClusterIPAlreadyAllocated Cluster IP [IPv4]:10.0.1.1 was assigned to multiple services; please recreate service"},
   316  		},
   317  	}
   318  
   319  	for _, test := range tests {
   320  		t.Run(test.name, func(t *testing.T) {
   321  
   322  			c, r := newFakeRepair()
   323  			// add cidrs
   324  			for _, cidr := range test.cidrs {
   325  				err := r.serviceCIDRStore.Add(cidr)
   326  				if err != nil {
   327  					t.Errorf("Unexpected error trying to add Service %v object: %v", cidr, err)
   328  				}
   329  			}
   330  			err := r.syncCIDRs()
   331  			if err != nil {
   332  				t.Fatal(err)
   333  			}
   334  			// override for testing
   335  			r.servicesSynced = func() bool { return true }
   336  			r.ipAddressSynced = func() bool { return true }
   337  			r.serviceCIDRSynced = func() bool { return true }
   338  			recorder := events.NewFakeRecorder(100)
   339  			r.recorder = recorder
   340  			for _, svc := range test.svcs {
   341  				err := r.serviceStore.Add(svc)
   342  				if err != nil {
   343  					t.Errorf("Unexpected error trying to add Service %v object: %v", svc, err)
   344  				}
   345  			}
   346  
   347  			for _, ip := range test.ipAddresses {
   348  				ip.CreationTimestamp = metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC)
   349  				err := r.ipAddressStore.Add(ip)
   350  				if err != nil {
   351  					t.Errorf("Unexpected error trying to add IPAddress %s object: %v", ip, err)
   352  				}
   353  			}
   354  
   355  			err = r.runOnce()
   356  			if err != nil {
   357  				t.Fatal(err)
   358  			}
   359  
   360  			for _, ip := range test.expectedIPs {
   361  				_, err := r.ipAddressLister.Get(ip)
   362  				if err != nil {
   363  					t.Errorf("Unexpected error trying to get IPAddress %s object: %v", ip, err)
   364  				}
   365  			}
   366  
   367  			expectAction(t, c.Actions(), test.actions)
   368  			expectEvents(t, recorder.Events, test.events)
   369  		})
   370  	}
   371  
   372  }
   373  
   374  func TestRepairIPAddress_syncIPAddress(t *testing.T) {
   375  	tests := []struct {
   376  		name    string
   377  		ip      *networkingv1alpha1.IPAddress
   378  		actions [][]string // verb and resource
   379  		wantErr bool
   380  	}{
   381  		{
   382  			name: "correct ipv4 address",
   383  			ip: &networkingv1alpha1.IPAddress{
   384  				ObjectMeta: metav1.ObjectMeta{
   385  					Name: "10.0.1.1",
   386  					Labels: map[string]string{
   387  						networkingv1alpha1.LabelIPAddressFamily: string(v1.IPv4Protocol),
   388  						networkingv1alpha1.LabelManagedBy:       ipallocator.ControllerName,
   389  					},
   390  					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
   391  				},
   392  				Spec: networkingv1alpha1.IPAddressSpec{
   393  					ParentRef: &networkingv1alpha1.ParentReference{
   394  						Group:     "",
   395  						Resource:  "services",
   396  						Name:      "foo",
   397  						Namespace: "bar",
   398  					},
   399  				},
   400  			},
   401  		},
   402  		{
   403  			name: "correct ipv6 address",
   404  			ip: &networkingv1alpha1.IPAddress{
   405  				ObjectMeta: metav1.ObjectMeta{
   406  					Name: "2001:db8::11",
   407  					Labels: map[string]string{
   408  						networkingv1alpha1.LabelIPAddressFamily: string(v1.IPv6Protocol),
   409  						networkingv1alpha1.LabelManagedBy:       ipallocator.ControllerName,
   410  					},
   411  					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
   412  				},
   413  				Spec: networkingv1alpha1.IPAddressSpec{
   414  					ParentRef: &networkingv1alpha1.ParentReference{
   415  						Group:     "",
   416  						Resource:  "services",
   417  						Name:      "foo",
   418  						Namespace: "bar",
   419  					},
   420  				},
   421  			},
   422  		},
   423  		{
   424  			name: "not managed by this controller",
   425  			ip: &networkingv1alpha1.IPAddress{
   426  				ObjectMeta: metav1.ObjectMeta{
   427  					Name: "2001:db8::11",
   428  					Labels: map[string]string{
   429  						networkingv1alpha1.LabelIPAddressFamily: string(v1.IPv6Protocol),
   430  						networkingv1alpha1.LabelManagedBy:       "controller-foo",
   431  					},
   432  					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
   433  				},
   434  				Spec: networkingv1alpha1.IPAddressSpec{
   435  					ParentRef: &networkingv1alpha1.ParentReference{
   436  						Group:     "networking.gateway.k8s.io",
   437  						Resource:  "gateway",
   438  						Name:      "foo",
   439  						Namespace: "bar",
   440  					},
   441  				},
   442  			},
   443  		},
   444  		{
   445  			name: "out of range",
   446  			ip: &networkingv1alpha1.IPAddress{
   447  				ObjectMeta: metav1.ObjectMeta{
   448  					Name: "fd00:db8::11",
   449  					Labels: map[string]string{
   450  						networkingv1alpha1.LabelIPAddressFamily: string(v1.IPv6Protocol),
   451  						networkingv1alpha1.LabelManagedBy:       ipallocator.ControllerName,
   452  					},
   453  					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
   454  				},
   455  				Spec: networkingv1alpha1.IPAddressSpec{
   456  					ParentRef: &networkingv1alpha1.ParentReference{
   457  						Group:     "",
   458  						Resource:  "services",
   459  						Name:      "foo",
   460  						Namespace: "bar",
   461  					},
   462  				},
   463  			},
   464  		},
   465  		{
   466  			name: "leaked ip",
   467  			ip: &networkingv1alpha1.IPAddress{
   468  				ObjectMeta: metav1.ObjectMeta{
   469  					Name: "10.0.1.1",
   470  					Labels: map[string]string{
   471  						networkingv1alpha1.LabelIPAddressFamily: string(v1.IPv6Protocol),
   472  						networkingv1alpha1.LabelManagedBy:       ipallocator.ControllerName,
   473  					},
   474  					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
   475  				},
   476  				Spec: networkingv1alpha1.IPAddressSpec{
   477  					ParentRef: &networkingv1alpha1.ParentReference{
   478  						Group:     "",
   479  						Resource:  "services",
   480  						Name:      "noexist",
   481  						Namespace: "bar",
   482  					},
   483  				},
   484  			},
   485  			actions: [][]string{{"delete", "ipaddresses"}},
   486  		},
   487  	}
   488  	for _, tt := range tests {
   489  		t.Run(tt.name, func(t *testing.T) {
   490  			c, r := newFakeRepair()
   491  			err := r.ipAddressStore.Add(tt.ip)
   492  			if err != nil {
   493  				t.Fatal(err)
   494  			}
   495  			err = r.serviceStore.Add(newService("foo", []string{tt.ip.Name}))
   496  			if err != nil {
   497  				t.Fatal(err)
   498  			}
   499  
   500  			// override for testing
   501  			r.servicesSynced = func() bool { return true }
   502  			r.ipAddressSynced = func() bool { return true }
   503  			recorder := events.NewFakeRecorder(100)
   504  			r.recorder = recorder
   505  			if err := r.syncIPAddress(tt.ip.Name); (err != nil) != tt.wantErr {
   506  				t.Errorf("RepairIPAddress.syncIPAddress() error = %v, wantErr %v", err, tt.wantErr)
   507  			}
   508  			expectAction(t, c.Actions(), tt.actions)
   509  
   510  		})
   511  	}
   512  }
   513  
   514  func newService(name string, ips []string) *v1.Service {
   515  	if len(ips) == 0 {
   516  		return nil
   517  	}
   518  	svc := &v1.Service{
   519  		ObjectMeta: metav1.ObjectMeta{Namespace: "bar", Name: name},
   520  		Spec: v1.ServiceSpec{
   521  			ClusterIP:  ips[0],
   522  			ClusterIPs: ips,
   523  			Type:       v1.ServiceTypeClusterIP,
   524  		},
   525  	}
   526  	return svc
   527  }
   528  
   529  func newServiceCIDR(name, primary, secondary string) *networkingv1alpha1.ServiceCIDR {
   530  	serviceCIDR := &networkingv1alpha1.ServiceCIDR{
   531  		ObjectMeta: metav1.ObjectMeta{
   532  			Name: name,
   533  		},
   534  		Spec: networkingv1alpha1.ServiceCIDRSpec{},
   535  	}
   536  	serviceCIDR.Spec.CIDRs = append(serviceCIDR.Spec.CIDRs, primary)
   537  	if secondary != "" {
   538  		serviceCIDR.Spec.CIDRs = append(serviceCIDR.Spec.CIDRs, secondary)
   539  	}
   540  	return serviceCIDR
   541  }
   542  
   543  func expectAction(t *testing.T, actions []k8stesting.Action, expected [][]string) {
   544  	t.Helper()
   545  	if len(actions) != len(expected) {
   546  		t.Fatalf("Expected at least %d actions, got %d \ndiff: %v", len(expected), len(actions), cmp.Diff(expected, actions))
   547  	}
   548  
   549  	for i, action := range actions {
   550  		verb := expected[i][0]
   551  		if action.GetVerb() != verb {
   552  			t.Errorf("Expected action %d verb to be %s, got %s", i, verb, action.GetVerb())
   553  		}
   554  		resource := expected[i][1]
   555  		if action.GetResource().Resource != resource {
   556  			t.Errorf("Expected action %d resource to be %s, got %s", i, resource, action.GetResource().Resource)
   557  		}
   558  	}
   559  }
   560  
   561  func expectEvents(t *testing.T, actual <-chan string, expected []string) {
   562  	t.Helper()
   563  	c := time.After(wait.ForeverTestTimeout)
   564  	for _, e := range expected {
   565  		select {
   566  		case a := <-actual:
   567  			if e != a {
   568  				t.Errorf("Expected event %q, got %q", e, a)
   569  				return
   570  			}
   571  		case <-c:
   572  			t.Errorf("Expected event %q, got nothing", e)
   573  			// continue iterating to print all expected events
   574  		}
   575  	}
   576  	for {
   577  		select {
   578  		case a := <-actual:
   579  			t.Errorf("Unexpected event: %q", a)
   580  		default:
   581  			return // No more events, as expected.
   582  		}
   583  	}
   584  }