package lifted import ( "fmt" "reflect" "testing" "time" "github.com/stretchr/testify/assert" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apiserver/pkg/storage/names" "k8s.io/utils/pointer" ) // +lifted:source=https://github.com/kubernetes/kubernetes/blob/release-1.26/pkg/controller/deployment/util/deployment_util_test.go#L40-L49 func newDControllerRef(d *appsv1.Deployment) *metav1.OwnerReference { isController := true return &metav1.OwnerReference{ APIVersion: "apps/v1", Kind: "Deployment", Name: d.GetName(), UID: d.GetUID(), Controller: &isController, } } // +lifted:source=https://github.com/kubernetes/kubernetes/blob/release-1.26/pkg/controller/deployment/util/deployment_util_test.go#L51-L67 // +lifted:changed // generateRS creates a replica set, with the input deployment's template as its template func generateRS(deployment appsv1.Deployment) appsv1.ReplicaSet { template := deployment.Spec.Template.DeepCopy() return appsv1.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{ UID: "test", Name: names.SimpleNameGenerator.GenerateName("replicaset"), Labels: template.Labels, OwnerReferences: []metav1.OwnerReference{*newDControllerRef(&deployment)}, }, Spec: appsv1.ReplicaSetSpec{ Replicas: new(int32), Template: *template, Selector: &metav1.LabelSelector{MatchLabels: template.Labels}, }, } } // +lifted:source=https://github.com/kubernetes/kubernetes/blob/release-1.26/pkg/controller/deployment/util/deployment_util_test.go#L73-L108 // generateDeployment creates a deployment, with the input image as its template func generateDeployment(image string) appsv1.Deployment { podLabels := map[string]string{"name": image} terminationSec := int64(30) enableServiceLinks := corev1.DefaultEnableServiceLinks return appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: image, Annotations: make(map[string]string), }, Spec: appsv1.DeploymentSpec{ Replicas: func(i int32) *int32 { return &i }(1), Selector: &metav1.LabelSelector{MatchLabels: podLabels}, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: podLabels, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: image, Image: image, ImagePullPolicy: corev1.PullAlways, TerminationMessagePath: corev1.TerminationMessagePathDefault, }, }, DNSPolicy: corev1.DNSClusterFirst, TerminationGracePeriodSeconds: &terminationSec, RestartPolicy: corev1.RestartPolicyAlways, SecurityContext: &corev1.PodSecurityContext{}, EnableServiceLinks: &enableServiceLinks, }, }, }, } } // +lifted:source=https://github.com/kubernetes/kubernetes/blob/release-1.26/pkg/controller/deployment/util/deployment_util_test.go#L110-L121 func generatePodTemplateSpec(name, nodeName string, annotations, labels map[string]string) corev1.PodTemplateSpec { return corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Name: name, Annotations: annotations, Labels: labels, }, Spec: corev1.PodSpec{ NodeName: nodeName, }, } } func TestReplicaSetsByCreationTimestamp_Len(t *testing.T) { rs1 := &appsv1.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{ Name: "rs1", CreationTimestamp: metav1.Now(), }, } rs2 := &appsv1.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{ Name: "rs2", CreationTimestamp: metav1.Now(), }, } rs3 := &appsv1.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{ Name: "rs3", CreationTimestamp: metav1.Now(), }, } rsList := ReplicaSetsByCreationTimestamp{rs1, rs2, rs3} expectedLen := 3 actualLen := rsList.Len() if actualLen != expectedLen { t.Errorf("Expected length to be %d, but got %d", expectedLen, actualLen) } } func TestReplicaSetsByCreationTimestamp_Swap(t *testing.T) { rs1 := &appsv1.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{ Name: "rs1", CreationTimestamp: metav1.Now(), }, } rs2 := &appsv1.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{ Name: "rs2", CreationTimestamp: metav1.Now(), }, } r := ReplicaSetsByCreationTimestamp{rs1, rs2} r.Swap(0, 1) if r[0] != rs2 || r[1] != rs1 { t.Errorf("Swap failed, expected %v, got %v", []*appsv1.ReplicaSet{rs2, rs1}, r) } } func TestReplicaSetsByCreationTimestamp_Less(t *testing.T) { now := time.Now() rs1 := &appsv1.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{ Name: "rs1", CreationTimestamp: metav1.NewTime(now), }, } rs2 := &appsv1.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{ Name: "rs2", CreationTimestamp: metav1.NewTime(now.Add(time.Duration(-10) * time.Minute)), }, } r := ReplicaSetsByCreationTimestamp{rs1, rs2} res := r.Less(0, 1) if res != false { t.Errorf("Expect false, but got true") } r = ReplicaSetsByCreationTimestamp{rs1, rs1} res = r.Less(0, 1) if res != false { t.Errorf("Expect false, but got true") } } func TestListReplicaSetsByDeployment(t *testing.T) { rs1 := &appsv1.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{ Name: "rs1", Namespace: "test", Labels: map[string]string{"key1": "value1"}, OwnerReferences: []metav1.OwnerReference{ { UID: "test", Controller: pointer.Bool(true), }, }, }, } err := fmt.Errorf("error") tests := []struct { name string deployment *appsv1.Deployment rs *appsv1.ReplicaSet rsListFunc RsListFunc wantErr bool wantRS bool }{ { name: "get rs without error", deployment: &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test", UID: "test", }, Spec: appsv1.DeploymentSpec{ Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{ "key1": "value1", }, }, }, }, rsListFunc: func(namespace string, selector labels.Selector) ([]*appsv1.ReplicaSet, error) { return []*appsv1.ReplicaSet{rs1}, nil }, wantErr: false, wantRS: true, }, { name: "failed in LabelSelectorAsSelector", deployment: &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test", UID: "test", }, Spec: appsv1.DeploymentSpec{ Selector: &metav1.LabelSelector{ MatchExpressions: []metav1.LabelSelectorRequirement{ { Key: "key1", Operator: "blah", Values: []string{"value1"}, }, }, }, }, }, rsListFunc: func(namespace string, selector labels.Selector) ([]*appsv1.ReplicaSet, error) { return []*appsv1.ReplicaSet{rs1}, nil }, wantErr: true, wantRS: false, }, { name: "failed in ReplicaSetListFunc", deployment: &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test", UID: "test", }, Spec: appsv1.DeploymentSpec{ Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{ "key1": "value1", }, }, }, }, rsListFunc: func(namespace string, selector labels.Selector) ([]*appsv1.ReplicaSet, error) { return []*appsv1.ReplicaSet{rs1}, err }, wantErr: true, wantRS: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rss, err := ListReplicaSetsByDeployment(tt.deployment, tt.rsListFunc) if (err != nil) != tt.wantErr { t.Errorf("ListReplicaSetsByDeployment() error = %v, wantErr %v", err, tt.wantErr) } if tt.wantRS { assert.ObjectsAreEqual([]*appsv1.ReplicaSet{rs1}, rss) } }) } } func TestListPodsByRS(t *testing.T) { pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "pod1", Namespace: "test", Labels: map[string]string{"key1": "value1"}, OwnerReferences: []metav1.OwnerReference{ { UID: "test", Controller: pointer.Bool(true), }, }, }, } err := fmt.Errorf("error") tests := []struct { name string deployment *appsv1.Deployment rsList []*appsv1.ReplicaSet podListFunc PodListFunc wantErr bool wantPod bool }{ { name: "get pods without error", deployment: &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test", UID: "test", }, Spec: appsv1.DeploymentSpec{ Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{ "key1": "value1", }, }, }, }, rsList: []*appsv1.ReplicaSet{ { ObjectMeta: metav1.ObjectMeta{ Name: "rs1", Namespace: "test", Labels: map[string]string{"key1": "value1"}, OwnerReferences: []metav1.OwnerReference{ { UID: "test", Controller: pointer.Bool(true), }, }, }, }, }, podListFunc: func(s string, selector labels.Selector) ([]*corev1.Pod, error) { return []*corev1.Pod{pod}, nil }, wantErr: false, wantPod: true, }, { name: "failed in LabelSelectorAsSelector", deployment: &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test", UID: "test", }, Spec: appsv1.DeploymentSpec{ Selector: &metav1.LabelSelector{ MatchExpressions: []metav1.LabelSelectorRequirement{ { Key: "key1", Operator: "blah", Values: []string{"value1"}, }, }, }, }, }, rsList: []*appsv1.ReplicaSet{ { ObjectMeta: metav1.ObjectMeta{ Name: "rs1", Namespace: "test", Labels: map[string]string{"key1": "value1"}, OwnerReferences: []metav1.OwnerReference{ { UID: "test", Controller: pointer.Bool(true), }, }, }, }, }, podListFunc: func(s string, selector labels.Selector) ([]*corev1.Pod, error) { return []*corev1.Pod{pod}, nil }, wantErr: true, wantPod: false, }, { name: "failed in ReplicaSetListFunc", deployment: &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test", UID: "test", }, Spec: appsv1.DeploymentSpec{ Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{ "key1": "value1", }, }, }, }, rsList: []*appsv1.ReplicaSet{ { ObjectMeta: metav1.ObjectMeta{ Name: "rs1", Namespace: "test", Labels: map[string]string{"key1": "value1"}, OwnerReferences: []metav1.OwnerReference{ { UID: "test", Controller: pointer.Bool(true), }, }, }, }, }, podListFunc: func(s string, selector labels.Selector) ([]*corev1.Pod, error) { return []*corev1.Pod{pod}, err }, wantErr: true, wantPod: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pods, err := ListPodsByRS(tt.deployment, tt.rsList, tt.podListFunc) if (err != nil) != tt.wantErr { t.Errorf("ListPodsByRS() error = %v, wantErr %v", err, tt.wantErr) } if tt.wantPod { assert.ObjectsAreEqual([]*corev1.Pod{pod}, pods) } }) } } // +lifted:source=https://github.com/kubernetes/kubernetes/blob/release-1.26/pkg/controller/deployment/util/deployment_util_test.go#L123-L214 func TestEqualIgnoreHash(t *testing.T) { tests := []struct { Name string former, latter corev1.PodTemplateSpec expected bool }{ { "Same spec, same labels", generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{appsv1.DefaultDeploymentUniqueLabelKey: "value-1", "something": "else"}), generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{appsv1.DefaultDeploymentUniqueLabelKey: "value-1", "something": "else"}), true, }, { "Same spec, only pod-template-hash label value is different", generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{appsv1.DefaultDeploymentUniqueLabelKey: "value-1", "something": "else"}), generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{appsv1.DefaultDeploymentUniqueLabelKey: "value-2", "something": "else"}), true, }, { "Same spec, the former doesn't have pod-template-hash label", generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{"something": "else"}), generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{appsv1.DefaultDeploymentUniqueLabelKey: "value-2", "something": "else"}), true, }, { "Same spec, the label is different, the former doesn't have pod-template-hash label, same number of labels", generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{"something": "else"}), generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{appsv1.DefaultDeploymentUniqueLabelKey: "value-2"}), false, }, { "Same spec, the label is different, the latter doesn't have pod-template-hash label, same number of labels", generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{appsv1.DefaultDeploymentUniqueLabelKey: "value-1"}), generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{"something": "else"}), false, }, { "Same spec, the label is different, and the pod-template-hash label value is the same", generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{appsv1.DefaultDeploymentUniqueLabelKey: "value-1"}), generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{appsv1.DefaultDeploymentUniqueLabelKey: "value-1", "something": "else"}), false, }, { "Different spec, same labels", generatePodTemplateSpec("foo", "foo-node", map[string]string{"former": "value"}, map[string]string{appsv1.DefaultDeploymentUniqueLabelKey: "value-1", "something": "else"}), generatePodTemplateSpec("foo", "foo-node", map[string]string{"latter": "value"}, map[string]string{appsv1.DefaultDeploymentUniqueLabelKey: "value-1", "something": "else"}), false, }, { "Different spec, different pod-template-hash label value", generatePodTemplateSpec("foo-1", "foo-node", map[string]string{}, map[string]string{appsv1.DefaultDeploymentUniqueLabelKey: "value-1", "something": "else"}), generatePodTemplateSpec("foo-2", "foo-node", map[string]string{}, map[string]string{appsv1.DefaultDeploymentUniqueLabelKey: "value-2", "something": "else"}), false, }, { "Different spec, the former doesn't have pod-template-hash label", generatePodTemplateSpec("foo-1", "foo-node-1", map[string]string{}, map[string]string{"something": "else"}), generatePodTemplateSpec("foo-2", "foo-node-2", map[string]string{}, map[string]string{appsv1.DefaultDeploymentUniqueLabelKey: "value-2", "something": "else"}), false, }, { "Different spec, different labels", generatePodTemplateSpec("foo", "foo-node-1", map[string]string{}, map[string]string{"something": "else"}), generatePodTemplateSpec("foo", "foo-node-2", map[string]string{}, map[string]string{"nothing": "else"}), false, }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { runTest := func(t1, t2 *corev1.PodTemplateSpec, reversed bool) { reverseString := "" if reversed { reverseString = " (reverse order)" } // Run equal := EqualIgnoreHash(t1, t2) if equal != test.expected { t.Errorf("%q%s: expected %v", test.Name, reverseString, test.expected) return } if t1.Labels == nil || t2.Labels == nil { t.Errorf("%q%s: unexpected labels becomes nil", test.Name, reverseString) } } runTest(&test.former, &test.latter, false) // Test the same case in reverse order runTest(&test.latter, &test.former, true) }) } } func TestGetNewReplicaSet(t *testing.T) { rs := &appsv1.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{ Name: "rs1", Namespace: "test", Labels: map[string]string{"key1": "value1"}, OwnerReferences: []metav1.OwnerReference{ { UID: "test", Controller: pointer.Bool(true), }, }, }, Spec: appsv1.ReplicaSetSpec{ Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Name: "pod1", Namespace: "test", Labels: map[string]string{appsv1.DefaultDeploymentUniqueLabelKey: "value2"}, }, }, }, } deployment := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test", UID: "test", }, Spec: appsv1.DeploymentSpec{ Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{ "key1": "value1", }, }, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Name: "pod1", Namespace: "test", Labels: map[string]string{appsv1.DefaultDeploymentUniqueLabelKey: "value1"}, }, }, }, } err := fmt.Errorf("error") t.Run("no error", func(t *testing.T) { rsListFunc := func(namespace string, selector labels.Selector) ([]*appsv1.ReplicaSet, error) { return []*appsv1.ReplicaSet{rs}, nil } gotRS, gotErr := GetNewReplicaSet(deployment, rsListFunc) if gotErr != nil { t.Errorf("Expect no error but got %v", err) } if gotRS != rs { t.Errorf("Got wrong rs %+v", gotRS) } }) t.Run("have error", func(t *testing.T) { rsListFunc := func(namespace string, selector labels.Selector) ([]*appsv1.ReplicaSet, error) { return []*appsv1.ReplicaSet{rs}, err } gotRS, gotErr := GetNewReplicaSet(deployment, rsListFunc) if gotErr == nil { t.Errorf("Expect error but got %v", err) } if gotRS != nil { t.Errorf("Expect empty rs, but got %+v", gotRS) } }) } // +lifted:source=https://github.com/kubernetes/kubernetes/blob/release-1.26/pkg/controller/deployment/util/deployment_util_test.go#L216-L267 func TestFindNewReplicaSet(t *testing.T) { now := metav1.Now() later := metav1.Time{Time: now.Add(time.Minute)} deployment := generateDeployment("nginx") newRS := generateRS(deployment) newRS.Labels[appsv1.DefaultDeploymentUniqueLabelKey] = "hash" newRS.CreationTimestamp = later newRSDup := generateRS(deployment) newRSDup.Labels[appsv1.DefaultDeploymentUniqueLabelKey] = "different-hash" newRSDup.CreationTimestamp = now oldDeployment := generateDeployment("nginx") oldDeployment.Spec.Template.Spec.Containers[0].Name = "nginx-old-1" oldRS := generateRS(oldDeployment) oldRS.Status.FullyLabeledReplicas = *(oldRS.Spec.Replicas) tests := []struct { Name string deployment appsv1.Deployment rsList []*appsv1.ReplicaSet expected *appsv1.ReplicaSet }{ { Name: "Get new ReplicaSet with the same template as Deployment spec but different pod-template-hash value", deployment: deployment, rsList: []*appsv1.ReplicaSet{&newRS, &oldRS}, expected: &newRS, }, { Name: "Get the oldest new ReplicaSet when there are more than one ReplicaSet with the same template", deployment: deployment, rsList: []*appsv1.ReplicaSet{&newRS, &oldRS, &newRSDup}, expected: &newRSDup, }, { Name: "Get nil new ReplicaSet", deployment: deployment, rsList: []*appsv1.ReplicaSet{&oldRS}, expected: nil, }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { if rs := FindNewReplicaSet(&test.deployment, test.rsList); !reflect.DeepEqual(rs, test.expected) { t.Errorf("In test case %q, expected %#v, got %#v", test.Name, test.expected, rs) } }) } }