its label selector. release: v1.20 file: test/e2e/instrumentation/core_events.go <del> - testname: Event resource lifecycle codename: '[sig-instrumentation] Events should ensure that an event can be fetched, patched, deleted, and listed [Conformance]' description: Create an event, the event MUST exist. The event is patched with a new message, the check MUST have the update message. The event is deleted and MUST NOT show up when listing all events. release: v1.20 </del> <ins> - testname: Event, manage lifecycle of an Event codename: '[sig-instrumentation] Events should manage the lifecycle of an event [Conformance]' description: Attempt to create an event which MUST succeed. Attempt to list all namespaces with a label selector which MUST succeed. One list MUST be found. The event is patched with a new message, the check MUST have the update message. The event is updated with a new series of events, the check MUST confirm this update. The event is deleted and MUST NOT show up when listing all events. release: v1.25 </ins> file: test/e2e/instrumentation/core_events.go - testname: DNS, cluster codename: '[sig-network] DNS should provide /etc/hosts entries for the cluster [Conformance]'


f.NamespacePodSecurityEnforceLevel = admissionapi.LevelPrivileged /* <del> Release: v1.20 Testname: Event resource lifecycle Description: Create an event, the event MUST exist. The event is patched with a new message, the check MUST have the update message. The event is deleted and MUST NOT show up when listing all events. </del> <ins> Release: v1.25 Testname: Event, manage lifecycle of an Event Description: Attempt to create an event which MUST succeed. Attempt to list all namespaces with a label selector which MUST succeed. One list MUST be found. The event is patched with a new message, the check MUST have the update message. The event is updated with a new series of events, the check MUST confirm this update. The event is deleted and MUST NOT show up when listing all events. </ins> */ <del> framework.ConformanceIt("should ensure that an event can be fetched, patched, deleted, and listed", func() { eventTestName := "event-test" ginkgo.By("creating a test event") // create a test event in test namespace _, err := f.ClientSet.CoreV1().Events(f.Namespace.Name).Create(context.TODO(), &v1.Event{ ObjectMeta: metav1.ObjectMeta{ Name: eventTestName, Labels: map[string]string{ "testevent-constant": "true", }, }, Message: "This is a test event", Reason:  "Test", Type:    "Normal", Count:   1, InvolvedObject: v1.ObjectReference{ Namespace: f.Namespace.Name, }, }, metav1.CreateOptions{}) framework.ExpectNoError(err, "failed to create test event") ginkgo.By("listing all events in all namespaces") // get a list of Events in all namespaces to ensure endpoint coverage eventsList, err := f.ClientSet.CoreV1().Events("").List(context.TODO(), metav1.ListOptions{ LabelSelector: "testevent-constant=true", }) framework.ExpectNoError(err, "failed list all events") foundCreatedEvent := false var eventCreatedName string for _, val := range eventsList.Items { if val.ObjectMeta.Name == eventTestName && val.ObjectMeta.Namespace == f.Namespace.Name { foundCreatedEvent = true eventCreatedName = val.ObjectMeta.Name break } } if !foundCreatedEvent { framework.Failf("unable to find test event %s in namespace %s, full list of events is %+v", eventTestName, f.Namespace.Name, eventsList.Items) } ginkgo.By("patching the test event") // patch the event's message eventPatchMessage := "This is a test event - patched" eventPatch, err := json.Marshal(map[string]interface{}{ "message": eventPatchMessage, }) framework.ExpectNoError(err, "failed to marshal the patch JSON payload") _, err = f.ClientSet.CoreV1().Events(f.Namespace.Name).Patch(context.TODO(), eventTestName, types.StrategicMergePatchType, []byte(eventPatch), metav1.PatchOptions{}) framework.ExpectNoError(err, "failed to patch the test event") ginkgo.By("fetching the test event") // get event by name event, err := f.ClientSet.CoreV1().Events(f.Namespace.Name).Get(context.TODO(), eventCreatedName, metav1.GetOptions{}) framework.ExpectNoError(err, "failed to fetch the test event") framework.ExpectEqual(event.Message, eventPatchMessage, "test event message does not match patch message") ginkgo.By("deleting the test event") // delete original event err = f.ClientSet.CoreV1().Events(f.Namespace.Name).Delete(context.TODO(), eventCreatedName, metav1.DeleteOptions{}) framework.ExpectNoError(err, "failed to delete the test event") ginkgo.By("listing all events in all namespaces") // get a list of Events list namespace eventsList, err = f.ClientSet.CoreV1().Events("").List(context.TODO(), metav1.ListOptions{ LabelSelector: "testevent-constant=true", }) framework.ExpectNoError(err, "fail to list all events") foundCreatedEvent = false for _, val := range eventsList.Items { if val.ObjectMeta.Name == eventTestName && val.ObjectMeta.Namespace == f.Namespace.Name { foundCreatedEvent = true break } } if foundCreatedEvent { framework.Failf("Should not have found test event %s in namespace %s, full list of events %+v", eventTestName, f.Namespace.Name, eventsList.Items) } }) </del> <ins> framework.ConformanceIt("should manage the lifecycle of an event", func() { // As per SIG-Arch meeting 14 July 2022 this e2e test now supersede // e2e test "Event resource lifecycle", which has been removed. </ins> <del> ginkgo.It("should manage the lifecycle of an event", func() { </del> eventTestName := "event-test" ginkgo.By("creating a test event")


v1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" <ins> apierrors "k8s.io/apimachinery/pkg/api/errors" </ins> "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels"


framework.ExpectNoError(verifyServeHostnameServiceUp(cs, ns, podNames3, svc3IP, servicePort)) }) <ins> ginkgo.It("should work after the service has been recreated", func() { serviceName := "service-deletion" ns := f.Namespace.Name numPods, servicePort := 1, defaultServeHostnameServicePort ginkgo.By("creating the service " + serviceName + " in namespace " + ns) defer func() { framework.ExpectNoError(StopServeHostnameService(f.ClientSet, ns, serviceName)) }() podNames, svcIP, _ := StartServeHostnameService(cs, getServeHostnameService(serviceName), ns, numPods) framework.ExpectNoError(verifyServeHostnameServiceUp(cs, ns, podNames, svcIP, servicePort)) ginkgo.By("deleting the service " + serviceName + " in namespace " + ns) err := cs.CoreV1().Services(ns).Delete(context.TODO(), serviceName, metav1.DeleteOptions{}) framework.ExpectNoError(err) ginkgo.By("Waiting for the service " + serviceName + " in namespace " + ns + " to disappear") if pollErr := wait.PollImmediate(framework.Poll, e2eservice.RespondingTimeout, func() (bool, error) { _, err := cs.CoreV1().Services(ns).Get(context.TODO(), serviceName, metav1.GetOptions{}) if err != nil { if apierrors.IsNotFound(err) { framework.Logf("Service %s/%s is gone.", ns, serviceName) return true, nil } return false, err } framework.Logf("Service %s/%s still exists", ns, serviceName) return false, nil }); pollErr != nil { framework.Failf("Failed to wait for service to disappear: %v", pollErr) } ginkgo.By("recreating the service " + serviceName + " in namespace " + ns) svc, err := cs.CoreV1().Services(ns).Create(context.TODO(), getServeHostnameService(serviceName), metav1.CreateOptions{}) framework.ExpectNoError(err) framework.ExpectNoError(verifyServeHostnameServiceUp(cs, ns, podNames, svc.Spec.ClusterIP, servicePort)) }) </ins> ginkgo.It("should work after restarting kube-proxy [Disruptive]", func() { kubeProxyLabelSet := map[string]string{clusterAddonLabelKey: kubeProxyLabelName} e2eskipper.SkipUnlessComponentRunsAsPodsAndClientCanDeleteThem(kubeProxyLabelName, cs, metav1.NamespaceSystem, kubeProxyLabelSet)


// Create a pod in one node to get evicted ginkgo.By("creating a client pod that is going to be evicted for the service " + serviceName) evictedPod := e2epod.NewAgnhostPod(namespace, "evicted-pod", nil, nil, nil) <del> evictedPod.Spec.Containers[0].Command = []string{"/bin/sh", "-c", "sleep 10; fallocate -l 10M file; sleep 10000"} </del> <ins> evictedPod.Spec.Containers[0].Command = []string{"/bin/sh", "-c", "sleep 10; dd if=/dev/zero of=file bs=1M count=10; sleep 10000"} </ins> evictedPod.Spec.Containers[0].Name = "evicted-pod" evictedPod.Spec.Containers[0].Resources = v1.ResourceRequirements{ Limits: v1.ResourceList{"ephemeral-storage": resource.MustParse("5Mi")},


Name:  "bar", Image: image, Command: []string{ <del> "/bin/sh", "-c", "sleep 10; fallocate -l 10M file; sleep 10000", </del> <ins> "/bin/sh", "-c", "sleep 10; dd if=/dev/zero of=file bs=1M count=10; sleep 10000", </ins> }, Resources: v1.ResourceRequirements{ Limits: v1.ResourceList{


import ( "context" "fmt" <ins> "log" </ins> "net" "net/url" <ins> "os" </ins> "path" "strings" "sync" "time" grpcprom "github.com/grpc-ecosystem/go-grpc-prometheus" <ins> "go.etcd.io/etcd/client/pkg/v3/logutil" </ins> "go.etcd.io/etcd/client/pkg/v3/transport" clientv3 "go.etcd.io/etcd/client/v3" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" <ins> "go.uber.org/zap" "go.uber.org/zap/zapcore" </ins> "google.golang.org/grpc" "k8s.io/apimachinery/pkg/runtime"


dbMetricsMonitorJitter = 0.5 ) <ins> // TODO(negz): Stop using a package scoped logger. At the time of writing we're // creating an etcd client for each CRD. We need to pass each etcd client a // logger or each client will create its own, which comes with a significant // memory cost (around 20% of the API server's memory when hundreds of CRDs are // present). The correct fix here is to not create a client per CRD. See // https://github.com/kubernetes/kubernetes/issues/111476 for more. var etcd3ClientLogger *zap.Logger </ins> func init() { // grpcprom auto-registers (via an init function) their client metrics, since we are opting out of // using the global prometheus registry and using our own wrapped global registry,


// For reference: https://github.com/kubernetes/kubernetes/pull/81387 legacyregistry.RawMustRegister(grpcprom.DefaultClientMetrics) dbMetricsMonitors = make(map[string]struct{}) <ins> l, err := logutil.CreateDefaultZapLogger(etcdClientDebugLevel()) if err != nil { l = zap.NewNop() } etcd3ClientLogger = l.Named("etcd-client") } // etcdClientDebugLevel translates ETCD_CLIENT_DEBUG into zap log level. // NOTE(negz): This is a copy of a private etcd client function: // https://github.com/etcd-io/etcd/blob/v3.5.4/client/v3/logger.go#L47 func etcdClientDebugLevel() zapcore.Level { envLevel := os.Getenv("ETCD_CLIENT_DEBUG") if envLevel == "" || envLevel == "true" { return zapcore.InfoLevel } var l zapcore.Level if err := l.Set(envLevel); err == nil { log.Printf("Deprecated env ETCD_CLIENT_DEBUG value. Using default level: 'info'") return zapcore.InfoLevel } return l </ins> } func newETCD3HealthCheck(c storagebackend.Config, stopCh <-chan struct{}) (func() error, error) {


} dialOptions = append(dialOptions, grpc.WithContextDialer(dialer)) } cfg := clientv3.Config{ DialTimeout:          dialTimeout, DialKeepAliveTime:    keepaliveTime,


DialOptions:          dialOptions, Endpoints:            c.ServerList, TLS:                  tlsConfig, <ins> Logger:               etcd3ClientLogger, </ins> } return clientv3.New(cfg)


package job import ( <del> "reflect" </del> "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" batchv1 "k8s.io/api/batch/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" <ins> "k8s.io/apimachinery/pkg/labels" </ins> "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request"


}, }, }, <ins> "attempt status update and verify it doesn't change": { job: batch.Job{ ObjectMeta: getValidObjectMeta(0), Spec: batch.JobSpec{ Selector: validSelector, Template: validPodTemplateSpec, }, Status: batch.JobStatus{ Active: 1, }, }, updatedJob: batch.Job{ ObjectMeta: getValidObjectMeta(0), Spec: batch.JobSpec{ Selector: validSelector, Template: validPodTemplateSpec, }, Status: batch.JobStatus{ Active: 2, }, }, wantJob: batch.Job{ ObjectMeta: getValidObjectMeta(0), Spec: batch.JobSpec{ Selector: validSelector, Template: validPodTemplateSpec, }, Status: batch.JobStatus{ Active: 1, }, }, }, "ensure generation doesn't change over non spec updates": { job: batch.Job{ ObjectMeta: getValidObjectMeta(0), Spec: batch.JobSpec{ Selector: validSelector, Template: validPodTemplateSpec, }, Status: batch.JobStatus{ Active: 1, }, }, updatedJob: batch.Job{ ObjectMeta: getValidObjectMetaWithAnnotations(0, map[string]string{"hello": "world"}), Spec: batch.JobSpec{ Selector: validSelector, Template: validPodTemplateSpec, }, Status: batch.JobStatus{ Active: 2, }, }, wantJob: batch.Job{ ObjectMeta: getValidObjectMetaWithAnnotations(0, map[string]string{"hello": "world"}), Spec: batch.JobSpec{ Selector: validSelector, Template: validPodTemplateSpec, }, Status: batch.JobStatus{ Active: 1, }, }, }, "test updating suspend false->true": { job: batch.Job{ ObjectMeta: getValidObjectMeta(0), Spec: batch.JobSpec{ Selector: validSelector, Template: validPodTemplateSpec, Suspend:  pointer.Bool(false), }, }, updatedJob: batch.Job{ ObjectMeta: getValidObjectMetaWithAnnotations(0, map[string]string{"hello": "world"}), Spec: batch.JobSpec{ Selector: validSelector, Template: validPodTemplateSpec, Suspend:  pointer.Bool(true), }, }, wantJob: batch.Job{ ObjectMeta: getValidObjectMetaWithAnnotations(1, map[string]string{"hello": "world"}), Spec: batch.JobSpec{ Selector: validSelector, Template: validPodTemplateSpec, Suspend:  pointer.Bool(true), }, }, }, "test updating suspend nil -> true": { job: batch.Job{ ObjectMeta: getValidObjectMeta(0), Spec: batch.JobSpec{ Selector: validSelector, Template: validPodTemplateSpec, }, }, updatedJob: batch.Job{ ObjectMeta: getValidObjectMetaWithAnnotations(0, map[string]string{"hello": "world"}), Spec: batch.JobSpec{ Selector: validSelector, Template: validPodTemplateSpec, Suspend:  pointer.Bool(true), }, }, wantJob: batch.Job{ ObjectMeta: getValidObjectMetaWithAnnotations(1, map[string]string{"hello": "world"}), Spec: batch.JobSpec{ Selector: validSelector, Template: validPodTemplateSpec, Suspend:  pointer.Bool(true), }, }, }, </ins> } for name, tc := range cases {


Strategy.PrepareForUpdate(ctx, &tc.updatedJob, &tc.job) if diff := cmp.Diff(tc.wantJob, tc.updatedJob); diff != "" { <del> t.Errorf("Job pod failure policy (-want,+got):n%s", diff) </del> <ins> t.Errorf("Job update differences (-want,+got):n%s", diff) </ins> } }) } } <del> // TestJobStrategy_PrepareForUpdate tests various scenearios for PrepareForCreate </del> <ins> // TestJobStrategy_PrepareForCreate tests various scenarios for PrepareForCreate </ins> func TestJobStrategy_PrepareForCreate(t *testing.T) { validSelector := getValidLabelSelector() validPodTemplateSpec := getValidPodTemplateSpecForSelector(validSelector)


}, }, }, <ins> "job does not allow setting status on create": { job: batch.Job{ ObjectMeta: getValidObjectMeta(0), Spec: batch.JobSpec{ Selector: validSelector, Template: validPodTemplateSpec, }, Status: batch.JobStatus{ Active: 1, }, }, wantJob: batch.Job{ ObjectMeta: getValidObjectMetaWithAnnotations(1, map[string]string{batchv1.JobTrackingFinalizer: ""}), Spec: batch.JobSpec{ Selector: validSelector, Template: validPodTemplateSpec, }, }, }, </ins> } for name, tc := range cases {


} } <del> // TODO(#111514): refactor by spliting into dedicated test functions func TestJobStrategy(t *testing.T) { ctx := genericapirequest.NewDefaultContext() if !Strategy.NamespaceScoped() { t.Errorf("Job must be namespace scoped") } if Strategy.AllowCreateOnUpdate() { t.Errorf("Job should not allow create on update") } validSelector := &metav1.LabelSelector{ MatchLabels: map[string]string{"a": "b"}, } validPodTemplateSpec := api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: validSelector.MatchLabels, }, Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyOnFailure, DNSPolicy:     api.DNSClusterFirst, Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, }, } job := &batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name:      "myjob", Namespace: metav1.NamespaceDefault, Annotations: map[string]string{ "foo": "bar", }, ResourceVersion: "0", }, Spec: batch.JobSpec{ Selector:       validSelector, Template:       validPodTemplateSpec, ManualSelector: pointer.BoolPtr(true), Completions:    pointer.Int32Ptr(2), // Set gated values. Suspend:                 pointer.BoolPtr(true), TTLSecondsAfterFinished: pointer.Int32Ptr(0), CompletionMode:          completionModePtr(batch.IndexedCompletion), }, Status: batch.JobStatus{ Active: 11, }, } Strategy.PrepareForCreate(ctx, job) if job.Status.Active != 0 { t.Errorf("Job does not allow setting status on create") } if job.Generation != 1 { t.Errorf("expected Generation=1, got %d", job.Generation) } errs := Strategy.Validate(ctx, job) if len(errs) != 0 { t.Errorf("Unexpected error validating %v", errs) } if job.Spec.CompletionMode == nil { t.Errorf("Job should allow setting .spec.completionMode") } wantAnnotations := map[string]string{ "foo":                        "bar", batchv1.JobTrackingFinalizer: "", } if diff := cmp.Diff(wantAnnotations, job.Annotations); diff != "" { t.Errorf("Job has annotations (-want,+got):n%s", diff) } parallelism := int32(10) // ensure we do not change generation for non-spec updates updatedLabelJob := job.DeepCopy() updatedLabelJob.Labels = map[string]string{"a": "true"} Strategy.PrepareForUpdate(ctx, updatedLabelJob, job) if updatedLabelJob.Generation != 1 { t.Errorf("expected Generation=1, got %d", updatedLabelJob.Generation) } errs = Strategy.ValidateUpdate(ctx, updatedLabelJob, job) if len(errs) != 0 { t.Errorf("Unexpected update validation error") } updatedJob := &batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name:            "bar", ResourceVersion: "4", // remove one annotation and try to enforce the job tracking finalizer. Annotations: map[string]string{batchv1.JobTrackingFinalizer: ""}, }, Spec: batch.JobSpec{ Parallelism: &parallelism, Completions: pointer.Int32Ptr(2), // Update gated features. TTLSecondsAfterFinished: pointer.Int32Ptr(1), CompletionMode:          completionModePtr(batch.IndexedCompletion), // No change because field is immutable. }, Status: batch.JobStatus{ Active: 11, }, } // Ensure we do not change status job.Status.Active = 10 Strategy.PrepareForUpdate(ctx, updatedJob, job) if updatedJob.Status.Active != 10 { t.Errorf("PrepareForUpdate should have preserved prior version status") } if updatedJob.Generation != 2 { t.Errorf("expected Generation=2, got %d", updatedJob.Generation) } wantAnnotations = map[string]string{ batchv1.JobTrackingFinalizer: "", } if diff := cmp.Diff(wantAnnotations, updatedJob.Annotations); diff != "" { t.Errorf("Job has annotations (-want,+got):n%s", diff) } errs = Strategy.ValidateUpdate(ctx, updatedJob, job) if len(errs) == 0 { t.Errorf("Expected a validation error") } // Test updating suspend false->true and nil-> true when the feature gate is // disabled. We don't care about other combinations. job.Spec.Suspend, updatedJob.Spec.Suspend = pointer.BoolPtr(false), pointer.BoolPtr(true) Strategy.PrepareForUpdate(ctx, updatedJob, job) job.Spec.Suspend, updatedJob.Spec.Suspend = nil, pointer.BoolPtr(true) Strategy.PrepareForUpdate(ctx, updatedJob, job) </del> <ins> func TestJobStrategy_GarbageCollectionPolicy(t *testing.T) { </ins> // Make sure we correctly implement the interface. // Otherwise a typo could silently change the default. var gcds rest.GarbageCollectionDeleteStrategy = Strategy


} } <del> func TestValidateToleratingBadLabels(t *testing.T) { invalidSelector := getValidLabelSelector() invalidSelector.MatchExpressions = []metav1.LabelSelectorRequirement{{Key: "key", Operator: metav1.LabelSelectorOpNotIn, Values: []string{"bad value"}}} validPodTemplateSpec := getValidPodTemplateSpecForSelector(getValidLabelSelector()) job := &batch.Job{ ObjectMeta: getValidObjectMeta(0), Spec: batch.JobSpec{ Selector:       invalidSelector, ManualSelector: pointer.BoolPtr(true), Template:       validPodTemplateSpec, }, } job.ResourceVersion = "1" oldObj := job.DeepCopy() newObj := job.DeepCopy() context := genericapirequest.NewContext() errorList := Strategy.ValidateUpdate(context, newObj, oldObj) if len(errorList) > 0 { t.Errorf("Unexpected error list with no-op update of bad object: %v", errorList) } } func TestJobStrategyValidateUpdate(t *testing.T) { </del> <ins> func TestJobStrategy_ValidateUpdate(t *testing.T) { </ins> ctx := genericapirequest.NewDefaultContext() validSelector := &metav1.LabelSelector{ MatchLabels: map[string]string{"a": "b"},


}, }, update: func(job *batch.Job) { <del> // change something. </del> job.Annotations["foo"] = "bar" }, }, <ins> "deleting user annotation": { job: &batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name:            "myjob", Namespace:       metav1.NamespaceDefault, ResourceVersion: "0", Annotations: map[string]string{ batch.JobTrackingFinalizer: "", "foo":                      "bar", }, }, Spec: batch.JobSpec{ Selector:       validSelector, Template:       validPodTemplateSpec, ManualSelector: pointer.BoolPtr(true), Parallelism:    pointer.Int32Ptr(1), }, }, update: func(job *batch.Job) { delete(job.Annotations, "foo") }, }, </ins> "updating node selector for unsuspended job disallowed": { job: &batch.Job{ ObjectMeta: metav1.ObjectMeta{


}, mutableSchedulingDirectivesEnabled: false, }, <ins> "invalid label selector": { job: &batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name:            "myjob", Namespace:       metav1.NamespaceDefault, ResourceVersion: "0", Annotations:     map[string]string{"foo": "bar"}, }, Spec: batch.JobSpec{ Selector: &metav1.LabelSelector{ MatchLabels:      map[string]string{"a": "b"}, MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "key", Operator: metav1.LabelSelectorOpNotIn, Values: []string{"bad value"}}}, }, ManualSelector: pointer.BoolPtr(true), Template:       validPodTemplateSpec, }, }, update: func(job *batch.Job) { job.Annotations["hello"] = "world" }, }, </ins> } for name, tc := range cases { t.Run(name, func(t *testing.T) {


} } <del> func TestJobStrategyWithGeneration(t *testing.T) { ctx := genericapirequest.NewDefaultContext() theUID := types.UID("1a2b3c4d5e6f7g8h9i0k") validPodTemplateSpec := api.PodTemplateSpec{ Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyOnFailure, DNSPolicy:     api.DNSClusterFirst, Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, }, } job := &batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name:      "myjob2", Namespace: metav1.NamespaceDefault, UID:       theUID, }, Spec: batch.JobSpec{ Selector: nil, Template: validPodTemplateSpec, }, } Strategy.PrepareForCreate(ctx, job) errs := Strategy.Validate(ctx, job) if len(errs) != 0 { t.Errorf("Unexpected error validating %v", errs) } // Validate the stuff that validation should have validated. if job.Spec.Selector == nil { t.Errorf("Selector not generated") } expectedLabels := make(map[string]string) expectedLabels["controller-uid"] = string(theUID) if !reflect.DeepEqual(job.Spec.Selector.MatchLabels, expectedLabels) { t.Errorf("Expected label selector not generated") } if job.Spec.Template.ObjectMeta.Labels == nil { t.Errorf("Expected template labels not generated") } if v, ok := job.Spec.Template.ObjectMeta.Labels["job-name"]; !ok || v != "myjob2" { t.Errorf("Expected template labels not present") } if v, ok := job.Spec.Template.ObjectMeta.Labels["controller-uid"]; !ok || v != string(theUID) { t.Errorf("Expected template labels not present: ok: %v, v: %v", ok, v) } } func TestJobStatusStrategy(t *testing.T) { </del> <ins> func TestJobStrategy_WarningsOnUpdate(t *testing.T) { </ins> ctx := genericapirequest.NewDefaultContext() <del> if !StatusStrategy.NamespaceScoped() { t.Errorf("Job must be namespace scoped") } if StatusStrategy.AllowCreateOnUpdate() { t.Errorf("Job should not allow create on update") } </del> validSelector := &metav1.LabelSelector{ MatchLabels: map[string]string{"a": "b"}, }


Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, }, } <del> oldParallelism := int32(10) newParallelism := int32(11) oldJob := &batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name:            "myjob", Namespace:       metav1.NamespaceDefault, ResourceVersion: "10", }, Spec: batch.JobSpec{ Selector:    validSelector, Template:    validPodTemplateSpec, Parallelism: &oldParallelism, }, Status: batch.JobStatus{ Active: 11, }, } newJob := &batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name:            "myjob", Namespace:       metav1.NamespaceDefault, ResourceVersion: "9", }, Spec: batch.JobSpec{ Selector:    validSelector, Template:    validPodTemplateSpec, Parallelism: &newParallelism, }, Status: batch.JobStatus{ Active: 12, }, } </del> <ins> cases := map[string]struct { oldJob            *batch.Job job               *batch.Job wantWarningsCount int32 }{ "generation 0 for both": { job: &batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name:            "myjob", Namespace:       metav1.NamespaceDefault, ResourceVersion: "0", Generation:      0, }, Spec: batch.JobSpec{ Selector:       validSelector, Template:       validPodTemplateSpec, ManualSelector: pointer.BoolPtr(true), Parallelism:    pointer.Int32Ptr(1), }, }, </ins> <del> StatusStrategy.PrepareForUpdate(ctx, newJob, oldJob) if newJob.Status.Active != 12 { t.Errorf("Job status updates must allow changes to job status") } if *newJob.Spec.Parallelism != 10 { t.Errorf("Job status updates must now allow changes to job spec") </del> <ins> oldJob: &batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name:            "myjob", Namespace:       metav1.NamespaceDefault, ResourceVersion: "0", Generation:      0, }, Spec: batch.JobSpec{ Selector:       validSelector, Template:       validPodTemplateSpec, ManualSelector: pointer.BoolPtr(true), Parallelism:    pointer.Int32Ptr(1), }, }, }, "generation 1 for new; force WarningsOnUpdate to check PodTemplate for updates": { job: &batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name:            "myjob", Namespace:       metav1.NamespaceDefault, ResourceVersion: "0", Generation:      1, }, Spec: batch.JobSpec{ Selector:       validSelector, Template:       validPodTemplateSpec, ManualSelector: pointer.BoolPtr(true), Parallelism:    pointer.Int32Ptr(1), }, }, oldJob: &batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name:            "myjob", Namespace:       metav1.NamespaceDefault, ResourceVersion: "0", Generation:      0, }, Spec: batch.JobSpec{ Selector:       validSelector, Template:       validPodTemplateSpec, ManualSelector: pointer.BoolPtr(true), Parallelism:    pointer.Int32Ptr(1), }, }, }, "force validation failure in pod template": { job: &batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name:            "myjob", Namespace:       metav1.NamespaceDefault, ResourceVersion: "0", Generation:      1, }, Spec: batch.JobSpec{ Selector: validSelector, Template: api.PodTemplateSpec{ Spec: api.PodSpec{Volumes: []api.Volume{{Name: "volume-name"}, {Name: "volume-name"}}}, }, ManualSelector: pointer.BoolPtr(true), Parallelism:    pointer.Int32Ptr(1), }, }, oldJob: &batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name:            "myjob", Namespace:       metav1.NamespaceDefault, ResourceVersion: "0", Generation:      0, }, Spec: batch.JobSpec{ Selector:       validSelector, Template:       validPodTemplateSpec, ManualSelector: pointer.BoolPtr(true), Parallelism:    pointer.Int32Ptr(1), }, }, wantWarningsCount: 1, }, } for val, tc := range cases { t.Run(val, func(t *testing.T) { gotWarnings := Strategy.WarningsOnUpdate(ctx, tc.job, tc.oldJob) if len(gotWarnings) != int(tc.wantWarningsCount) { t.Errorf("got warning length of %d but expected %d", len(gotWarnings), tc.wantWarningsCount) } }) } } func TestJobStrategy_WarningsOnCreate(t *testing.T) { ctx := genericapirequest.NewDefaultContext() theUID := types.UID("1a2b3c4d5e6f7g8h9i0k") validSelector := &metav1.LabelSelector{ MatchLabels: map[string]string{"a": "b"}, } validSpec := batch.JobSpec{ Selector: nil, Template: api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: validSelector.MatchLabels, }, Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyOnFailure, DNSPolicy:     api.DNSClusterFirst, Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, }, }, } testcases := map[string]struct { job               *batch.Job wantWarningsCount int32 }{ "happy path job": { job: &batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name:      "myjob2", Namespace: metav1.NamespaceDefault, UID:       theUID, }, Spec: validSpec, }, }, "dns invalid name": { wantWarningsCount: 1, job: &batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name:      "my job2", Namespace: metav1.NamespaceDefault, UID:       theUID, }, Spec: validSpec, }, }, } for name, tc := range testcases { t.Run(name, func(t *testing.T) { gotWarnings := Strategy.WarningsOnCreate(ctx, tc.job) if len(gotWarnings) != int(tc.wantWarningsCount) { t.Errorf("got warning length of %d but expected %d", len(gotWarnings), tc.wantWarningsCount) } }) } } func TestJobStrategy_Validate(t *testing.T) { ctx := genericapirequest.NewDefaultContext() theUID := types.UID("1a2b3c4d5e6f7g8h9i0k") validSelector := &metav1.LabelSelector{ MatchLabels: map[string]string{"a": "b"}, } validPodSpec := api.PodSpec{ RestartPolicy: api.RestartPolicyOnFailure, DNSPolicy:     api.DNSClusterFirst, Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, } validObjectMeta := metav1.ObjectMeta{ Name:      "myjob2", Namespace: metav1.NamespaceDefault, UID:       theUID, } testcases := map[string]struct { job              *batch.Job wantJob          *batch.Job wantWarningCount int32 }{ "valid job with labels in pod template": { job: &batch.Job{ ObjectMeta: validObjectMeta, Spec: batch.JobSpec{ Selector: nil, Template: api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: validSelector.MatchLabels, }, Spec: validPodSpec, }}, }, wantJob: &batch.Job{ ObjectMeta: validObjectMeta, Spec: batch.JobSpec{ Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"controller-uid": string(theUID)}}, Template: api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: validSelector.MatchLabels, }, Spec: validPodSpec, }}, }, }, "no labels in job": { job: &batch.Job{ ObjectMeta: validObjectMeta, Spec: batch.JobSpec{ Selector: nil, Template: api.PodTemplateSpec{ Spec: validPodSpec, }}, }, wantJob: &batch.Job{ ObjectMeta: validObjectMeta, Spec: batch.JobSpec{ Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"controller-uid": string(theUID)}}, Template: api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"job-name": "myjob2", "controller-uid": string(theUID)}, }, Spec: validPodSpec, }}, }, }, "labels exist": { job: &batch.Job{ ObjectMeta: validObjectMeta, Spec: batch.JobSpec{ Selector: nil, Template: api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"a": "b", "job-name": "myjob2", "controller-uid": string(theUID)}, }, Spec: validPodSpec, }}, }, wantJob: &batch.Job{ ObjectMeta: validObjectMeta, Spec: batch.JobSpec{ Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"controller-uid": string(theUID)}}, Template: api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"a": "b", "job-name": "myjob2", "controller-uid": string(theUID)}, }, Spec: validPodSpec, }}, }, }, "manual selector; do not generate labels": { job: &batch.Job{ ObjectMeta: validObjectMeta, Spec: batch.JobSpec{ Selector: validSelector, Template: api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: validSelector.MatchLabels, }, Spec: validPodSpec, }, Completions:    pointer.Int32Ptr(2), ManualSelector: pointer.BoolPtr(true), }, }, wantJob: &batch.Job{ ObjectMeta: validObjectMeta, Spec: batch.JobSpec{ Selector: validSelector, Template: api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: validSelector.MatchLabels, }, Spec: validPodSpec, }, Completions:    pointer.Int32Ptr(2), ManualSelector: pointer.BoolPtr(true), }, }, }, "valid job with extended configuration": { job: &batch.Job{ ObjectMeta: validObjectMeta, Spec: batch.JobSpec{ Selector: nil, Template: api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: validSelector.MatchLabels, }, Spec: validPodSpec, }, Completions:             pointer.Int32Ptr(2), Suspend:                 pointer.BoolPtr(true), TTLSecondsAfterFinished: pointer.Int32Ptr(0), CompletionMode:          completionModePtr(batch.IndexedCompletion), }, }, wantJob: &batch.Job{ ObjectMeta: validObjectMeta, Spec: batch.JobSpec{ Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"controller-uid": string(theUID)}}, Template: api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"a": "b", "job-name": "myjob2", "controller-uid": string(theUID)}, }, Spec: validPodSpec, }, Completions:             pointer.Int32Ptr(2), Suspend:                 pointer.BoolPtr(true), TTLSecondsAfterFinished: pointer.Int32Ptr(0), CompletionMode:          completionModePtr(batch.IndexedCompletion), }, }, }, "fail validation due to invalid volume spec": { job: &batch.Job{ ObjectMeta: validObjectMeta, Spec: batch.JobSpec{ Selector: nil, Template: api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: validSelector.MatchLabels, }, Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyOnFailure, DNSPolicy:     api.DNSClusterFirst, Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, Volumes:       []api.Volume{{Name: "volume-name"}}, }, }, }, }, wantJob: &batch.Job{ ObjectMeta: validObjectMeta, Spec: batch.JobSpec{ Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"controller-uid": string(theUID)}}, Template: api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"a": "b", "job-name": "myjob2", "controller-uid": string(theUID)}, }, Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyOnFailure, DNSPolicy:     api.DNSClusterFirst, Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, Volumes:       []api.Volume{{Name: "volume-name"}}, }, }, }, }, wantWarningCount: 1, }, } for name, tc := range testcases { t.Run(name, func(t *testing.T) { errs := Strategy.Validate(ctx, tc.job) if len(errs) != int(tc.wantWarningCount) { t.Errorf("want warnings %d but got %d", tc.wantWarningCount, len(errs)) } if diff := cmp.Diff(tc.wantJob, tc.job); diff != "" { t.Errorf("Unexpected job (-want,+got):n%s", diff) } }) } } func TestStrategy_ResetFields(t *testing.T) { resetFields := Strategy.GetResetFields() if len(resetFields) != 1 { t.Error("ResetFields should have 1 element") } } func TestJobStatusStrategy_ResetFields(t *testing.T) { resetFields := StatusStrategy.GetResetFields() if len(resetFields) != 1 { t.Error("ResetFields should have 1 element") } } func TestStatusStrategy_PrepareForUpdate(t *testing.T) { ctx := genericapirequest.NewDefaultContext() validSelector := &metav1.LabelSelector{ MatchLabels: map[string]string{"a": "b"}, } validPodTemplateSpec := api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: validSelector.MatchLabels, }, Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyOnFailure, DNSPolicy:     api.DNSClusterFirst, Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, }, } validObjectMeta := metav1.ObjectMeta{ Name:            "myjob", Namespace:       metav1.NamespaceDefault, ResourceVersion: "10", } cases := map[string]struct { job     *batch.Job newJob  *batch.Job wantJob *batch.Job }{ "job must allow status updates": { job: &batch.Job{ ObjectMeta: validObjectMeta, Spec: batch.JobSpec{ Selector:    validSelector, Template:    validPodTemplateSpec, Parallelism: pointer.Int32(4), }, Status: batch.JobStatus{ Active: 11, }, }, newJob: &batch.Job{ ObjectMeta: validObjectMeta, Spec: batch.JobSpec{ Selector:    validSelector, Template:    validPodTemplateSpec, Parallelism: pointer.Int32(4), }, Status: batch.JobStatus{ Active: 12, }, }, wantJob: &batch.Job{ ObjectMeta: validObjectMeta, Spec: batch.JobSpec{ Selector:    validSelector, Template:    validPodTemplateSpec, Parallelism: pointer.Int32(4), }, Status: batch.JobStatus{ Active: 12, }, }, }, "parallelism changes not allowed": { job: &batch.Job{ ObjectMeta: validObjectMeta, Spec: batch.JobSpec{ Selector:    validSelector, Template:    validPodTemplateSpec, Parallelism: pointer.Int32(3), }, }, newJob: &batch.Job{ ObjectMeta: validObjectMeta, Spec: batch.JobSpec{ Selector:    validSelector, Template:    validPodTemplateSpec, Parallelism: pointer.Int32(4), }, }, wantJob: &batch.Job{ ObjectMeta: validObjectMeta, Spec: batch.JobSpec{ Selector:    validSelector, Template:    validPodTemplateSpec, Parallelism: pointer.Int32(3), }, }, }, } for name, tc := range cases { t.Run(name, func(t *testing.T) { StatusStrategy.PrepareForUpdate(ctx, tc.newJob, tc.job) if diff := cmp.Diff(tc.wantJob, tc.newJob); diff != "" { t.Errorf("Unexpected job (-want,+got):n%s", diff) } }) } } func TestStatusStrategy_ValidateUpdate(t *testing.T) { ctx := genericapirequest.NewDefaultContext() validSelector := &metav1.LabelSelector{ MatchLabels: map[string]string{"a": "b"}, } validPodTemplateSpec := api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: validSelector.MatchLabels, }, Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyOnFailure, DNSPolicy:     api.DNSClusterFirst, Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, }, } cases := map[string]struct { job     *batch.Job newJob  *batch.Job wantJob *batch.Job }{ "incoming resource version on update should not be mutated": { job: &batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name:            "myjob", Namespace:       metav1.NamespaceDefault, ResourceVersion: "10", }, Spec: batch.JobSpec{ Selector:    validSelector, Template:    validPodTemplateSpec, Parallelism: pointer.Int32(4), }, }, newJob: &batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name:            "myjob", Namespace:       metav1.NamespaceDefault, ResourceVersion: "9", }, Spec: batch.JobSpec{ Selector:    validSelector, Template:    validPodTemplateSpec, Parallelism: pointer.Int32(4), }, }, wantJob: &batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name:            "myjob", Namespace:       metav1.NamespaceDefault, ResourceVersion: "9", }, Spec: batch.JobSpec{ Selector:    validSelector, Template:    validPodTemplateSpec, Parallelism: pointer.Int32(4), }, }, }, } for name, tc := range cases { t.Run(name, func(t *testing.T) { errs := StatusStrategy.ValidateUpdate(ctx, tc.newJob, tc.job) if len(errs) != 0 { t.Errorf("Unexpected error %v", errs) } if diff := cmp.Diff(tc.wantJob, tc.newJob); diff != "" { t.Errorf("Unexpected job (-want,+got):n%s", diff) } }) } } func TestJobStrategy_GetAttrs(t *testing.T) { validSelector := &metav1.LabelSelector{ MatchLabels: map[string]string{"a": "b"}, } validPodTemplateSpec := api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: validSelector.MatchLabels, }, Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyOnFailure, DNSPolicy:     api.DNSClusterFirst, Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, }, </ins> } <del> errs := StatusStrategy.ValidateUpdate(ctx, newJob, oldJob) if len(errs) != 0 { t.Errorf("Unexpected error %v", errs) </del> <ins> cases := map[string]struct { job          *batch.Job wantErr      string nonJobObject *api.Pod }{ "valid job with no labels": { job: &batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name:            "myjob", Namespace:       metav1.NamespaceDefault, ResourceVersion: "0", }, Spec: batch.JobSpec{ Selector:       validSelector, Template:       validPodTemplateSpec, ManualSelector: pointer.BoolPtr(true), Parallelism:    pointer.Int32Ptr(1), }, }, }, "valid job with a label": { job: &batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name:            "myjob", Namespace:       metav1.NamespaceDefault, ResourceVersion: "0", Labels:          map[string]string{"a": "b"}, }, Spec: batch.JobSpec{ Selector:       validSelector, Template:       validPodTemplateSpec, ManualSelector: pointer.BoolPtr(true), Parallelism:    pointer.Int32Ptr(1), }, }, }, "pod instead": { job:          nil, nonJobObject: &api.Pod{}, wantErr:      "given object is not a job.", }, </ins> } <del> if newJob.ResourceVersion != "9" { t.Errorf("Incoming resource version on update should not be mutated") </del> <ins> for name, tc := range cases { t.Run(name, func(t *testing.T) { if tc.job == nil { _, _, err := GetAttrs(tc.nonJobObject) if diff := cmp.Diff(tc.wantErr, err.Error()); diff != "" { t.Errorf("Unexpected errors (-want,+got):n%s", diff) } } else { gotLabels, _, err := GetAttrs(tc.job) if err != nil { t.Errorf("Error %s supposed to be nil", err.Error()) } if diff := cmp.Diff(labels.Set(tc.job.ObjectMeta.Labels), gotLabels); diff != "" { t.Errorf("Unexpected attrs (-want,+got):n%s", diff) } } }) </ins> } } <del> func TestSelectableFieldLabelConversions(t *testing.T) { </del> <ins> func TestJobToSelectiableFields(t *testing.T) { </ins> apitesting.TestSelectableFieldLabelConversionsOfKind(t, "batch/v1", "Job",


"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" "go.opentelemetry.io/otel/trace" <ins> "k8s.io/apiserver/pkg/endpoints/request" </ins> tracing "k8s.io/component-base/tracing" )


otelhttp.WithPropagators(tracing.Propagators()), otelhttp.WithPublicEndpoint(), otelhttp.WithTracerProvider(tp), <ins> otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string { ctx := r.Context() info, exist := request.RequestInfoFrom(ctx) if !exist || !info.IsResourceRequest { return r.Method } return getSpanNameFromRequestInfo(info, r) }), </ins> } wrappedHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Add the http.target attribute to the otelhttp span


