karmada/pkg/util/lifted/deployment_test.go

674 lines
20 KiB
Go

/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
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/ptr"
)
// +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: ptr.To[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: ptr.To[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: ptr.To[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: ptr.To[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: ptr.To[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: ptr.To[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)
}
})
}
}