k8s.io/kubernetes@v1.29.3/test/e2e/windows/kubelet_stats.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package windows
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	v1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/labels"
    27  	"k8s.io/apimachinery/pkg/util/uuid"
    28  	"k8s.io/kubernetes/test/e2e/feature"
    29  	"k8s.io/kubernetes/test/e2e/framework"
    30  	e2ekubelet "k8s.io/kubernetes/test/e2e/framework/kubelet"
    31  	e2enode "k8s.io/kubernetes/test/e2e/framework/node"
    32  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    33  	e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
    34  	imageutils "k8s.io/kubernetes/test/utils/image"
    35  	admissionapi "k8s.io/pod-security-admission/api"
    36  
    37  	"github.com/onsi/ginkgo/v2"
    38  	"github.com/onsi/gomega"
    39  )
    40  
    41  var _ = sigDescribe(feature.Windows, "Kubelet-Stats", framework.WithSerial(), skipUnlessWindows(func() {
    42  	f := framework.NewDefaultFramework("kubelet-stats-test-windows-serial")
    43  	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
    44  
    45  	ginkgo.Describe("Kubelet stats collection for Windows nodes", func() {
    46  
    47  		ginkgo.Context("when running 10 pods", func() {
    48  			// 10 seconds is the default scrape timeout for metrics-server and kube-prometheus
    49  			ginkgo.It("should return within 10 seconds", func(ctx context.Context) {
    50  
    51  				ginkgo.By("Selecting a Windows node")
    52  				targetNode, err := findWindowsNode(ctx, f)
    53  				framework.ExpectNoError(err, "Error finding Windows node")
    54  				framework.Logf("Using node: %v", targetNode.Name)
    55  
    56  				ginkgo.By("Scheduling 10 pods")
    57  				powershellImage := imageutils.GetConfig(imageutils.BusyBox)
    58  				pods := newKubeletStatsTestPods(10, powershellImage, targetNode.Name)
    59  				e2epod.NewPodClient(f).CreateBatch(ctx, pods)
    60  
    61  				ginkgo.By("Waiting up to 3 minutes for pods to be running")
    62  				timeout := 3 * time.Minute
    63  				err = e2epod.WaitForPodsRunningReady(ctx, f.ClientSet, f.Namespace.Name, 10, 0, timeout)
    64  				framework.ExpectNoError(err)
    65  
    66  				ginkgo.By("Getting kubelet stats 5 times and checking average duration")
    67  				iterations := 5
    68  				var totalDurationMs int64
    69  
    70  				for i := 0; i < iterations; i++ {
    71  					start := time.Now()
    72  					nodeStats, err := e2ekubelet.GetStatsSummary(ctx, f.ClientSet, targetNode.Name)
    73  					duration := time.Since(start)
    74  					totalDurationMs += duration.Milliseconds()
    75  
    76  					framework.ExpectNoError(err, "Error getting kubelet stats")
    77  
    78  					// Perform some basic sanity checks on retrieved stats for pods in this test's namespace
    79  					statsChecked := 0
    80  					for _, podStats := range nodeStats.Pods {
    81  						if podStats.PodRef.Namespace != f.Namespace.Name {
    82  							continue
    83  						}
    84  						statsChecked = statsChecked + 1
    85  
    86  						if *podStats.CPU.UsageCoreNanoSeconds <= 0 {
    87  							framework.Failf("Pod %s/%s stats report cpu usage equal to %v but it should be greater than 0", podStats.PodRef.Namespace, podStats.PodRef.Name, *podStats.CPU.UsageCoreNanoSeconds)
    88  						}
    89  						if *podStats.Memory.WorkingSetBytes <= 0 {
    90  							framework.Failf("Pod %s/%s stats report bytes for memory working set equal to %v but it should be greater than 0", podStats.PodRef.Namespace, podStats.PodRef.Name, *podStats.Memory.WorkingSetBytes)
    91  						}
    92  
    93  						for _, containerStats := range podStats.Containers {
    94  							if containerStats.Logs == nil {
    95  								framework.Failf("Pod %s/%s stats should have container log stats, but it is nil", podStats.PodRef.Namespace, podStats.PodRef.Name)
    96  							}
    97  							if *containerStats.Logs.AvailableBytes <= 0 {
    98  								framework.Failf("Pod %s/%s container log stats report %v bytes for AvailableBytes, but it should be greater than 0", podStats.PodRef.Namespace, podStats.PodRef.Name, *containerStats.Logs.AvailableBytes)
    99  							}
   100  						}
   101  					}
   102  					gomega.Expect(statsChecked).To(gomega.Equal(10), "Should find stats for 10 pods in kubelet stats")
   103  
   104  					time.Sleep(5 * time.Second)
   105  				}
   106  
   107  				avgDurationMs := totalDurationMs / int64(iterations)
   108  
   109  				durationMatch := avgDurationMs <= time.Duration(10*time.Second).Milliseconds()
   110  				framework.Logf("Getting kubelet stats for node %v took an average of %v milliseconds over %v iterations", targetNode.Name, avgDurationMs, iterations)
   111  				if !durationMatch {
   112  					framework.Failf("Collecting kubelet stats took %v seconds but it should not take longer than 10 seconds", durationMatch)
   113  				}
   114  			})
   115  		})
   116  	})
   117  }))
   118  
   119  var _ = sigDescribe(feature.Windows, "Kubelet-Stats", skipUnlessWindows(func() {
   120  	f := framework.NewDefaultFramework("kubelet-stats-test-windows")
   121  	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
   122  
   123  	ginkgo.Describe("Kubelet stats collection for Windows nodes", func() {
   124  
   125  		ginkgo.Context("when windows is booted", func() {
   126  			ginkgo.It("should return bootid within 10 seconds", func(ctx context.Context) {
   127  				ginkgo.By("Selecting a Windows node")
   128  				targetNode, err := findWindowsNode(ctx, f)
   129  				framework.ExpectNoError(err, "Error finding Windows node")
   130  				framework.Logf("Using node: %v", targetNode.Name)
   131  
   132  				ginkgo.By("Getting bootid")
   133  				if len(targetNode.Status.NodeInfo.BootID) == 0 {
   134  					framework.Failf("expected bootId in kubelet stats, got none")
   135  				}
   136  			})
   137  		})
   138  
   139  		ginkgo.Context("when running 3 pods", func() {
   140  			// 10 seconds is the default scrape timeout for metrics-server and kube-prometheus
   141  			ginkgo.It("should return within 10 seconds", func(ctx context.Context) {
   142  
   143  				ginkgo.By("Selecting a Windows node")
   144  				targetNode, err := findWindowsNode(ctx, f)
   145  				framework.ExpectNoError(err, "Error finding Windows node")
   146  				framework.Logf("Using node: %v", targetNode.Name)
   147  
   148  				ginkgo.By("Scheduling 3 pods")
   149  				powershellImage := imageutils.GetConfig(imageutils.BusyBox)
   150  				pods := newKubeletStatsTestPods(3, powershellImage, targetNode.Name)
   151  				e2epod.NewPodClient(f).CreateBatch(ctx, pods)
   152  
   153  				ginkgo.By("Waiting up to 3 minutes for pods to be running")
   154  				timeout := 3 * time.Minute
   155  				err = e2epod.WaitForPodsRunningReady(ctx, f.ClientSet, f.Namespace.Name, 3, 0, timeout)
   156  				framework.ExpectNoError(err)
   157  
   158  				ginkgo.By("Getting kubelet stats 1 time")
   159  				iterations := 1
   160  				var totalDurationMs int64
   161  
   162  				for i := 0; i < iterations; i++ {
   163  					start := time.Now()
   164  					nodeStats, err := e2ekubelet.GetStatsSummary(ctx, f.ClientSet, targetNode.Name)
   165  					duration := time.Since(start)
   166  					totalDurationMs += duration.Milliseconds()
   167  
   168  					framework.ExpectNoError(err, "Error getting kubelet stats")
   169  
   170  					// Perform some basic sanity checks on retrieved stats for pods in this test's namespace
   171  					statsChecked := 0
   172  					for _, podStats := range nodeStats.Pods {
   173  						if podStats.PodRef.Namespace != f.Namespace.Name {
   174  							continue
   175  						}
   176  						statsChecked = statsChecked + 1
   177  
   178  						if *podStats.CPU.UsageCoreNanoSeconds <= 0 {
   179  							framework.Failf("Pod %s/%s stats report cpu usage equal to %v but it should be greater than 0", podStats.PodRef.Namespace, podStats.PodRef.Name, *podStats.CPU.UsageCoreNanoSeconds)
   180  						}
   181  						if *podStats.Memory.WorkingSetBytes <= 0 {
   182  							framework.Failf("Pod %s/%s stats report bytes for memory working set equal to %v but it should be greater than 0", podStats.PodRef.Namespace, podStats.PodRef.Name, *podStats.Memory.WorkingSetBytes)
   183  						}
   184  
   185  						for _, containerStats := range podStats.Containers {
   186  							if containerStats.Logs == nil {
   187  								framework.Failf("Pod %s/%s stats should have container log stats, but it is nil", podStats.PodRef.Namespace, podStats.PodRef.Name)
   188  							}
   189  							if *containerStats.Logs.AvailableBytes <= 0 {
   190  								framework.Failf("Pod %s/%s container log stats report %v bytes for AvailableBytes, but it should be greater than 0", podStats.PodRef.Namespace, podStats.PodRef.Name, *containerStats.Logs.AvailableBytes)
   191  							}
   192  						}
   193  					}
   194  					gomega.Expect(statsChecked).To(gomega.Equal(3), "Should find stats for 3 pods in kubelet stats")
   195  
   196  					time.Sleep(5 * time.Second)
   197  				}
   198  
   199  				avgDurationMs := totalDurationMs / int64(iterations)
   200  
   201  				durationMatch := avgDurationMs <= time.Duration(10*time.Second).Milliseconds()
   202  				framework.Logf("Getting kubelet stats for node %v took an average of %v milliseconds over %v iterations", targetNode.Name, avgDurationMs, iterations)
   203  				if !durationMatch {
   204  					framework.Failf("Collecting kubelet stats took %v seconds but it should not take longer than 10 seconds", durationMatch)
   205  				}
   206  			})
   207  		})
   208  	})
   209  }))
   210  
   211  // findWindowsNode finds a Windows node that is Ready and Schedulable
   212  func findWindowsNode(ctx context.Context, f *framework.Framework) (v1.Node, error) {
   213  	selector := labels.Set{"kubernetes.io/os": "windows"}.AsSelector()
   214  	nodeList, err := f.ClientSet.CoreV1().Nodes().List(ctx, metav1.ListOptions{LabelSelector: selector.String()})
   215  
   216  	if err != nil {
   217  		return v1.Node{}, err
   218  	}
   219  
   220  	var targetNode v1.Node
   221  	foundNode := false
   222  	for _, n := range nodeList.Items {
   223  		if e2enode.IsNodeReady(&n) && e2enode.IsNodeSchedulable(&n) {
   224  			targetNode = n
   225  			foundNode = true
   226  			break
   227  		}
   228  	}
   229  
   230  	if !foundNode {
   231  		e2eskipper.Skipf("Could not find and ready and schedulable Windows nodes")
   232  	}
   233  
   234  	return targetNode, nil
   235  }
   236  
   237  // newKubeletStatsTestPods creates a list of pods (specification) for test.
   238  func newKubeletStatsTestPods(numPods int, image imageutils.Config, nodeName string) []*v1.Pod {
   239  	var pods []*v1.Pod
   240  
   241  	for i := 0; i < numPods; i++ {
   242  		podName := "statscollectiontest-" + string(uuid.NewUUID())
   243  		pod := v1.Pod{
   244  			ObjectMeta: metav1.ObjectMeta{
   245  				Name: fmt.Sprintf("%s-%d", podName, i),
   246  				Labels: map[string]string{
   247  					"name":    podName,
   248  					"testapp": "stats-collection",
   249  				},
   250  			},
   251  			Spec: v1.PodSpec{
   252  				Containers: []v1.Container{
   253  					{
   254  						Image: image.GetE2EImage(),
   255  						Name:  "stat-container",
   256  						Command: []string{
   257  							"powershell.exe",
   258  							"-Command",
   259  							"sleep -Seconds 600",
   260  						},
   261  					},
   262  				},
   263  				InitContainers: []v1.Container{
   264  					{
   265  						Image: image.GetE2EImage(),
   266  						Name:  "init-container",
   267  						Command: []string{
   268  							"powershell.exe",
   269  							"-Command",
   270  							"sleep -Seconds 1",
   271  						},
   272  					},
   273  				},
   274  				NodeName: nodeName,
   275  			},
   276  		}
   277  
   278  		pods = append(pods, &pod)
   279  	}
   280  
   281  	return pods
   282  }