k8s.io/kubernetes@v1.29.3/pkg/registry/apps/replicaset/strategy_test.go (about)

     1  /*
     2  Copyright 2016 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 replicaset
    18  
    19  import (
    20  	"reflect"
    21  	"testing"
    22  
    23  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    24  	"k8s.io/apimachinery/pkg/util/validation/field"
    25  	genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
    26  	"k8s.io/kubernetes/pkg/apis/apps"
    27  	api "k8s.io/kubernetes/pkg/apis/core"
    28  )
    29  
    30  const (
    31  	fakeImageName  = "fake-name"
    32  	fakeImage      = "fakeimage"
    33  	replicasetName = "test-replicaset"
    34  	namespace      = "test-namespace"
    35  )
    36  
    37  func TestReplicaSetStrategy(t *testing.T) {
    38  	ctx := genericapirequest.NewDefaultContext()
    39  	if !Strategy.NamespaceScoped() {
    40  		t.Errorf("ReplicaSet must be namespace scoped")
    41  	}
    42  	if Strategy.AllowCreateOnUpdate() {
    43  		t.Errorf("ReplicaSet should not allow create on update")
    44  	}
    45  
    46  	validSelector := map[string]string{"a": "b"}
    47  	validPodTemplate := api.PodTemplate{
    48  		Template: api.PodTemplateSpec{
    49  			ObjectMeta: metav1.ObjectMeta{
    50  				Labels: validSelector,
    51  			},
    52  			Spec: api.PodSpec{
    53  				RestartPolicy: api.RestartPolicyAlways,
    54  				DNSPolicy:     api.DNSClusterFirst,
    55  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
    56  			},
    57  		},
    58  	}
    59  	rs := &apps.ReplicaSet{
    60  		ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
    61  		Spec: apps.ReplicaSetSpec{
    62  			Selector: &metav1.LabelSelector{MatchLabels: validSelector},
    63  			Template: validPodTemplate.Template,
    64  		},
    65  		Status: apps.ReplicaSetStatus{
    66  			Replicas:           1,
    67  			ObservedGeneration: int64(10),
    68  		},
    69  	}
    70  
    71  	Strategy.PrepareForCreate(ctx, rs)
    72  	if rs.Status.Replicas != 0 {
    73  		t.Error("ReplicaSet should not allow setting status.replicas on create")
    74  	}
    75  	if rs.Status.ObservedGeneration != int64(0) {
    76  		t.Error("ReplicaSet should not allow setting status.observedGeneration on create")
    77  	}
    78  	errs := Strategy.Validate(ctx, rs)
    79  	if len(errs) != 0 {
    80  		t.Errorf("Unexpected error validating %v", errs)
    81  	}
    82  
    83  	invalidRc := &apps.ReplicaSet{
    84  		ObjectMeta: metav1.ObjectMeta{Name: "bar", ResourceVersion: "4"},
    85  	}
    86  	Strategy.PrepareForUpdate(ctx, invalidRc, rs)
    87  	errs = Strategy.ValidateUpdate(ctx, invalidRc, rs)
    88  	if len(errs) == 0 {
    89  		t.Errorf("Expected a validation error")
    90  	}
    91  	if invalidRc.ResourceVersion != "4" {
    92  		t.Errorf("Incoming resource version on update should not be mutated")
    93  	}
    94  }
    95  
    96  func TestReplicaSetStatusStrategy(t *testing.T) {
    97  	ctx := genericapirequest.NewDefaultContext()
    98  	if !StatusStrategy.NamespaceScoped() {
    99  		t.Errorf("ReplicaSet must be namespace scoped")
   100  	}
   101  	if StatusStrategy.AllowCreateOnUpdate() {
   102  		t.Errorf("ReplicaSet should not allow create on update")
   103  	}
   104  	validSelector := map[string]string{"a": "b"}
   105  	validPodTemplate := api.PodTemplate{
   106  		Template: api.PodTemplateSpec{
   107  			ObjectMeta: metav1.ObjectMeta{
   108  				Labels: validSelector,
   109  			},
   110  			Spec: api.PodSpec{
   111  				RestartPolicy: api.RestartPolicyAlways,
   112  				DNSPolicy:     api.DNSClusterFirst,
   113  				Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
   114  			},
   115  		},
   116  	}
   117  	oldRS := &apps.ReplicaSet{
   118  		ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault, ResourceVersion: "10"},
   119  		Spec: apps.ReplicaSetSpec{
   120  			Replicas: 3,
   121  			Selector: &metav1.LabelSelector{MatchLabels: validSelector},
   122  			Template: validPodTemplate.Template,
   123  		},
   124  		Status: apps.ReplicaSetStatus{
   125  			Replicas:           1,
   126  			ObservedGeneration: int64(10),
   127  		},
   128  	}
   129  	newRS := &apps.ReplicaSet{
   130  		ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault, ResourceVersion: "9"},
   131  		Spec: apps.ReplicaSetSpec{
   132  			Replicas: 1,
   133  			Selector: &metav1.LabelSelector{MatchLabels: validSelector},
   134  			Template: validPodTemplate.Template,
   135  		},
   136  		Status: apps.ReplicaSetStatus{
   137  			Replicas:           3,
   138  			ObservedGeneration: int64(11),
   139  		},
   140  	}
   141  	StatusStrategy.PrepareForUpdate(ctx, newRS, oldRS)
   142  	if newRS.Status.Replicas != 3 {
   143  		t.Errorf("ReplicaSet status updates should allow change of replicas: %v", newRS.Status.Replicas)
   144  	}
   145  	if newRS.Spec.Replicas != 3 {
   146  		t.Errorf("PrepareForUpdate should have preferred spec")
   147  	}
   148  	errs := StatusStrategy.ValidateUpdate(ctx, newRS, oldRS)
   149  	if len(errs) != 0 {
   150  		t.Errorf("Unexpected error %v", errs)
   151  	}
   152  }
   153  
   154  func TestSelectorImmutability(t *testing.T) {
   155  	tests := []struct {
   156  		requestInfo       genericapirequest.RequestInfo
   157  		oldSelectorLabels map[string]string
   158  		newSelectorLabels map[string]string
   159  		expectedErrorList field.ErrorList
   160  	}{
   161  		{
   162  			genericapirequest.RequestInfo{
   163  				APIGroup:   "apps",
   164  				APIVersion: "v1beta2",
   165  				Resource:   "replicasets",
   166  			},
   167  			map[string]string{"a": "b"},
   168  			map[string]string{"c": "d"},
   169  			field.ErrorList{
   170  				&field.Error{
   171  					Type:  field.ErrorTypeInvalid,
   172  					Field: field.NewPath("spec").Child("selector").String(),
   173  					BadValue: &metav1.LabelSelector{
   174  						MatchLabels:      map[string]string{"c": "d"},
   175  						MatchExpressions: []metav1.LabelSelectorRequirement{},
   176  					},
   177  					Detail: "field is immutable",
   178  				},
   179  			},
   180  		},
   181  		{
   182  			genericapirequest.RequestInfo{
   183  				APIGroup:   "extensions",
   184  				APIVersion: "v1beta1",
   185  				Resource:   "replicasets",
   186  			},
   187  			map[string]string{"a": "b"},
   188  			map[string]string{"c": "d"},
   189  			nil,
   190  		},
   191  	}
   192  
   193  	for _, test := range tests {
   194  		oldReplicaSet := newReplicaSetWithSelectorLabels(test.oldSelectorLabels)
   195  		newReplicaSet := newReplicaSetWithSelectorLabels(test.newSelectorLabels)
   196  		context := genericapirequest.NewContext()
   197  		context = genericapirequest.WithRequestInfo(context, &test.requestInfo)
   198  		errorList := rsStrategy{}.ValidateUpdate(context, newReplicaSet, oldReplicaSet)
   199  		if !reflect.DeepEqual(test.expectedErrorList, errorList) {
   200  			t.Errorf("Unexpected error list, expected: %v, actual: %v", test.expectedErrorList, errorList)
   201  		}
   202  	}
   203  }
   204  
   205  func newReplicaSetWithSelectorLabels(selectorLabels map[string]string) *apps.ReplicaSet {
   206  	return &apps.ReplicaSet{
   207  		ObjectMeta: metav1.ObjectMeta{
   208  			Name:            replicasetName,
   209  			Namespace:       namespace,
   210  			ResourceVersion: "1",
   211  		},
   212  		Spec: apps.ReplicaSetSpec{
   213  			Selector: &metav1.LabelSelector{
   214  				MatchLabels:      selectorLabels,
   215  				MatchExpressions: []metav1.LabelSelectorRequirement{},
   216  			},
   217  			Template: api.PodTemplateSpec{
   218  				ObjectMeta: metav1.ObjectMeta{
   219  					Labels: selectorLabels,
   220  				},
   221  				Spec: api.PodSpec{
   222  					RestartPolicy: api.RestartPolicyAlways,
   223  					DNSPolicy:     api.DNSClusterFirst,
   224  					Containers:    []api.Container{{Name: fakeImageName, Image: fakeImage, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
   225  				},
   226  			},
   227  		},
   228  	}
   229  }