istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/serviceregistry/kube/controller/pod_test.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package controller
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"reflect"
    21  	"testing"
    22  	"time"
    23  
    24  	v1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/types"
    27  	"k8s.io/client-go/kubernetes"
    28  
    29  	"istio.io/istio/pilot/pkg/model"
    30  	"istio.io/istio/pilot/pkg/serviceregistry/util/xdsfake"
    31  	"istio.io/istio/pkg/config/labels"
    32  	"istio.io/istio/pkg/kube/kclient/clienttest"
    33  	"istio.io/istio/pkg/test"
    34  	"istio.io/istio/pkg/test/util/assert"
    35  	"istio.io/istio/pkg/test/util/retry"
    36  	"istio.io/istio/pkg/util/sets"
    37  )
    38  
    39  // Prepare k8s. This can be used in multiple tests, to
    40  // avoid duplicating creation, which can be tricky. It can be used with the fake or
    41  // standalone apiserver.
    42  func initTestEnv(t *testing.T, ki kubernetes.Interface, fx *xdsfake.Updater) {
    43  	cleanup(ki)
    44  	for _, n := range []string{"nsa", "nsb"} {
    45  		_, err := ki.CoreV1().Namespaces().Create(context.TODO(), &v1.Namespace{
    46  			ObjectMeta: metav1.ObjectMeta{
    47  				Name: n,
    48  				Labels: map[string]string{
    49  					"istio-injection": "enabled",
    50  				},
    51  			},
    52  		}, metav1.CreateOptions{})
    53  		if err != nil {
    54  			t.Fatalf("failed creating test namespace: %v", err)
    55  		}
    56  
    57  		// K8S 1.10 also checks if service account exists
    58  		_, err = ki.CoreV1().ServiceAccounts(n).Create(context.TODO(), &v1.ServiceAccount{
    59  			ObjectMeta: metav1.ObjectMeta{
    60  				Name: "default",
    61  				Annotations: map[string]string{
    62  					"kubernetes.io/enforce-mountable-secrets": "false",
    63  				},
    64  			},
    65  			Secrets: []v1.ObjectReference{
    66  				{
    67  					Name: "default-token-2",
    68  					UID:  "1",
    69  				},
    70  			},
    71  		}, metav1.CreateOptions{})
    72  		if err != nil {
    73  			t.Fatalf("failed creating test service account: %v", err)
    74  		}
    75  
    76  		_, err = ki.CoreV1().Secrets(n).Create(context.TODO(), &v1.Secret{
    77  			ObjectMeta: metav1.ObjectMeta{
    78  				Name: "default-token-2",
    79  				Annotations: map[string]string{
    80  					"kubernetes.io/service-account.name": "default",
    81  					"kubernetes.io/service-account.uid":  "1",
    82  				},
    83  			},
    84  			Type: v1.SecretTypeServiceAccountToken,
    85  			Data: map[string][]byte{
    86  				"token": []byte("1"),
    87  			},
    88  		}, metav1.CreateOptions{})
    89  		if err != nil {
    90  			t.Fatalf("failed creating test secret: %v", err)
    91  		}
    92  	}
    93  	fx.Clear()
    94  }
    95  
    96  func cleanup(ki kubernetes.Interface) {
    97  	for _, n := range []string{"nsa", "nsb"} {
    98  		n := n
    99  		pods, err := ki.CoreV1().Pods(n).List(context.TODO(), metav1.ListOptions{})
   100  		if err == nil {
   101  			// Make sure the pods don't exist
   102  			for _, pod := range pods.Items {
   103  				_ = ki.CoreV1().Pods(pod.Namespace).Delete(context.TODO(), pod.Name, metav1.DeleteOptions{})
   104  			}
   105  		}
   106  	}
   107  }
   108  
   109  func TestPodLabelUpdate(t *testing.T) {
   110  	c, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{
   111  		WatchedNamespaces: "nsa,nsb",
   112  	})
   113  
   114  	initTestEnv(t, c.client.Kube(), fx)
   115  	// Setup a service with 1 pod and endpointslices
   116  	createServiceWait(c, "ratings", "nsa",
   117  		nil, nil, []int32{8080}, map[string]string{"app": "test"}, t)
   118  	pod := generatePod("128.0.0.1", "cpod1", "nsa", "", "", map[string]string{"app": "test", "foo": "bar"}, map[string]string{})
   119  	addPods(t, c, fx, pod)
   120  	createEndpoints(t, c, "rating", "nsa", []string{"tcp-port"}, []string{"128.0.0.1"}, []*v1.ObjectReference{
   121  		{
   122  			Kind:      "Pod",
   123  			Namespace: "nsa",
   124  			Name:      "cpod1",
   125  		},
   126  	}, nil)
   127  	fx.WaitOrFail(t, "eds")
   128  	fx.Clear()
   129  
   130  	// Verify podCache
   131  	got := c.pods.getPodsByIP("128.0.0.1")
   132  	assert.Equal(t, got != nil, true)
   133  	assert.Equal(t, map[string]string{"app": "test", "foo": "bar"}, got[0].Labels)
   134  
   135  	pod.Labels["foo"] = "not-bar"
   136  	clienttest.Wrap(t, c.podsClient).CreateOrUpdate(pod)
   137  	fx.StrictMatchOrFail(t, xdsfake.Event{
   138  		Type: "proxy",
   139  		ID:   "128.0.0.1",
   140  	},
   141  		xdsfake.Event{
   142  			Type: "xds",
   143  			ID:   "ratings.nsa.svc.company.com",
   144  		})
   145  
   146  	got = c.pods.getPodsByIP("128.0.0.1")
   147  	assert.Equal(t, got != nil, true)
   148  	assert.Equal(t, map[string]string{"app": "test", "foo": "not-bar"}, got[0].Labels)
   149  }
   150  
   151  func TestHostNetworkPod(t *testing.T) {
   152  	c, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{})
   153  	pods := clienttest.Wrap(t, c.podsClient)
   154  	events := assert.NewTracker[string](t)
   155  	c.AppendWorkloadHandler(func(instance *model.WorkloadInstance, event model.Event) {
   156  		events.Record(fmt.Sprintf("%v/%v", instance.Name, event))
   157  	})
   158  	initTestEnv(t, c.client.Kube(), fx)
   159  	createPod := func(ip, name string) {
   160  		addPods(t, c, fx, generatePod(ip, name, "ns", "1", "", map[string]string{}, map[string]string{}))
   161  	}
   162  
   163  	createPod("128.0.0.1", "pod1")
   164  	assert.Equal(t, c.pods.getPodKeys("128.0.0.1"), []types.NamespacedName{{Name: "pod1", Namespace: "ns"}})
   165  	events.WaitOrdered("pod1/add", "pod1/update")
   166  	createPod("128.0.0.1", "pod2")
   167  	events.WaitOrdered("pod2/add", "pod2/update")
   168  	assert.Equal(t, sets.New(c.pods.getPodKeys("128.0.0.1")...), sets.New(
   169  		types.NamespacedName{Name: "pod1", Namespace: "ns"},
   170  		types.NamespacedName{Name: "pod2", Namespace: "ns"},
   171  	))
   172  
   173  	p := c.pods.getPodByKey(types.NamespacedName{Name: "pod1", Namespace: "ns"})
   174  	if p == nil || p.Name != "pod1" {
   175  		t.Fatalf("unexpected pod: %v", p)
   176  	}
   177  	pods.Delete("pod1", "ns")
   178  	pods.Delete("pod2", "ns")
   179  	events.WaitOrdered("pod1/delete", "pod2/delete")
   180  }
   181  
   182  // Regression test for https://github.com/istio/istio/issues/20676
   183  func TestIPReuse(t *testing.T) {
   184  	c, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{})
   185  	pods := clienttest.Wrap(t, c.podsClient)
   186  	initTestEnv(t, c.client.Kube(), fx)
   187  
   188  	createPod := func(ip, name string) {
   189  		addPods(t, c, fx, generatePod(ip, name, "ns", "1", "", map[string]string{}, map[string]string{}))
   190  	}
   191  
   192  	createPod("128.0.0.1", "pod")
   193  	assert.Equal(t, c.pods.getPodKeys("128.0.0.1"), []types.NamespacedName{{Name: "pod", Namespace: "ns"}})
   194  
   195  	// Change the pod IP. This can happen if the pod moves to another node, for example.
   196  	createPod("128.0.0.2", "pod")
   197  	assert.Equal(t, c.pods.getPodKeys("128.0.0.2"), []types.NamespacedName{{Name: "pod", Namespace: "ns"}})
   198  	assert.Equal(t, c.pods.getPodKeys("128.0.0.1"), nil)
   199  
   200  	// A new pod is created with the old IP. We should get new-pod, not pod
   201  	createPod("128.0.0.1", "new-pod")
   202  	assert.Equal(t, c.pods.getPodKeys("128.0.0.1"), []types.NamespacedName{{Name: "new-pod", Namespace: "ns"}})
   203  
   204  	// A new pod is created with the same IP. This happens with hostNetwork, or maybe we miss an update somehow.
   205  	createPod("128.0.0.1", "another-pod")
   206  	assert.Equal(t, sets.New(c.pods.getPodKeys("128.0.0.1")...), sets.New(
   207  		types.NamespacedName{Name: "new-pod", Namespace: "ns"},
   208  		types.NamespacedName{Name: "another-pod", Namespace: "ns"},
   209  	))
   210  
   211  	fetch := func() sets.Set[types.NamespacedName] { return sets.New(c.pods.getPodKeys("128.0.0.1")...) }
   212  	pods.Delete("another-pod", "ns")
   213  	assert.EventuallyEqual(t, fetch, sets.New(types.NamespacedName{Name: "new-pod", Namespace: "ns"}))
   214  
   215  	pods.Delete("new-pod", "ns")
   216  	assert.EventuallyEqual(t, fetch, nil)
   217  }
   218  
   219  func waitForPod(t test.Failer, c *FakeController, ip string) {
   220  	retry.UntilOrFail(t, func() bool {
   221  		c.pods.RLock()
   222  		defer c.pods.RUnlock()
   223  		if _, ok := c.pods.podsByIP[ip]; ok {
   224  			return true
   225  		}
   226  		return false
   227  	})
   228  }
   229  
   230  func waitForNode(t test.Failer, c *FakeController, name string) {
   231  	retry.UntilOrFail(t, func() bool {
   232  		return c.nodes.Get(name, "") != nil
   233  	}, retry.Timeout(time.Second*1), retry.Delay(time.Millisecond*5))
   234  }
   235  
   236  // Checks that events from the watcher create the proper internal structures
   237  func TestPodCacheEvents(t *testing.T) {
   238  	t.Parallel()
   239  	c, _ := NewFakeControllerWithOptions(t, FakeControllerOptions{})
   240  
   241  	ns := "default"
   242  	podCache := c.pods
   243  
   244  	handled := 0
   245  	podCache.c.handlers.AppendWorkloadHandler(func(*model.WorkloadInstance, model.Event) {
   246  		handled++
   247  	})
   248  
   249  	f := podCache.onEvent
   250  
   251  	ip := "172.0.3.35"
   252  	pod1 := metav1.ObjectMeta{Name: "pod1", Namespace: ns}
   253  	if err := f(nil, &v1.Pod{ObjectMeta: pod1}, model.EventAdd); err != nil {
   254  		t.Error(err)
   255  	}
   256  
   257  	notReadyCondition := []v1.PodCondition{{Type: v1.PodReady, Status: v1.ConditionFalse}}
   258  	readyCondition := []v1.PodCondition{{Type: v1.PodReady, Status: v1.ConditionTrue}}
   259  
   260  	if err := f(nil,
   261  		&v1.Pod{ObjectMeta: pod1, Status: v1.PodStatus{Conditions: notReadyCondition, PodIP: ip, Phase: v1.PodPending}},
   262  		model.EventUpdate); err != nil {
   263  		t.Error(err)
   264  	}
   265  	if handled != 0 {
   266  		t.Errorf("notified workload handler %d times, want %d", handled, 0)
   267  	}
   268  
   269  	if err := f(nil, &v1.Pod{ObjectMeta: pod1, Status: v1.PodStatus{Conditions: readyCondition, PodIP: ip, Phase: v1.PodPending}}, model.EventUpdate); err != nil {
   270  		t.Error(err)
   271  	}
   272  	if handled != 1 {
   273  		t.Errorf("notified workload handler %d times, want %d", handled, 1)
   274  	}
   275  	assert.Equal(t, c.pods.getPodKeys(ip), []types.NamespacedName{{Name: "pod1", Namespace: "default"}})
   276  
   277  	if err := f(nil,
   278  		&v1.Pod{ObjectMeta: pod1, Status: v1.PodStatus{Conditions: readyCondition, PodIP: ip, Phase: v1.PodFailed}}, model.EventUpdate); err != nil {
   279  		t.Error(err)
   280  	}
   281  	if handled != 2 {
   282  		t.Errorf("notified workload handler %d times, want %d", handled, 2)
   283  	}
   284  	assert.Equal(t, podCache.getPodKeys(ip), nil)
   285  
   286  	pod1.DeletionTimestamp = &metav1.Time{Time: time.Now()}
   287  	if err := f(nil, &v1.Pod{ObjectMeta: pod1, Status: v1.PodStatus{PodIP: ip, Phase: v1.PodFailed}}, model.EventUpdate); err != nil {
   288  		t.Error(err)
   289  	}
   290  	if handled != 2 {
   291  		t.Errorf("notified workload handler %d times, want %d", handled, 2)
   292  	}
   293  
   294  	pod2 := metav1.ObjectMeta{Name: "pod2", Namespace: ns}
   295  	if err := f(nil, &v1.Pod{ObjectMeta: pod2, Status: v1.PodStatus{Conditions: readyCondition, PodIP: ip, Phase: v1.PodRunning}}, model.EventAdd); err != nil {
   296  		t.Error(err)
   297  	}
   298  	if handled != 3 {
   299  		t.Errorf("notified workload handler %d times, want %d", handled, 3)
   300  	}
   301  	assert.Equal(t, sets.New(c.pods.getPodKeys(ip)...), sets.New(types.NamespacedName{Name: "pod2", Namespace: "default"}))
   302  
   303  	if err := f(nil, &v1.Pod{ObjectMeta: pod1, Status: v1.PodStatus{PodIP: ip, Phase: v1.PodFailed}}, model.EventDelete); err != nil {
   304  		t.Error(err)
   305  	}
   306  	if handled != 3 {
   307  		t.Errorf("notified workload handler %d times, want %d", handled, 3)
   308  	}
   309  	assert.Equal(t, sets.New(c.pods.getPodKeys(ip)...), sets.New(types.NamespacedName{Name: "pod2", Namespace: "default"}))
   310  
   311  	if err := f(nil, &v1.Pod{ObjectMeta: pod2, Spec: v1.PodSpec{
   312  		RestartPolicy: v1.RestartPolicyOnFailure,
   313  	}, Status: v1.PodStatus{Conditions: readyCondition, PodIP: ip, Phase: v1.PodFailed}}, model.EventUpdate); err != nil {
   314  		t.Error(err)
   315  	}
   316  	if handled != 4 {
   317  		t.Errorf("notified workload handler %d times, want %d", handled, 4)
   318  	}
   319  
   320  	assert.Equal(t, c.pods.getPodsByIP(ip), nil)
   321  
   322  	if err := f(nil, &v1.Pod{ObjectMeta: pod2, Status: v1.PodStatus{Conditions: readyCondition, PodIP: ip, Phase: v1.PodFailed}}, model.EventDelete); err != nil {
   323  		t.Error(err)
   324  	}
   325  	if handled != 4 {
   326  		t.Errorf("notified workload handler %d times, want %d", handled, 5)
   327  	}
   328  }
   329  
   330  func TestPodUpdates(t *testing.T) {
   331  	c, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{
   332  		WatchedNamespaces: "nsa,nsb",
   333  	})
   334  
   335  	initTestEnv(t, c.client.Kube(), fx)
   336  
   337  	// Namespace must be lowercase (nsA doesn't work)
   338  	pods := []*v1.Pod{
   339  		generatePod("128.0.0.1", "cpod1", "nsa", "", "", map[string]string{"app": "test-app"}, map[string]string{}),
   340  		generatePod("128.0.0.2", "cpod2", "nsa", "", "", map[string]string{"app": "prod-app-1"}, map[string]string{}),
   341  		generatePod("128.0.0.3", "cpod3", "nsb", "", "", map[string]string{"app": "prod-app-2"}, map[string]string{}),
   342  	}
   343  
   344  	addPods(t, c, fx, pods...)
   345  
   346  	// Verify podCache
   347  	wantLabels := map[string]labels.Instance{
   348  		"128.0.0.1": {"app": "test-app"},
   349  		"128.0.0.2": {"app": "prod-app-1"},
   350  		"128.0.0.3": {"app": "prod-app-2"},
   351  	}
   352  	for addr, wantTag := range wantLabels {
   353  		pod := c.pods.getPodsByIP(addr)
   354  		if pod == nil {
   355  			t.Error("Not found ", addr)
   356  			continue
   357  		}
   358  		if !reflect.DeepEqual(wantTag, labels.Instance(pod[0].Labels)) {
   359  			t.Errorf("Expected %v got %v", wantTag, labels.Instance(pod[0].Labels))
   360  		}
   361  	}
   362  
   363  	// This pod exists, but should not be in the cache because it is in a
   364  	// namespace not watched by the controller.
   365  	assert.Equal(t, c.pods.getPodsByIP("128.0.0.4"), nil)
   366  
   367  	// This pod should not be in the cache because it never existed.
   368  	assert.Equal(t, c.pods.getPodsByIP("128.0.0.128"), nil)
   369  }