|
| 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 | + "time" |
| 21 | + |
| 22 | + v1 "k8s.io/api/core/v1" |
| 23 | + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| 24 | + "k8s.io/apimachinery/pkg/labels" |
| 25 | + "k8s.io/apimachinery/pkg/util/uuid" |
| 26 | + "k8s.io/kubernetes/test/e2e/framework" |
| 27 | + e2ekubelet "k8s.io/kubernetes/test/e2e/framework/kubelet" |
| 28 | + e2enode "k8s.io/kubernetes/test/e2e/framework/node" |
| 29 | + e2epod "k8s.io/kubernetes/test/e2e/framework/pod" |
| 30 | + imageutils "k8s.io/kubernetes/test/utils/image" |
| 31 | + |
| 32 | + "github.com/onsi/ginkgo" |
| 33 | +) |
| 34 | + |
| 35 | +var _ = SIGDescribe("[Feature:Windows] Kubelet-Stats [Serial]", func() { |
| 36 | + f := framework.NewDefaultFramework("kubelet-stats-test-windows") |
| 37 | + |
| 38 | + ginkgo.Describe("Kubelet stats collection for Windows nodes", func() { |
| 39 | + ginkgo.Context("when running 10 pods", func() { |
| 40 | + // 10 seconds is the default scrape timeout for metrics-server and kube-prometheus |
| 41 | + ginkgo.It("should return within 10 seconds", func() { |
| 42 | + |
| 43 | + ginkgo.By("Selecting a Windows node") |
| 44 | + targetNode, err := findWindowsNode(f) |
| 45 | + framework.ExpectNoError(err, "Error finding Windows node") |
| 46 | + framework.Logf("Using node: %v", targetNode.Name) |
| 47 | + |
| 48 | + ginkgo.By("Scheduling 10 pods") |
| 49 | + powershellImage := imageutils.GetConfig(imageutils.BusyBox) |
| 50 | + pods := newKubeletStatsTestPods(10, powershellImage, targetNode.Name) |
| 51 | + f.PodClient().CreateBatch(pods) |
| 52 | + |
| 53 | + ginkgo.By("Waiting up to 3 minutes for pods to be running") |
| 54 | + timeout := 3 * time.Minute |
| 55 | + e2epod.WaitForPodsRunningReady(f.ClientSet, f.Namespace.Name, 10, 0, timeout, make(map[string]string)) |
| 56 | + |
| 57 | + ginkgo.By("Getting kubelet stats 5 times and checking average duration") |
| 58 | + iterations := 5 |
| 59 | + var totalDurationMs int64 |
| 60 | + |
| 61 | + for i := 0; i < iterations; i++ { |
| 62 | + start := time.Now() |
| 63 | + nodeStats, err := e2ekubelet.GetStatsSummary(f.ClientSet, targetNode.Name) |
| 64 | + duration := time.Since(start) |
| 65 | + totalDurationMs += duration.Milliseconds() |
| 66 | + |
| 67 | + framework.ExpectNoError(err, "Error getting kubelet stats") |
| 68 | + |
| 69 | + // Perform some basic sanity checks on retrieved stats for pods in this test's namespace |
| 70 | + statsChecked := 0 |
| 71 | + for _, podStats := range nodeStats.Pods { |
| 72 | + if podStats.PodRef.Namespace != f.Namespace.Name { |
| 73 | + continue |
| 74 | + } |
| 75 | + statsChecked = statsChecked + 1 |
| 76 | + |
| 77 | + framework.ExpectEqual(*podStats.CPU.UsageCoreNanoSeconds > 0, true, "Pod stats should not report 0 cpu usage") |
| 78 | + framework.ExpectEqual(*podStats.Memory.WorkingSetBytes > 0, true, "Pod stats should not report 0 bytes for memory working set ") |
| 79 | + } |
| 80 | + framework.ExpectEqual(statsChecked, 10, "Should find stats for 10 pods in kubelet stats") |
| 81 | + |
| 82 | + time.Sleep(5 * time.Second) |
| 83 | + } |
| 84 | + |
| 85 | + avgDurationMs := totalDurationMs / int64(iterations) |
| 86 | + |
| 87 | + durationMatch := avgDurationMs <= time.Duration(10*time.Second).Milliseconds() |
| 88 | + framework.Logf("Getting kubelet stats for node %v took an average of %v milliseconds over %v iterations", targetNode.Name, avgDurationMs, iterations) |
| 89 | + framework.ExpectEqual(durationMatch, true, "Collecting kubelet stats should not take longer than 10 seconds") |
| 90 | + }) |
| 91 | + }) |
| 92 | + }) |
| 93 | +}) |
| 94 | + |
| 95 | +// findWindowsNode finds a Windows node that is Ready and Schedulable |
| 96 | +func findWindowsNode(f *framework.Framework) (v1.Node, error) { |
| 97 | + selector := labels.Set{"kubernetes.io/os": "windows"}.AsSelector() |
| 98 | + nodeList, err := f.ClientSet.CoreV1().Nodes().List(metav1.ListOptions{LabelSelector: selector.String()}) |
| 99 | + |
| 100 | + if err != nil { |
| 101 | + return v1.Node{}, err |
| 102 | + } |
| 103 | + |
| 104 | + var targetNode v1.Node |
| 105 | + foundNode := false |
| 106 | + for _, n := range nodeList.Items { |
| 107 | + if e2enode.IsConditionSetAsExpected(&n, v1.NodeReady, true) && !n.Spec.Unschedulable { |
| 108 | + targetNode = n |
| 109 | + foundNode = true |
| 110 | + break |
| 111 | + } |
| 112 | + } |
| 113 | + |
| 114 | + if foundNode == false { |
| 115 | + framework.Skipf("Could not find and ready and schedulable Windows nodes") |
| 116 | + } |
| 117 | + |
| 118 | + return targetNode, nil |
| 119 | +} |
| 120 | + |
| 121 | +// newKubeletStatsTestPods creates a list of pods (specification) for test. |
| 122 | +func newKubeletStatsTestPods(numPods int, image imageutils.Config, nodeName string) []*v1.Pod { |
| 123 | + var pods []*v1.Pod |
| 124 | + |
| 125 | + for i := 0; i < numPods; i++ { |
| 126 | + podName := "statscollectiontest-" + string(uuid.NewUUID()) |
| 127 | + pod := v1.Pod{ |
| 128 | + ObjectMeta: metav1.ObjectMeta{ |
| 129 | + Name: podName, |
| 130 | + Labels: map[string]string{ |
| 131 | + "name": podName, |
| 132 | + "testapp": "stats-collection", |
| 133 | + }, |
| 134 | + }, |
| 135 | + Spec: v1.PodSpec{ |
| 136 | + Containers: []v1.Container{ |
| 137 | + { |
| 138 | + Image: image.GetE2EImage(), |
| 139 | + Name: podName, |
| 140 | + Command: []string{ |
| 141 | + "powershell.exe", |
| 142 | + "-Command", |
| 143 | + "sleep -Seconds 600", |
| 144 | + }, |
| 145 | + }, |
| 146 | + }, |
| 147 | + |
| 148 | + NodeName: nodeName, |
| 149 | + }, |
| 150 | + } |
| 151 | + |
| 152 | + pods = append(pods, &pod) |
| 153 | + } |
| 154 | + |
| 155 | + return pods |
| 156 | +} |
0 commit comments