// See https://github.com/open-telemetry/opentelemetry-go/tree/main/example/passthrough return otelhttp.NewHandler(wrappedHandler, "KubernetesAPI", opts...) } <ins> func getSpanNameFromRequestInfo(info *request.RequestInfo, r *http.Request) string { spanName := "/" + info.APIPrefix if info.APIGroup != "" { spanName += "/" + info.APIGroup } spanName += "/" + info.APIVersion if info.Namespace != "" { spanName += "/namespaces/{:namespace}" } spanName += "/" + info.Resource if info.Name != "" { spanName += "/" + "{:name}" } if info.Subresource != "" { spanName += "/" + info.Subresource } return r.Method + " " + spanName } </ins>


}, expectedTrace: []*spanExpectation{ { <del> name: "KubernetesAPI", </del> <ins> name: "POST /api/v1/nodes", </ins> attributes: map[string]func(*commonv1.AnyValue) bool{ "http.user_agent": func(v *commonv1.AnyValue) bool { return strings.HasPrefix(v.GetStringValue(), "tracing.test")


}, expectedTrace: []*spanExpectation{ { <del> name: "KubernetesAPI", </del> <ins> name: "GET /api/v1/nodes/{:name}", </ins> attributes: map[string]func(*commonv1.AnyValue) bool{ "http.user_agent": func(v *commonv1.AnyValue) bool { return strings.HasPrefix(v.GetStringValue(), "tracing.test")


}, expectedTrace: []*spanExpectation{ { <del> name: "KubernetesAPI", </del> <ins> name: "GET /api/v1/nodes", </ins> attributes: map[string]func(*commonv1.AnyValue) bool{ "http.user_agent": func(v *commonv1.AnyValue) bool { return strings.HasPrefix(v.GetStringValue(), "tracing.test")


}, expectedTrace: []*spanExpectation{ { <del> name: "KubernetesAPI", </del> <ins> name: "PUT /api/v1/nodes/{:name}", </ins> attributes: map[string]func(*commonv1.AnyValue) bool{ "http.user_agent": func(v *commonv1.AnyValue) bool { return strings.HasPrefix(v.GetStringValue(), "tracing.test")


}, expectedTrace: []*spanExpectation{ { <del> name: "KubernetesAPI", </del> <ins> name: "PATCH /api/v1/nodes/{:name}", </ins> attributes: map[string]func(*commonv1.AnyValue) bool{ "http.user_agent": func(v *commonv1.AnyValue) bool { return strings.HasPrefix(v.GetStringValue(), "tracing.test")


}, expectedTrace: []*spanExpectation{ { <del> name: "KubernetesAPI", </del> <ins> name: "DELETE /api/v1/nodes/{:name}", </ins> attributes: map[string]func(*commonv1.AnyValue) bool{ "http.user_agent": func(v *commonv1.AnyValue) bool { return strings.HasPrefix(v.GetStringValue(), "tracing.test")


"path/filepath" "strings" <del> "k8s.io/api/core/v1" </del> <ins> v1 "k8s.io/api/core/v1" </ins> utilerrors "k8s.io/apimachinery/pkg/util/errors" utilvalidation "k8s.io/apimachinery/pkg/util/validation" utilfeature "k8s.io/apiserver/pkg/util/feature"


searches = []string{} for _, s := range fields[1:] { if s != "." { <del> s = strings.TrimSuffix(s, ".") </del> <ins> searches = append(searches, strings.TrimSuffix(s, ".")) </ins> } <del> searches = append(searches, s) </del> } } if fields[0] == "options" {


{"nameserver t 1.2.3.4", []string{"1.2.3.4"}, []string{}, []string{}, false}, {"nameserver 1.2.3.4nnameserver 5.6.7.8", []string{"1.2.3.4", "5.6.7.8"}, []string{}, []string{}, false}, {"nameserver 1.2.3.4 #comment", []string{"1.2.3.4"}, []string{}, []string{}, false}, <del> {"search ", []string{}, []string{}, []string{}, false}, // search empty {"search .", []string{}, []string{"."}, []string{}, false}, </del> <ins> {"search ", []string{}, []string{}, []string{}, false},  // search empty {"search .", []string{}, []string{}, []string{}, false}, // ignore lone dot {"search . foo", []string{}, []string{"foo"}, []string{}, false}, {"search foo .", []string{}, []string{"foo"}, []string{}, false}, {"search foo .  bar", []string{}, []string{"foo", "bar"}, []string{}, false}, </ins> {"search foo", []string{}, []string{"foo"}, []string{}, false}, {"search foo bar", []string{}, []string{"foo", "bar"}, []string{}, false}, {"search foo. bar", []string{}, []string{"foo", "bar"}, []string{}, false},


utilruntime.Must(flowcontrolv1beta1.AddToScheme(scheme)) utilruntime.Must(flowcontrolv1beta2.AddToScheme(scheme)) utilruntime.Must(flowcontrolv1beta3.AddToScheme(scheme)) <del> // TODO(#112512): This controls serialization order, for 1.26, we can //  set the serialization version to v1beta2 because vN-1 understands that //  level. In 1.27, we should set the serialization version to v1beta3. utilruntime.Must(scheme.SetVersionPriority(flowcontrolv1beta2.SchemeGroupVersion, flowcontrolv1beta3.SchemeGroupVersion, </del> <ins> utilruntime.Must(scheme.SetVersionPriority(flowcontrolv1beta3.SchemeGroupVersion, flowcontrolv1beta2.SchemeGroupVersion, </ins> flowcontrolv1beta1.SchemeGroupVersion, flowcontrolv1alpha1.SchemeGroupVersion)) }


"admissionregistration.k8s.io/v1/mutatingwebhookconfigurations":    "Sqi0GUgDaX0=", "admissionregistration.k8s.io/v1/validatingwebhookconfigurations":  "B0wHjQmsGNk=", "events.k8s.io/v1/events":                                          "r2yiGXH7wu8=", <del> "flowcontrol.apiserver.k8s.io/v1beta3/flowschemas":                 "G+8IkrqFuJw=", "flowcontrol.apiserver.k8s.io/v1beta3/prioritylevelconfigurations": "wltM4WMeeXs=", "flowcontrol.apiserver.k8s.io/v1beta2/flowschemas":                 "G+8IkrqFuJw=", "flowcontrol.apiserver.k8s.io/v1beta2/prioritylevelconfigurations": "wltM4WMeeXs=", </del> <ins> "flowcontrol.apiserver.k8s.io/v1beta3/flowschemas":                 "9NnFrw7ZEmA=", "flowcontrol.apiserver.k8s.io/v1beta3/prioritylevelconfigurations": "+CwSrEWTDhc=", "flowcontrol.apiserver.k8s.io/v1beta2/flowschemas":                 "9NnFrw7ZEmA=", "flowcontrol.apiserver.k8s.io/v1beta2/prioritylevelconfigurations": "+CwSrEWTDhc=", </ins> }


gvr("flowcontrol.apiserver.k8s.io", "v1alpha1", "flowschemas"): { Stub:             `{"metadata": {"name": "va1"}, "spec": {"priorityLevelConfiguration": {"name": "name1"}}}`, ExpectedEtcdPath: "/registry/flowschemas/va1", <del> ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1beta2", "FlowSchema"), </del> <ins> ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1beta3", "FlowSchema"), </ins> }, // --


gvr("flowcontrol.apiserver.k8s.io", "v1alpha1", "prioritylevelconfigurations"): { Stub:             `{"metadata": {"name": "conf1"}, "spec": {"type": "Limited", "limited": {"assuredConcurrencyShares":3, "limitResponse": {"type": "Reject"}}}}`, ExpectedEtcdPath: "/registry/prioritylevelconfigurations/conf1", <del> ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1beta2", "PriorityLevelConfiguration"), </del> <ins> ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1beta3", "PriorityLevelConfiguration"), </ins> }, // --


gvr("flowcontrol.apiserver.k8s.io", "v1beta1", "flowschemas"): { Stub:             `{"metadata": {"name": "va2"}, "spec": {"priorityLevelConfiguration": {"name": "name1"}}}`, ExpectedEtcdPath: "/registry/flowschemas/va2", <del> ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1beta2", "FlowSchema"), </del> <ins> ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1beta3", "FlowSchema"), </ins> }, // --


gvr("flowcontrol.apiserver.k8s.io", "v1beta1", "prioritylevelconfigurations"): { Stub:             `{"metadata": {"name": "conf2"}, "spec": {"type": "Limited", "limited": {"assuredConcurrencyShares":3, "limitResponse": {"type": "Reject"}}}}`, ExpectedEtcdPath: "/registry/prioritylevelconfigurations/conf2", <del> ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1beta2", "PriorityLevelConfiguration"), </del> <ins> ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1beta3", "PriorityLevelConfiguration"), </ins> }, // --


gvr("flowcontrol.apiserver.k8s.io", "v1beta2", "flowschemas"): { Stub:             `{"metadata": {"name": "fs-1"}, "spec": {"priorityLevelConfiguration": {"name": "name1"}}}`, ExpectedEtcdPath: "/registry/flowschemas/fs-1", <ins> ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1beta3", "FlowSchema"), </ins> }, // --


gvr("flowcontrol.apiserver.k8s.io", "v1beta2", "prioritylevelconfigurations"): { Stub:             `{"metadata": {"name": "conf3"}, "spec": {"type": "Limited", "limited": {"assuredConcurrencyShares":3, "limitResponse": {"type": "Reject"}}}}`, ExpectedEtcdPath: "/registry/prioritylevelconfigurations/conf3", <ins> ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1beta3", "PriorityLevelConfiguration"), </ins> }, // --


gvr("flowcontrol.apiserver.k8s.io", "v1beta3", "flowschemas"): { Stub:             `{"metadata": {"name": "fs-2"}, "spec": {"priorityLevelConfiguration": {"name": "name1"}}}`, ExpectedEtcdPath: "/registry/flowschemas/fs-2", <del> ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1beta2", "FlowSchema"), </del> }, // --


gvr("flowcontrol.apiserver.k8s.io", "v1beta3", "prioritylevelconfigurations"): { Stub:             `{"metadata": {"name": "conf4"}, "spec": {"type": "Limited", "limited": {"nominalConcurrencyShares":3, "limitResponse": {"type": "Reject"}}}}`, ExpectedEtcdPath: "/registry/prioritylevelconfigurations/conf4", <del> ExpectedGVK:      gvkP("flowcontrol.apiserver.k8s.io", "v1beta2", "PriorityLevelConfiguration"), </del> }, // --


oldModifyResponse := proxy.ModifyResponse proxy.ModifyResponse = func(response *http.Response) error { code := response.StatusCode <del> if code >= 300 && code <= 399 { </del> <ins> if code >= 300 && code <= 399 && len(response.Header.Get("Location")) > 0 { </ins> // close the original response response.Body.Close() msg := "the backend attempted to redirect this request, which is not permitted"


name                      string rejectForwardingRedirects bool serverStatusCode          int <ins> redirect                  string </ins> expectStatusCode          int expectBody                []byte }{


name:                      "reject redirection enabled in proxy, backend server sending 301 response", rejectForwardingRedirects: true, serverStatusCode:          301, <ins> redirect:                  "/", </ins> expectStatusCode:          502, expectBody:                []byte(`the backend attempted to redirect this request, which is not permitted`), }, { <ins> name:                      "reject redirection enabled in proxy, backend server sending 304 response with a location header", rejectForwardingRedirects: true, serverStatusCode:          304, redirect:                  "/", expectStatusCode:          502, expectBody:                []byte(`the backend attempted to redirect this request, which is not permitted`), }, { name:                      "reject redirection enabled in proxy, backend server sending 304 response with no location header", rejectForwardingRedirects: true, serverStatusCode:          304, expectStatusCode:          304, expectBody:                []byte{}, // client doesn't read the body for 304 responses }, { </ins> name:                      "reject redirection disabled in proxy, backend server sending 200 response", rejectForwardingRedirects: false, serverStatusCode:          200,


name:                      "reject redirection disabled in proxy, backend server sending 301 response", rejectForwardingRedirects: false, serverStatusCode:          301, <ins> redirect:                  "/", </ins> expectStatusCode:          301, expectBody:                originalBody, },


t.Run(tc.name, func(t *testing.T) { // Set up a backend server backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { <ins> if tc.redirect != "" { w.Header().Set("Location", tc.redirect) } </ins> w.WriteHeader(tc.serverStatusCode) w.Write(originalBody) }))


kl.statusManager.SetPodStatus(pod, v1.PodStatus{ Phase:   v1.PodFailed, Reason:  reason, <del> Message: "Pod " + message}) </del> <ins> Message: "Pod was rejected: " + message}) </ins> } // canAdmitPod determines if a pod can be admitted, and gives a reason if it


// perform any validation. // According to our measurements this is significantly faster // in codepaths that matter at high scale. <ins> // Note: this method copies the Set; if the Set is immutable, consider wrapping it with ValidatedSetSelector // instead, which does not copy. </ins> func (ls Set) AsSelectorPreValidated() Selector { return SelectorFromValidatedSet(ls) }


// SelectorFromValidatedSet returns a Selector which will match exactly the given Set. // A nil and empty Sets are considered equivalent to Everything(). // It assumes that Set is already validated and doesn't do any validation. <ins> // Note: this method copies the Set; if the Set is immutable, consider wrapping it with ValidatedSetSelector // instead, which does not copy. </ins> func SelectorFromValidatedSet(ls Set) Selector { if ls == nil || len(ls) == 0 { return internalSelector{}


func ParseToRequirements(selector string, opts ...field.PathOption) ([]Requirement, error) { return parse(selector, field.ToPath(opts...)) } <ins> // ValidatedSetSelector wraps a Set, allowing it to implement the Selector interface. Unlike // Set.AsSelectorPreValidated (which copies the input Set), this type simply wraps the underlying // Set. As a result, it is substantially more efficient. A nil and empty Sets are considered // equivalent to Everything(). // // Callers MUST ensure the underlying Set is not mutated, and that it is already validated. If these // constraints are not met, Set.AsValidatedSelector should be preferred // // None of the Selector methods mutate the underlying Set, but Add() and Requirements() convert to // the less optimized version. type ValidatedSetSelector Set func (s ValidatedSetSelector) Matches(labels Labels) bool { for k, v := range s { if !labels.Has(k) || v != labels.Get(k) { return false } } return true } func (s ValidatedSetSelector) Empty() bool { return len(s) == 0 } func (s ValidatedSetSelector) String() string { keys := make([]string, 0, len(s)) for k := range s { keys = append(keys, k) } // Ensure deterministic output sort.Strings(keys) b := strings.Builder{} for i, key := range keys { v := s[key] b.Grow(len(key) + 2 + len(v)) if i != 0 { b.WriteString(",") } b.WriteString(key) b.WriteString("=") b.WriteString(v) } return b.String() } func (s ValidatedSetSelector) Add(r ...Requirement) Selector { return s.toFullSelector().Add(r...) } func (s ValidatedSetSelector) Requirements() (requirements Requirements, selectable bool) { return s.toFullSelector().Requirements() } func (s ValidatedSetSelector) DeepCopySelector() Selector { res := make(ValidatedSetSelector, len(s)) for k, v := range s { res[k] = v } return res } func (s ValidatedSetSelector) RequiresExactMatch(label string) (value string, found bool) { v, f := s[label] return v, f } func (s ValidatedSetSelector) toFullSelector() Selector { return SelectorFromValidatedSet(Set(s)) } var _ Selector = ValidatedSetSelector{} </ins>


"foo": "foo", "bar": "bar", } <ins> matchee := Set(map[string]string{ "foo":   "foo", "bar":   "bar", "extra": "label", }) </ins> for i := 0; i < b.N; i++ { <del> if SelectorFromValidatedSet(set).Empty() { </del> <ins> s := SelectorFromValidatedSet(set) if s.Empty() { </ins> b.Errorf("Unexpected selector") } <ins> if !s.Matches(matchee) { b.Errorf("Unexpected match") } } } func BenchmarkSetSelector(b *testing.B) { set := map[string]string{ "foo": "foo", "bar": "bar", } matchee := Set(map[string]string{ "foo":   "foo", "bar":   "bar", "extra": "label", }) for i := 0; i < b.N; i++ { s := ValidatedSetSelector(set) if s.Empty() { b.Errorf("Unexpected selector") } if !s.Matches(matchee) { b.Errorf("Unexpected match") } } } func TestSetSelectorString(t *testing.T) { cases := []struct { set Set out string }{ { Set{}, "", }, { Set{"app": "foo"}, "app=foo", }, { Set{"app": "foo", "a": "b"}, "a=b,app=foo", }, } for _, tt := range cases { t.Run(tt.out, func(t *testing.T) { if got := ValidatedSetSelector(tt.set).String(); tt.out != got { t.Fatalf("expected %v, got %v", tt.out, got) } }) </ins> } }


"fmt" "strings" <del> "k8s.io/api/core/v1" </del> <ins> v1 "k8s.io/api/core/v1" </ins> "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation/field"


if exists { // For GPUs, not only requests can't exceed limits, they also can't be lower, i.e. must be equal. if quantity.Cmp(limitQuantity) != 0 && !v1helper.IsOvercommitAllowed(resourceName) { <del> allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s limit", resourceName))) </del> <ins> allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s limit of %s", resourceName, limitQuantity.String()))) </ins> } else if quantity.Cmp(limitQuantity) > 0 { <del> allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be less than or equal to %s limit", resourceName))) </del> <ins> allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be less than or equal to %s limit of %s", resourceName, limitQuantity.String()))) </ins> } } }


package validation import ( <ins> "strings" </ins> "testing" <del> "k8s.io/api/core/v1" </del> <ins> v1 "k8s.io/api/core/v1" </ins> "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets"


} errorCase := []struct { <del> name         string requirements v1.ResourceRequirements </del> <ins> name                  string requirements          v1.ResourceRequirements skipLimitValueCheck   bool skipRequestValueCheck bool </ins> }{ { name: "Resources with Requests Larger Than Limits",


v1.ResourceName("my.org"): resource.MustParse("10m"), }, }, <ins> skipRequestValueCheck: true, </ins> }, { name: "Invalid Resources with Limits",


v1.ResourceName("my.org"): resource.MustParse("9m"), }, }, <ins> skipLimitValueCheck: true, </ins> }, } for _, tc := range errorCase { t.Run(tc.name, func(t *testing.T) { <del> if errs := ValidateResourceRequirements(&tc.requirements, field.NewPath("resources")); len(errs) == 0 { </del> <ins> errs := ValidateResourceRequirements(&tc.requirements, field.NewPath("resources")) if len(errs) == 0 { </ins> t.Errorf("expected error") } <ins> validateNamesAndValuesInDescription(t, tc.requirements.Limits, errs, tc.skipLimitValueCheck, "limit") validateNamesAndValuesInDescription(t, tc.requirements.Requests, errs, tc.skipRequestValueCheck, "request") </ins> }) } } <ins> func validateNamesAndValuesInDescription(t *testing.T, r v1.ResourceList, errs field.ErrorList, skipValueTest bool, rl string) { for name, value := range r { containsName := false containsValue := false for _, e := range errs { if strings.Contains(e.Error(), name.String()) { containsName = true } if strings.Contains(e.Error(), value.String()) { containsValue = true } } if !containsName { t.Errorf("error must contain %s name", rl) } if !containsValue && !skipValueTest { t.Errorf("error must contain %s value", rl) } } } </ins> func TestValidateContainerResourceName(t *testing.T) { successCase := []struct { name         string


if exists { // For non overcommitable resources, not only requests can't exceed limits, they also can't be lower, i.e. must be equal. if quantity.Cmp(limitQuantity) != 0 && !helper.IsOvercommitAllowed(resourceName) { <del> allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s limit", resourceName))) </del> <ins> allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s limit of %s", resourceName, limitQuantity.String()))) </ins> } else if quantity.Cmp(limitQuantity) > 0 { <del> allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be less than or equal to %s limit", resourceName))) </del> <ins> allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be less than or equal to %s limit of %s", resourceName, limitQuantity.String()))) </ins> } } else if !helper.IsOvercommitAllowed(resourceName) { allErrs = append(allErrs, field.Required(limPath, "Limit must be set for non overcommitable resources"))


"k8s.io/apimachinery/pkg/util/uuid" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apiserver/pkg/server/healthz" <ins> utilfeature "k8s.io/apiserver/pkg/util/feature" </ins> cacheddiscovery "k8s.io/client-go/discovery/cached" "k8s.io/client-go/informers" v1core "k8s.io/client-go/kubernetes/typed/core/v1"


cliflag "k8s.io/component-base/cli/flag" "k8s.io/component-base/cli/globalflag" "k8s.io/component-base/configz" <ins> "k8s.io/component-base/metrics/features" </ins> controllersmetrics "k8s.io/component-base/metrics/prometheus/controllers" <ins> "k8s.io/component-base/metrics/prometheus/slis" </ins> "k8s.io/component-base/term" "k8s.io/component-base/version" "k8s.io/component-base/version/verflag"


"k8s.io/klog/v2" ) <ins> func init() { utilruntime.Must(features.AddFeatureGates(utilfeature.DefaultMutableFeatureGate)) } </ins> const ( // ControllerStartJitter is the jitter value used when starting controller managers. ControllerStartJitter = 1.0


// Start the controller manager HTTP server if c.SecureServing != nil { unsecuredMux := genericcontrollermanager.NewBaseHandler(&c.ComponentConfig.Generic.Debugging, healthzHandler) <ins> if utilfeature.DefaultFeatureGate.Enabled(features.ComponentSLIs) { slis.SLIMetricsWithReset{}.Install(unsecuredMux) } </ins> handler := genericcontrollermanager.BuildHandlerChain(unsecuredMux, &c.Authorization, &c.Authentication) // TODO: handle stoppedCh and listenerStoppedCh returned by c.SecureServing.Serve if _, _, err := c.SecureServing.Serve(handler, 0, stopCh); err != nil {


// with gingko.BeforeEach/AfterEach/DeferCleanup. // // When a test runs, functions will be invoked in this order: <ins> // - BeforeEaches defined by tests before f.NewDefaultFramework //   in the order in which they were defined (first-in-first-out) </ins> // - f.BeforeEach <del> // - all BeforeEaches in the order in which they were defined (first-in-first-out) </del> <ins> // - BeforeEaches defined by tests after f.NewDefaultFramework </ins> // - It callback // - all AfterEaches in the order in which they were defined // - all DeferCleanups with the order reversed (first-in-last-out) // - f.AfterEach <ins> // // Because a test might skip test execution in a BeforeEach that runs // before f.BeforeEach, AfterEach callbacks that depend on the // framework instance must check whether it was initialized. They can // do that by checking f.ClientSet for nil. DeferCleanup callbacks // don't need to do this because they get defined when the test // runs. </ins> NewFrameworkExtensions []func(f *Framework) )


} } <del> // Paranoia-- prevent reuse! </del> <ins> // Unsetting this is relevant for a following test that uses // the same instance because it might not reach f.BeforeEach // when some other BeforeEach skips the test first. </ins> f.Namespace = nil f.clientConfig = nil f.ClientSet = nil


framework.NewFrameworkExtensions = append(framework.NewFrameworkExtensions, func(f *framework.Framework) { ginkgo.AfterEach(func() { <ins> if f.ClientSet == nil { // Test didn't reach f.BeforeEach, most // likely because the test got // skipped. Nothing to check... return } </ins> e2enode.AllNodesReady(f.ClientSet, 3*time.Minute) }) },


WhenScaled:  apps.DeletePersistentVolumeClaimRetentionPolicyType, WhenDeleted: apps.DeletePersistentVolumeClaimRetentionPolicyType, }, <ins> // tests the case when no policy is set. nil, </ins> } { subtestName := pvcDeletePolicyString(policy) + "/StatefulSetAutoDeletePVCEnabled" if testName != "" {


} func pvcDeletePolicyString(policy *apps.StatefulSetPersistentVolumeClaimRetentionPolicy) string { <ins> if policy == nil { return "nullPolicy" } </ins> const retain = apps.RetainPersistentVolumeClaimRetentionPolicyType const delete = apps.DeletePersistentVolumeClaimRetentionPolicyType switch {


// claimOwnerMatchesSetAndPod returns false if the ownerRefs of the claim are not set consistently with the // PVC deletion policy for the StatefulSet. func claimOwnerMatchesSetAndPod(claim *v1.PersistentVolumeClaim, set *apps.StatefulSet, pod *v1.Pod) bool { <del> policy := set.Spec.PersistentVolumeClaimRetentionPolicy </del> <ins> policy := getPersistentVolumeClaimRetentionPolicy(set) </ins> const retain = apps.RetainPersistentVolumeClaimRetentionPolicyType const delete = apps.DeletePersistentVolumeClaimRetentionPolicyType switch {


updateMeta(&podMeta, "Pod") setMeta := set.TypeMeta updateMeta(&setMeta, "StatefulSet") <del> policy := set.Spec.PersistentVolumeClaimRetentionPolicy </del> <ins> policy := getPersistentVolumeClaimRetentionPolicy(set) </ins> const retain = apps.RetainPersistentVolumeClaimRetentionPolicyType const delete = apps.DeletePersistentVolumeClaimRetentionPolicyType switch {


/* Release : v1.23 Testname: Pod Lifecycle, poststart https hook <del> Description: When a post-start handler is specified in the container lifecycle using a 'HttpGet' action, then the handler MUST be invoked before the container is terminated. A server pod is created that will serve https requests, create a second pod with a container lifecycle specifying a post-start that invokes the server pod to validate that the post-start is executed. </del> <ins> Description: When a post-start handler is specified in the container lifecycle using a 'HttpGet' action, then the handler MUST be invoked before the container is terminated. A server pod is created that will serve https requests, create a second pod on the same node with a container lifecycle specifying a post-start that invokes the server pod to validate that the post-start is executed. </ins> */ ginkgo.It("should execute poststart https hook properly [MinimumKubeletVersion:1.23] [NodeConformance]", func() { lifecycle := &v1.Lifecycle{


}, } podWithHook := getPodWithHook("pod-with-poststart-https-hook", imageutils.GetPauseImageName(), lifecycle) <ins> // make sure we spawn the test pod on the same node as the webserver. nodeSelection := e2epod.NodeSelection{} e2epod.SetAffinity(&nodeSelection, targetNode) e2epod.SetNodeSelection(&podWithHook.Spec, nodeSelection) </ins> testPodWithHook(podWithHook) }) /*


/* Release : v1.23 Testname: Pod Lifecycle, prestop https hook <del> Description: When a pre-stop handler is specified in the container lifecycle using a 'HttpGet' action, then the handler MUST be invoked before the container is terminated. A server pod is created that will serve https requests, create a second pod with a container lifecycle specifying a pre-stop that invokes the server pod to validate that the pre-stop is executed. </del> <ins> Description: When a pre-stop handler is specified in the container lifecycle using a 'HttpGet' action, then the handler MUST be invoked before the container is terminated. A server pod is created that will serve https requests, create a second pod on the same node with a container lifecycle specifying a pre-stop that invokes the server pod to validate that the pre-stop is executed. </ins> */ ginkgo.It("should execute prestop https hook properly [MinimumKubeletVersion:1.23] [NodeConformance]", func() { lifecycle := &v1.Lifecycle{


}, } podWithHook := getPodWithHook("pod-with-prestop-https-hook", imageutils.GetPauseImageName(), lifecycle) <ins> // make sure we spawn the test pod on the same node as the webserver. nodeSelection := e2epod.NodeSelection{} e2epod.SetAffinity(&nodeSelection, targetNode) e2epod.SetNodeSelection(&podWithHook.Spec, nodeSelection) </ins> testPodWithHook(podWithHook) }) })


"fmt" "unsafe" <del> "github.com/pkg/errors" </del> "golang.org/x/sys/windows" "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/windows/service" )


func createWindowsJobObject(pc uint32) (windows.Handle, error) { job, err := windows.CreateJobObject(nil, nil) if err != nil { <del> return windows.InvalidHandle, errors.Wrap(err, "windows.CreateJobObject failed") </del> <ins> return windows.InvalidHandle, fmt.Errorf("windows.CreateJobObject failed: %w", err) </ins> } limitInfo := windows.JOBOBJECT_BASIC_LIMIT_INFORMATION{ LimitFlags:    windows.JOB_OBJECT_LIMIT_PRIORITY_CLASS,


windows.JobObjectBasicLimitInformation, uintptr(unsafe.Pointer(&limitInfo)), uint32(unsafe.Sizeof(limitInfo))); err != nil { <del> return windows.InvalidHandle, errors.Wrap(err, "windows.SetInformationJobObject failed") </del> <ins> return windows.InvalidHandle, fmt.Errorf("windows.SetInformationJobObject failed: %w", err) </ins> } return job, nil }


return err } if err := windows.AssignProcessToJobObject(job, windows.CurrentProcess()); err != nil { <del> return errors.Wrap(err, "windows.AssignProcessToJobObject failed") </del> <ins> return fmt.Errorf("windows.AssignProcessToJobObject failed: %w", err) </ins> } if windowsService {


if getPodRevision(replicas[target]) != updateRevision.Name && !isTerminating(replicas[target]) { klog.V(2).InfoS("Pod of StatefulSet is terminating for update", "statefulSet", klog.KObj(set), "pod", klog.KObj(replicas[target])) <del> err := ssc.podControl.DeleteStatefulPod(set, replicas[target]) </del> <ins> if err := ssc.podControl.DeleteStatefulPod(set, replicas[target]); err != nil { if !errors.IsNotFound(err) { return &status, err } } </ins> status.CurrentReplicas-- return &status, err }


{UpdatePodFailure, simpleSetFn}, {UpdateSetStatusFailure, simpleSetFn}, {PodRecreateDeleteFailure, simpleSetFn}, <ins> {NewRevisionDeletePodFailure, simpleSetFn}, </ins> } for _, testCase := range testCases {


} } <ins> func NewRevisionDeletePodFailure(t *testing.T, set *apps.StatefulSet, invariants invariantFunc) { client := fake.NewSimpleClientset(set) om, _, ssc := setupController(client) if err := scaleUpStatefulSetControl(set, ssc, om, invariants); err != nil { t.Errorf("Failed to turn up StatefulSet : %s", err) } var err error set, err = om.setsLister.StatefulSets(set.Namespace).Get(set.Name) if err != nil { t.Fatalf("Error getting updated StatefulSet: %v", err) } if set.Status.Replicas != 3 { t.Error("Failed to scale StatefulSet to 3 replicas") } selector, err := metav1.LabelSelectorAsSelector(set.Spec.Selector) if err != nil { t.Error(err) } pods, err := om.podsLister.Pods(set.Namespace).List(selector) if err != nil { t.Error(err) } // trigger a new revision updateSet := set.DeepCopy() updateSet.Spec.Template.Spec.Containers[0].Image = "nginx-new" if err := om.setsIndexer.Update(updateSet); err != nil { t.Error("Failed to update StatefulSet") } set, err = om.setsLister.StatefulSets(set.Namespace).Get(set.Name) if err != nil { t.Fatalf("Error getting updated StatefulSet: %v", err) } // delete fails om.SetDeleteStatefulPodError(apierrors.NewInternalError(errors.New("API server failed")), 0) _, err = ssc.UpdateStatefulSet(context.TODO(), set, pods) if err == nil { t.Error("Expected err in update StatefulSet when deleting a pod") } set, err = om.setsLister.StatefulSets(set.Namespace).Get(set.Name) if err != nil { t.Fatalf("Error getting updated StatefulSet: %v", err) } if err := invariants(set, om); err != nil { t.Error(err) } if set.Status.CurrentReplicas != 3 { t.Fatalf("Failed pod deletion should not update CurrentReplicas: want 3, got %d", set.Status.CurrentReplicas) } if set.Status.CurrentRevision == set.Status.UpdateRevision { t.Error("Failed to create new revision") } // delete works om.SetDeleteStatefulPodError(nil, 0) status, err := ssc.UpdateStatefulSet(context.TODO(), set, pods) if err != nil { t.Fatalf("Unexpected err in update StatefulSet: %v", err) } if status.CurrentReplicas != 2 { t.Fatalf("Pod deletion should update CurrentReplicas: want 2, got %d", status.CurrentReplicas) } if err := invariants(set, om); err != nil { t.Error(err) } } </ins> func emptyInvariants(set *apps.StatefulSet, om *fakeObjectManager) error { return nil }


} interval := time.NewTicker(time.Millisecond * 500) defer interval.Stop() <del> done := make(chan bool, 1) </del> <ins> done := make(chan bool) </ins> go func() { time.Sleep(time.Minute * 2) <del> done <- true </del> <ins> close(done) </ins> }() for { select {


timedout := make(chan bool) go func() { time.Sleep(gracefulWait) <del> timedout <- true </del> <ins> close(timedout) </ins> }() go func() { select {


} }() err = r.cmd.Wait() <del> stopped <- true </del> <ins> close(stopped) </ins> if exiterr, ok := err.(*exec.ExitError); ok { klog.Infof("etcd server stopped (signal: %s)", exiterr.Error()) // stopped


return } ref := &v1.ObjectReference{ <del> Kind:      "Pod", Name:      nsName.Name, Namespace: nsName.Namespace, </del> <ins> APIVersion: "v1", Kind:       "Pod", Name:       nsName.Name, Namespace:  nsName.Namespace, </ins> } tc.recorder.Eventf(ref, v1.EventTypeNormal, "TaintManagerEviction", "Marking for deletion Pod %s", nsName.String()) }


return } ref := &v1.ObjectReference{ <del> Kind:      "Pod", Name:      nsName.Name, Namespace: nsName.Namespace, </del> <ins> APIVersion: "v1", Kind:       "Pod", Name:       nsName.Name, Namespace:  nsName.Namespace, </ins> } tc.recorder.Eventf(ref, v1.EventTypeNormal, "TaintManagerEviction", "Cancelling deletion of Pod %s", nsName.String()) }


"testing" "time" <ins> "github.com/google/go-cmp/cmp" </ins> v1 "k8s.io/api/core/v1" <ins> metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" </ins> "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types"


featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/kubernetes/pkg/controller/testutil" "k8s.io/kubernetes/pkg/features" <del> metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" </del> ) var timeForControllerToProgressForSanityCheck = 20 * time.Millisecond


t.Errorf("[%v]Unexpected test result. Expected delete %v, got %v", description, expectDelete, podDeleted) } } <ins> // TestPodDeletionEvent Verify that the output events are as expected func TestPodDeletionEvent(t *testing.T) { f := func(path cmp.Path) bool { switch path.String() { // These fields change at runtime, so ignore it case "LastTimestamp", "FirstTimestamp", "ObjectMeta.Name": return true } return false } t.Run("emitPodDeletionEvent", func(t *testing.T) { controller := &NoExecuteTaintManager{} recorder := testutil.NewFakeRecorder() controller.recorder = recorder controller.emitPodDeletionEvent(types.NamespacedName{ Name:      "test", Namespace: "test", }) want := []*v1.Event{ { ObjectMeta: metav1.ObjectMeta{ Namespace: "test", }, InvolvedObject: v1.ObjectReference{ Kind:       "Pod", APIVersion: "v1", Namespace:  "test", Name:       "test", }, Reason:  "TaintManagerEviction", Type:    "Normal", Count:   1, Message: "Marking for deletion Pod test/test", Source:  v1.EventSource{Component: "nodeControllerTest"}, }, } if diff := cmp.Diff(want, recorder.Events, cmp.FilterPath(f, cmp.Ignore())); len(diff) > 0 { t.Errorf("emitPodDeletionEvent() returned data (-want,+got):n%s", diff) } }) t.Run("emitCancelPodDeletionEvent", func(t *testing.T) { controller := &NoExecuteTaintManager{} recorder := testutil.NewFakeRecorder() controller.recorder = recorder controller.emitCancelPodDeletionEvent(types.NamespacedName{ Name:      "test", Namespace: "test", }) want := []*v1.Event{ { ObjectMeta: metav1.ObjectMeta{ Namespace: "test", }, InvolvedObject: v1.ObjectReference{ Kind:       "Pod", APIVersion: "v1", Namespace:  "test", Name:       "test", }, Reason:  "TaintManagerEviction", Type:    "Normal", Count:   1, Message: "Cancelling deletion of Pod test/test", Source:  v1.EventSource{Component: "nodeControllerTest"}, }, } if diff := cmp.Diff(want, recorder.Events, cmp.FilterPath(f, cmp.Ignore())); len(diff) > 0 { t.Errorf("emitPodDeletionEvent() returned data (-want,+got):n%s", diff) } }) } </ins>


QOSReserved featuregate.Feature = "QOSReserved" // owner: @chrishenzie <ins> // kep: https://kep.k8s.io/2485 </ins> // alpha: v1.22 <ins> // beta: v1.27 </ins> // // Enables usage of the ReadWriteOncePod PersistentVolume access mode. ReadWriteOncePod featuregate.Feature = "ReadWriteOncePod"


QOSReserved: {Default: false, PreRelease: featuregate.Alpha}, <del> ReadWriteOncePod: {Default: false, PreRelease: featuregate.Alpha}, </del> <ins> ReadWriteOncePod: {Default: true, PreRelease: featuregate.Beta}, </ins> RecoverVolumeExpansionFailure: {Default: false, PreRelease: featuregate.Alpha},


"github.com/onsi/ginkgo/v2" v1 "k8s.io/api/core/v1" <ins> schedulingv1 "k8s.io/api/scheduling/v1" </ins> metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" errors "k8s.io/apimachinery/pkg/util/errors" <ins> "k8s.io/apimachinery/pkg/util/uuid" </ins> clientset "k8s.io/client-go/kubernetes" "k8s.io/kubernetes/pkg/kubelet/events" "k8s.io/kubernetes/test/e2e/framework"


type readWriteOncePodTest struct { config *storageframework.PerTestConfig <del> cs     clientset.Interface volume *storageframework.VolumeResource pods   []*v1.Pod </del> <ins> cs            clientset.Interface volume        *storageframework.VolumeResource pods          []*v1.Pod priorityClass *schedulingv1.PriorityClass </ins> migrationCheck *migrationOpCheck }


tsInfo: storageframework.TestSuiteInfo{ Name:         "read-write-once-pod", TestPatterns: patterns, <del> FeatureTag:   "[Feature:ReadWriteOncePod]", </del> }, } }


err := l.volume.CleanupResource(ctx) errs = append(errs, err) <ins> if l.priorityClass != nil { framework.Logf("Deleting PriorityClass %v", l.priorityClass.Name) err := l.cs.SchedulingV1().PriorityClasses().Delete(ctx, l.priorityClass.Name, metav1.DeleteOptions{}) errs = append(errs, err) } </ins> framework.ExpectNoError(errors.NewAggregate(errs), "while cleaning up resource") l.migrationCheck.validateMigrationVolumeOpCounts(ctx) }


ginkgo.DeferCleanup(cleanup) }) <del> ginkgo.It("should block a second pod from using an in-use ReadWriteOncePod volume", func(ctx context.Context) { </del> <ins> ginkgo.It("should preempt lower priority pods using ReadWriteOncePod volumes", func(ctx context.Context) { </ins> // Create the ReadWriteOncePod PVC. accessModes := []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod} l.volume = storageframework.CreateVolumeResourceWithAccessModes(ctx, driver, l.config, pattern, t.GetTestSuiteInfo().SupportedSizeRange, accessModes) <ins> l.priorityClass = &schedulingv1.PriorityClass{ ObjectMeta: metav1.ObjectMeta{Name: "e2e-test-read-write-once-pod-" + string(uuid.NewUUID())}, Value:      int32(1000), } _, err := l.cs.SchedulingV1().PriorityClasses().Create(ctx, l.priorityClass, metav1.CreateOptions{}) framework.ExpectNoError(err, "failed to create priority class") </ins> podConfig := e2epod.Config{ NS:           f.Namespace.Name, PVCs:         []*v1.PersistentVolumeClaim{l.volume.Pvc},


framework.ExpectNoError(err, "failed to wait for pod1 running status") l.pods = append(l.pods, pod1) <del> // Create the second pod, which will fail scheduling because the ReadWriteOncePod PVC is already in use. </del> <ins> // Create the second pod, which will preempt the first pod because it's using the // ReadWriteOncePod PVC and has higher priority. </ins> pod2, err := e2epod.MakeSecPod(&podConfig) framework.ExpectNoError(err, "failed to create spec for pod2") <ins> pod2.Spec.PriorityClassName = l.priorityClass.Name </ins> _, err = l.cs.CoreV1().Pods(pod2.Namespace).Create(ctx, pod2, metav1.CreateOptions{}) framework.ExpectNoError(err, "failed to create pod2") <del> err = e2epod.WaitForPodNameUnschedulableInNamespace(ctx, l.cs, pod2.Name, pod2.Namespace) framework.ExpectNoError(err, "failed to wait for pod2 unschedulable status") </del> l.pods = append(l.pods, pod2) <del> // Delete the first pod and observe the second pod can now start. err = e2epod.DeletePodWithWait(ctx, l.cs, pod1) framework.ExpectNoError(err, "failed to delete pod1") </del> <ins> // Wait for the first pod to be preempted and the second pod to start. err = e2epod.WaitForPodNotFoundInNamespace(ctx, l.cs, pod1.Name, pod1.Namespace, f.Timeouts.PodStart) framework.ExpectNoError(err, "failed to wait for pod1 to be preempted") </ins> err = e2epod.WaitTimeoutForPodRunningInNamespace(ctx, l.cs, pod2.Name, pod2.Namespace, f.Timeouts.PodStart) framework.ExpectNoError(err, "failed to wait for pod2 running status") <ins> // Recreate the first pod, which will fail to schedule because the second pod // is using the ReadWriteOncePod PVC and has higher priority. _, err = l.cs.CoreV1().Pods(pod1.Namespace).Create(ctx, pod1, metav1.CreateOptions{}) framework.ExpectNoError(err, "failed to create pod1") err = e2epod.WaitForPodNameUnschedulableInNamespace(ctx, l.cs, pod1.Name, pod1.Namespace) framework.ExpectNoError(err, "failed to wait for pod1 unschedulable status") // Delete the second pod with higher priority and observe the first pod can now start. err = e2epod.DeletePodWithWait(ctx, l.cs, pod2) framework.ExpectNoError(err, "failed to delete pod2") err = e2epod.WaitTimeoutForPodRunningInNamespace(ctx, l.cs, pod1.Name, pod1.Namespace, f.Timeouts.PodStart) framework.ExpectNoError(err, "failed to wait for pod1 running status") </ins> }) ginkgo.It("should block a second pod from using an in-use ReadWriteOncePod volume on the same node", func(ctx context.Context) {


serviceAccountName: csi-hostpathplugin-sa containers: - name: hostpath <del> image: registry.k8s.io/sig-storage/hostpathplugin:v1.9.0 </del> <ins> image: registry.k8s.io/sig-storage/hostpathplugin:v1.11.0 </ins> args: - "--drivername=hostpath.csi.k8s.io" - "--v=5"


command := exec.Command("umount", target) output, err := command.CombinedOutput() if err != nil { <del> if err.Error() == errNoChildProcesses { if command.ProcessState.Success() { // We don't consider errNoChildProcesses an error if the process itself succeeded (see - k/k issue #103753). return nil } // Rewrite err with the actual exit error of the process. err = &exec.ExitError{ProcessState: command.ProcessState} } if mounter.withSafeNotMountedBehavior && strings.Contains(string(output), errNotMounted) { klog.V(4).Infof("ignoring 'not mounted' error for %s", target) return nil } return fmt.Errorf("unmount failed: %vnUnmounting arguments: %snOutput: %s", err, target, string(output)) </del> <ins> return checkUmountError(target, command, output, err, mounter.withSafeNotMountedBehavior) </ins> } return nil }


// UnmountWithForce unmounts given target but will retry unmounting with force option // after given timeout. func (mounter *Mounter) UnmountWithForce(target string, umountTimeout time.Duration) error { <del> err := tryUnmount(mounter, target, umountTimeout) </del> <ins> err := tryUnmount(target, mounter.withSafeNotMountedBehavior, umountTimeout) </ins> if err != nil { if err == context.DeadlineExceeded { klog.V(2).Infof("Timed out waiting for unmount of %s, trying with -f", target) <del> err = forceUmount(target) </del> <ins> err = forceUmount(target, mounter.withSafeNotMountedBehavior) </ins> } return err }


} // tryUnmount calls plain "umount" and waits for unmountTimeout for it to finish. <del> func tryUnmount(mounter *Mounter, path string, unmountTimeout time.Duration) error { klog.V(4).Infof("Unmounting %s", path) </del> <ins> func tryUnmount(target string, withSafeNotMountedBehavior bool, unmountTimeout time.Duration) error { klog.V(4).Infof("Unmounting %s", target) </ins> ctx, cancel := context.WithTimeout(context.Background(), unmountTimeout) defer cancel() <del> cmd := exec.CommandContext(ctx, "umount", path) out, cmderr := cmd.CombinedOutput() </del> <ins> command := exec.CommandContext(ctx, "umount", target) output, err := command.CombinedOutput() </ins> // CombinedOutput() does not return DeadlineExceeded, make sure it's // propagated on timeout.


return ctx.Err() } <del> if cmderr != nil { if mounter.withSafeNotMountedBehavior && strings.Contains(string(out), errNotMounted) { klog.V(4).Infof("ignoring 'not mounted' error for %s", path) return nil } return fmt.Errorf("unmount failed: %vnUnmounting arguments: %snOutput: %s", cmderr, path, string(out)) </del> <ins> if err != nil { return checkUmountError(target, command, output, err, withSafeNotMountedBehavior) </ins> } return nil } <del> func forceUmount(path string) error { cmd := exec.Command("umount", "-f", path) out, cmderr := cmd.CombinedOutput() </del> <ins> func forceUmount(target string, withSafeNotMountedBehavior bool) error { command := exec.Command("umount", "-f", target) output, err := command.CombinedOutput() </ins> <del> if cmderr != nil { return fmt.Errorf("unmount failed: %vnUnmounting arguments: %snOutput: %s", cmderr, path, string(out)) </del> <ins> if err != nil { return checkUmountError(target, command, output, err, withSafeNotMountedBehavior) </ins> } return nil } <ins> // checkUmountError checks a result of umount command and determine a return value. func checkUmountError(target string, command *exec.Cmd, output []byte, err error, withSafeNotMountedBehavior bool) error { if err.Error() == errNoChildProcesses { if command.ProcessState.Success() { // We don't consider errNoChildProcesses an error if the process itself succeeded (see - k/k issue #103753). return nil } // Rewrite err with the actual exit error of the process. err = &exec.ExitError{ProcessState: command.ProcessState} } if withSafeNotMountedBehavior && strings.Contains(string(output), errNotMounted) { klog.V(4).Infof("ignoring 'not mounted' error for %s", target) return nil } return fmt.Errorf("unmount failed: %vnUnmounting arguments: %snOutput: %s", err, target, string(output)) } </ins>


"reflect" "strings" "testing" <ins> "time" </ins> utilexec "k8s.io/utils/exec" testexec "k8s.io/utils/exec/testing"


return testexec.InitFakeCmd(&c, cmd, args...) } } <ins> func TestNotMountedBehaviorOfUnmount(t *testing.T) { target, err := ioutil.TempDir("", "kubelet-umount") if err != nil { t.Errorf("Cannot create temp dir: %v", err) } defer os.RemoveAll(target) m := Mounter{withSafeNotMountedBehavior: true} if err = m.Unmount(target); err != nil { t.Errorf(`Expect complete Unmount(), but it dose not: %v`, err) } if err = tryUnmount(target, m.withSafeNotMountedBehavior, time.Minute); err != nil { t.Errorf(`Expect complete tryUnmount(), but it does not: %v`, err) } // forceUmount exec "umount -f", so skip this case if user is not root. if os.Getuid() == 0 { if err = forceUmount(target, m.withSafeNotMountedBehavior); err != nil { t.Errorf(`Expect complete forceUnmount(), but it does not: %v`, err) } } } </ins>


"fmt" "io/ioutil" "os" <ins> "os/exec" </ins> "reflect" "strings" "sync"


} } <ins> func TestCheckUmountError(t *testing.T) { target := "/test/path" withSafeNotMountedBehavior := true command := exec.Command("uname", "-r") // dummy command return status 0 if err := command.Run(); err != nil { t.Errorf("Faild to exec dummy command. err: %s", err) } testcases := []struct { output   []byte err      error expected bool }{ { err:      errors.New("wait: no child processes"), expected: true, }, { output:   []byte("umount: /test/path: not mounted."), err:      errors.New("exit status 1"), expected: true, }, { output:   []byte("umount: /test/path: No such file or directory"), err:      errors.New("exit status 1"), expected: false, }, } for _, v := range testcases { if err := checkUmountError(target, command, v.output, v.err, withSafeNotMountedBehavior); (err == nil) != v.expected { if v.expected { t.Errorf("Expected to return nil, but did not. err: %s", err) } else { t.Errorf("Expected to return error, but did not.") } } } } </ins> func TestFormat(t *testing.T) { const ( formatCount    = 5


"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" <del> "google.golang.org/grpc/codes" </del> "google.golang.org/grpc/credentials/insecure" <del> "google.golang.org/grpc/status" </del> utilfeature "k8s.io/apiserver/pkg/util/feature" tracing "k8s.io/component-base/tracing" "k8s.io/klog/v2"


klog.V(4).InfoS("Validating the CRI v1 API image version") r.imageClient = runtimeapi.NewImageServiceClient(conn) <del> if _, err := r.imageClient.ImageFsInfo(ctx, &runtimeapi.ImageFsInfoRequest{}); err == nil { klog.V(2).InfoS("Validated CRI v1 image API") } else if status.Code(err) == codes.Unimplemented { return fmt.Errorf("CRI v1 image API is not implemented for endpoint %q: %w", endpoint, err) </del> <ins> if _, err := r.imageClient.ImageFsInfo(ctx, &runtimeapi.ImageFsInfoRequest{}); err != nil { return fmt.Errorf("validate CRI v1 image API for endpoint %q: %w", endpoint, err) </ins> } <ins> klog.V(2).InfoS("Validated CRI v1 image API") </ins> return nil }


klog.V(4).InfoS("Validating the CRI v1 API runtime version") r.runtimeClient = runtimeapi.NewRuntimeServiceClient(conn) <del> if _, err := r.runtimeClient.Version(ctx, &runtimeapi.VersionRequest{}); err == nil { klog.V(2).InfoS("Validated CRI v1 runtime API") } else if status.Code(err) == codes.Unimplemented { return fmt.Errorf("CRI v1 runtime API is not implemented for endpoint %q: %w", endpoint, err) </del> <ins> if _, err := r.runtimeClient.Version(ctx, &runtimeapi.VersionRequest{}); err != nil { return fmt.Errorf("validate CRI v1 runtime API for endpoint %q: %w", endpoint, err) </ins> } <ins> klog.V(2).InfoS("Validated CRI v1 runtime API") </ins> return nil }


return converted, nil } <ins> // Deep copy the list before we invoke the converter to ensure that if the converter does mutate the // list (which it shouldn't, but you never know), it doesn't have any impact. convertedList := list.DeepCopy() convertedList.SetAPIVersion(desiredAPIVersion) </ins> convertedObjects, err := c.converter.Convert(list, toGVK.GroupVersion()) if err != nil { return nil, fmt.Errorf("conversion for %v failed: %w", in.GetObjectKind().GroupVersionKind(), err)


return nil, fmt.Errorf("conversion for %v returned %d objects, expected %d", in.GetObjectKind().GroupVersionKind(), len(convertedObjects.Items), len(objectsToConvert)) } <del> // start a deepcopy of the input and fill in the converted objects from the response at the right spots. </del> <ins> // Fill in the converted objects from the response at the right spots. </ins> // The response list might be sparse because objects had the right version already. <del> convertedList := list.DeepCopy() convertedList.SetAPIVersion(desiredAPIVersion) </del> convertedIndex := 0 for i := range convertedList.Items { original := &convertedList.Items[i]


SourceObject: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "example.com/v1", <ins> "metadata":   map[string]interface{}{}, </ins> "other":      "data", "kind":       "foo", },


ExpectedObject: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "example.com/v2", <ins> "metadata":   map[string]interface{}{}, </ins> "other":      "data", "kind":       "foo", },


{ Object: map[string]interface{}{ "apiVersion": "example.com/v1", <ins> "metadata":   map[string]interface{}{}, </ins> "kind":       "foo", "other":      "data", },


{ Object: map[string]interface{}{ "apiVersion": "example.com/v1", <ins> "metadata":   map[string]interface{}{}, </ins> "kind":       "foo", "other":      "data2", },


{ Object: map[string]interface{}{ "apiVersion": "example.com/v2", <ins> "metadata":   map[string]interface{}{}, </ins> "kind":       "foo", "other":      "data", },


{ Object: map[string]interface{}{ "apiVersion": "example.com/v2", <ins> "metadata":   map[string]interface{}{}, </ins> "kind":       "foo", "other":      "data2", },


}) } } <ins> func TestConverterMutatesInput(t *testing.T) { testCRD := apiextensionsv1.CustomResourceDefinition{ Spec: apiextensionsv1.CustomResourceDefinitionSpec{ Conversion: &apiextensionsv1.CustomResourceConversion{ Strategy: apiextensionsv1.NoneConverter, }, Group: "test.k8s.io", Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ { Name:   "v1alpha1", Served: true, }, { Name:   "v1alpha2", Served: true, }, }, }, } safeConverter, _, err := NewDelegatingConverter(&testCRD, &inputMutatingConverter{}) if err != nil { t.Fatalf("Cannot create converter: %v", err) } input := &unstructured.UnstructuredList{ Object: map[string]interface{}{ "apiVersion": "test.k8s.io/v1alpha1", }, Items: []unstructured.Unstructured{ { Object: map[string]interface{}{ "apiVersion": "test.k8s.io/v1alpha1", "metadata": map[string]interface{}{ "name": "item1", }, }, }, { Object: map[string]interface{}{ "apiVersion": "test.k8s.io/v1alpha1", "metadata": map[string]interface{}{ "name": "item2", }, }, }, }, } toVersion, _ := schema.ParseGroupVersion("test.k8s.io/v1alpha2") toVersions := schema.GroupVersions{toVersion} converted, err := safeConverter.ConvertToVersion(input, toVersions) if err != nil { t.Fatalf("unexpected error: %v", err) } convertedList := converted.(*unstructured.UnstructuredList) if e, a := 2, len(convertedList.Items); e != a { t.Fatalf("length: expected %d, got %d", e, a) } } type inputMutatingConverter struct{} func (i *inputMutatingConverter) Convert(in *unstructured.UnstructuredList, targetGVK schema.GroupVersion) (*unstructured.UnstructuredList, error) { out := &unstructured.UnstructuredList{} for _, obj := range in.Items { u := obj.DeepCopy() u.SetAPIVersion(targetGVK.String()) out.Items = append(out.Items, *u) } in.Items = nil return out, nil } </ins>


ginkgo.It("should log default container if not specified", func(ctx context.Context) { ginkgo.By("Waiting for log generator to start.") <del> if !e2epod.CheckPodsRunningReadyOrSucceeded(ctx, c, ns, []string{podName}, framework.PodStartTimeout) { framework.Failf("Pod %s was not ready", podName) </del> <ins> // we need to wait for pod completion, to check the generated number of lines if err := e2epod.WaitForPodSuccessInNamespaceTimeout(ctx, c, podName, ns, framework.PodStartTimeout); err != nil { framework.Failf("Pod %s did not finish: %v", podName, err) </ins> } ginkgo.By("specified container log lines")


kube::golang::build_some_binaries "${nonstatics[@]}" fi <ins> # Workaround for https://github.com/kubernetes/kubernetes/issues/115675 local testgogcflags="${gogcflags}" local testgoexperiment="${GOEXPERIMENT:-}" if [[ "$(go version)" == *"go1.20"* && "${platform}" == "linux/arm" ]]; then # work around https://github.com/golang/go/issues/58339 until fixed in go1.20.x testgogcflags="${testgogcflags} -d=inlstaticinit=0" # work around https://github.com/golang/go/issues/58425 testgoexperiment="nounified,${testgoexperiment}" kube::log::info "Building test binaries with GOEXPERIMENT=${testgoexperiment} GCFLAGS=${testgogcflags} ASMFLAGS=${goasmflags} LDFLAGS=${goldflags}" fi </ins> for test in "${tests[@]:+${tests[@]}}"; do local outfile testpkg outfile=$(kube::golang::outfile_for_binary "${test}" "${platform}") testpkg=$(dirname "${test}") mkdir -p "$(dirname "${outfile}")" <del> go test -c  </del> <ins> GOEXPERIMENT="${testgoexperiment}" go test -c  </ins> ${goflags:+"${goflags[@]}"}  <del> -gcflags="${gogcflags}"  </del> <ins> -gcflags="${testgogcflags}"  </ins> -asmflags="${goasmflags}"  -ldflags="${goldflags}"  -tags="${gotags:-}"


msg, )) } <ins> // warn if labelSelector is empty which is no-match. if t.LabelSelector == nil { warnings = append(warnings, fmt.Sprintf("%s: a null labelSelector results in matching no pod", fieldPath.Child("spec", "topologySpreadConstraints").Index(i).Child("labelSelector"))) } </ins> } // use of deprecated annotations


if podSpec.TerminationGracePeriodSeconds != nil && *podSpec.TerminationGracePeriodSeconds < 0 { warnings = append(warnings, fmt.Sprintf("%s: must be >= 0; negative values are invalid and will be treated as 1", fieldPath.Child("spec", "terminationGracePeriodSeconds"))) } <ins> if podSpec.Affinity != nil { if affinity := podSpec.Affinity.PodAffinity; affinity != nil { warnings = append(warnings, warningsForPodAffinityTerms(affinity.RequiredDuringSchedulingIgnoredDuringExecution, fieldPath.Child("spec", "affinity", "podAffinity", "requiredDuringSchedulingIgnoredDuringExecution"))...) warnings = append(warnings, warningsForWeightedPodAffinityTerms(affinity.PreferredDuringSchedulingIgnoredDuringExecution, fieldPath.Child("spec", "affinity", "podAffinity", "preferredDuringSchedulingIgnoredDuringExecution"))...) } if affinity := podSpec.Affinity.PodAntiAffinity; affinity != nil { warnings = append(warnings, warningsForPodAffinityTerms(affinity.RequiredDuringSchedulingIgnoredDuringExecution, fieldPath.Child("spec", "affinity", "podAntiAffinity", "requiredDuringSchedulingIgnoredDuringExecution"))...) warnings = append(warnings, warningsForWeightedPodAffinityTerms(affinity.PreferredDuringSchedulingIgnoredDuringExecution, fieldPath.Child("spec", "affinity", "podAntiAffinity", "preferredDuringSchedulingIgnoredDuringExecution"))...) } } return warnings } func warningsForPodAffinityTerms(terms []api.PodAffinityTerm, fieldPath *field.Path) []string { var warnings []string for i, t := range terms { if t.LabelSelector == nil { warnings = append(warnings, fmt.Sprintf("%s: a null labelSelector results in matching no pod", fieldPath.Index(i).Child("labelSelector"))) } } return warnings } func warningsForWeightedPodAffinityTerms(terms []api.WeightedPodAffinityTerm, fieldPath *field.Path) []string { var warnings []string for i, t := range terms { // warn if labelSelector is empty which is no-match. if t.PodAffinityTerm.LabelSelector == nil { warnings = append(warnings, fmt.Sprintf("%s: a null labelSelector results in matching no pod", fieldPath.Index(i).Child("podAffinityTerm", "labelSelector"))) } } </ins> return warnings }