github.com/aclisp/heapster@v0.19.2-0.20160613100040-51756f899a96/metrics/sources/summary/summary_test.go (about) 1 // Copyright 2015 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package summary 16 17 import ( 18 "encoding/json" 19 "net/http/httptest" 20 "strconv" 21 "strings" 22 "testing" 23 "time" 24 25 "github.com/stretchr/testify/assert" 26 "github.com/stretchr/testify/require" 27 "k8s.io/heapster/metrics/core" 28 "k8s.io/heapster/metrics/sources/kubelet" 29 "k8s.io/kubernetes/pkg/api/unversioned" 30 "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/stats" 31 util "k8s.io/kubernetes/pkg/util/testing" 32 ) 33 34 const ( 35 // Offsets from seed value in generated container stats. 36 offsetCPUUsageCores = iota 37 offsetCPUUsageCoreSeconds 38 offsetMemPageFaults 39 offsetMemMajorPageFaults 40 offsetMemUsageBytes 41 offsetMemWorkingSetBytes 42 offsetNetRxBytes 43 offsetNetRxErrors 44 offsetNetTxBytes 45 offsetNetTxErrors 46 offsetFsUsed 47 offsetFsCapacity 48 offsetFsAvailable 49 ) 50 51 const ( 52 seedNode = 0 53 seedRuntime = 100 54 seedKubelet = 200 55 seedMisc = 300 56 seedPod0 = 1000 57 seedPod0Container0 = 2000 58 seedPod0Container1 = 2001 59 seedPod1 = 3000 60 seedPod1Container = 4000 61 seedPod2 = 5000 62 seedPod2Container = 6000 63 ) 64 65 const ( 66 namespace0 = "test0" 67 namespace1 = "test1" 68 69 pName0 = "pod0" 70 pName1 = "pod1" 71 pName2 = "pod0" // ensure pName2 conflicts with pName0, but is in a different namespace 72 73 cName00 = "c0" 74 cName01 = "c1" 75 cName10 = "c0" // ensure cName10 conflicts with cName02, but is in a different pod 76 cName20 = "c1" // ensure cName20 conflicts with cName01, but is in a different pod + namespace 77 ) 78 79 var ( 80 scrapeTime = time.Now() 81 startTime = time.Now().Add(-time.Minute) 82 ) 83 84 var nodeInfo = NodeInfo{ 85 NodeName: "test", 86 HostName: "test-hostname", 87 HostID: "1234567890", 88 KubeletVersion: "1.2", 89 } 90 91 type fakeSource struct { 92 scraped bool 93 } 94 95 func (f *fakeSource) Name() string { return "fake" } 96 func (f *fakeSource) ScrapeMetrics(start, end time.Time) *core.DataBatch { 97 f.scraped = true 98 return nil 99 } 100 101 func testingSummaryMetricsSource() *summaryMetricsSource { 102 return &summaryMetricsSource{ 103 node: nodeInfo, 104 kubeletClient: &kubelet.KubeletClient{}, 105 useFallback: false, 106 fallback: &fakeSource{}, 107 } 108 } 109 110 func TestDecodeSummaryMetrics(t *testing.T) { 111 ms := testingSummaryMetricsSource() 112 summary := stats.Summary{ 113 Node: stats.NodeStats{ 114 NodeName: nodeInfo.NodeName, 115 StartTime: unversioned.NewTime(startTime), 116 CPU: genTestSummaryCPU(seedNode), 117 Memory: genTestSummaryMemory(seedNode), 118 Network: genTestSummaryNetwork(seedNode), 119 SystemContainers: []stats.ContainerStats{ 120 genTestSummaryContainer(stats.SystemContainerKubelet, seedKubelet), 121 genTestSummaryContainer(stats.SystemContainerRuntime, seedRuntime), 122 genTestSummaryContainer(stats.SystemContainerMisc, seedMisc), 123 }, 124 Fs: genTestSummaryFsStats(seedNode), 125 }, 126 Pods: []stats.PodStats{{ 127 PodRef: stats.PodReference{ 128 Name: pName0, 129 Namespace: namespace0, 130 }, 131 StartTime: unversioned.NewTime(startTime), 132 Network: genTestSummaryNetwork(seedPod0), 133 Containers: []stats.ContainerStats{ 134 genTestSummaryContainer(cName00, seedPod0Container0), 135 genTestSummaryContainer(cName01, seedPod0Container1), 136 }, 137 }, { 138 PodRef: stats.PodReference{ 139 Name: pName1, 140 Namespace: namespace0, 141 }, 142 StartTime: unversioned.NewTime(startTime), 143 Network: genTestSummaryNetwork(seedPod1), 144 Containers: []stats.ContainerStats{ 145 genTestSummaryContainer(cName10, seedPod1Container), 146 }, 147 VolumeStats: []stats.VolumeStats{{ 148 Name: "A", 149 FsStats: *genTestSummaryFsStats(seedPod1), 150 }, { 151 Name: "B", 152 FsStats: *genTestSummaryFsStats(seedPod1), 153 }}, 154 }, { 155 PodRef: stats.PodReference{ 156 Name: pName2, 157 Namespace: namespace1, 158 }, 159 StartTime: unversioned.NewTime(startTime), 160 Network: genTestSummaryNetwork(seedPod2), 161 Containers: []stats.ContainerStats{ 162 genTestSummaryContainer(cName20, seedPod2Container), 163 }, 164 }}, 165 } 166 167 containerFs := []string{"/", "logs"} 168 expectations := []struct { 169 key string 170 setType string 171 seed int 172 cpu bool 173 memory bool 174 network bool 175 fs []string 176 }{{ 177 key: core.NodeKey(nodeInfo.NodeName), 178 setType: core.MetricSetTypeNode, 179 seed: seedNode, 180 cpu: true, 181 memory: true, 182 network: true, 183 fs: []string{"/"}, 184 }, { 185 key: core.NodeContainerKey(nodeInfo.NodeName, "kubelet"), 186 setType: core.MetricSetTypeSystemContainer, 187 seed: seedKubelet, 188 cpu: true, 189 memory: true, 190 }, { 191 key: core.NodeContainerKey(nodeInfo.NodeName, "docker-daemon"), 192 setType: core.MetricSetTypeSystemContainer, 193 seed: seedRuntime, 194 cpu: true, 195 memory: true, 196 }, { 197 key: core.NodeContainerKey(nodeInfo.NodeName, "system"), 198 setType: core.MetricSetTypeSystemContainer, 199 seed: seedMisc, 200 cpu: true, 201 memory: true, 202 }, { 203 key: core.PodKey(namespace0, pName0), 204 setType: core.MetricSetTypePod, 205 seed: seedPod0, 206 network: true, 207 }, { 208 key: core.PodKey(namespace0, pName1), 209 setType: core.MetricSetTypePod, 210 seed: seedPod1, 211 network: true, 212 fs: []string{"Volume:A", "Volume:B"}, 213 }, { 214 key: core.PodKey(namespace1, pName2), 215 setType: core.MetricSetTypePod, 216 seed: seedPod2, 217 network: true, 218 }, { 219 key: core.PodContainerKey(namespace0, pName0, cName00), 220 setType: core.MetricSetTypePodContainer, 221 seed: seedPod0Container0, 222 cpu: true, 223 memory: true, 224 fs: containerFs, 225 }, { 226 key: core.PodContainerKey(namespace0, pName0, cName01), 227 setType: core.MetricSetTypePodContainer, 228 seed: seedPod0Container1, 229 cpu: true, 230 memory: true, 231 fs: containerFs, 232 }, { 233 key: core.PodContainerKey(namespace0, pName1, cName10), 234 setType: core.MetricSetTypePodContainer, 235 seed: seedPod1Container, 236 cpu: true, 237 memory: true, 238 fs: containerFs, 239 }, { 240 key: core.PodContainerKey(namespace1, pName2, cName20), 241 setType: core.MetricSetTypePodContainer, 242 seed: seedPod2Container, 243 cpu: true, 244 memory: true, 245 fs: containerFs, 246 }} 247 248 metrics := ms.decodeSummary(&summary) 249 for _, e := range expectations { 250 m, ok := metrics[e.key] 251 if !assert.True(t, ok, "missing metric %q", e.key) { 252 continue 253 } 254 assert.Equal(t, m.Labels[core.LabelMetricSetType.Key], e.setType, e.key) 255 assert.Equal(t, m.CreateTime, startTime, e.key) 256 assert.Equal(t, m.ScrapeTime, scrapeTime, e.key) 257 if e.cpu { 258 checkIntMetric(t, m, e.key, core.MetricCpuUsage, e.seed+offsetCPUUsageCoreSeconds) 259 } 260 if e.memory { 261 checkIntMetric(t, m, e.key, core.MetricMemoryUsage, e.seed+offsetMemUsageBytes) 262 checkIntMetric(t, m, e.key, core.MetricMemoryWorkingSet, e.seed+offsetMemWorkingSetBytes) 263 checkIntMetric(t, m, e.key, core.MetricMemoryPageFaults, e.seed+offsetMemPageFaults) 264 checkIntMetric(t, m, e.key, core.MetricMemoryMajorPageFaults, e.seed+offsetMemMajorPageFaults) 265 } 266 if e.network { 267 checkIntMetric(t, m, e.key, core.MetricNetworkRx, e.seed+offsetNetRxBytes) 268 checkIntMetric(t, m, e.key, core.MetricNetworkRxErrors, e.seed+offsetNetRxErrors) 269 checkIntMetric(t, m, e.key, core.MetricNetworkTx, e.seed+offsetNetTxBytes) 270 checkIntMetric(t, m, e.key, core.MetricNetworkTxErrors, e.seed+offsetNetTxErrors) 271 } 272 for _, label := range e.fs { 273 checkFsMetric(t, m, e.key, label, core.MetricFilesystemAvailable, e.seed+offsetFsAvailable) 274 checkFsMetric(t, m, e.key, label, core.MetricFilesystemLimit, e.seed+offsetFsCapacity) 275 checkFsMetric(t, m, e.key, label, core.MetricFilesystemUsage, e.seed+offsetFsUsed) 276 } 277 delete(metrics, e.key) 278 } 279 280 for k, v := range metrics { 281 assert.Fail(t, "unexpected metric", "%q: %+v", k, v) 282 } 283 } 284 285 func genTestSummaryContainer(name string, seed int) stats.ContainerStats { 286 return stats.ContainerStats{ 287 Name: name, 288 StartTime: unversioned.NewTime(startTime), 289 CPU: genTestSummaryCPU(seed), 290 Memory: genTestSummaryMemory(seed), 291 Rootfs: genTestSummaryFsStats(seed), 292 Logs: genTestSummaryFsStats(seed), 293 } 294 } 295 296 func genTestSummaryCPU(seed int) *stats.CPUStats { 297 cpu := stats.CPUStats{ 298 Time: unversioned.NewTime(scrapeTime), 299 UsageNanoCores: uint64Val(seed, offsetCPUUsageCores), 300 UsageCoreNanoSeconds: uint64Val(seed, offsetCPUUsageCoreSeconds), 301 } 302 *cpu.UsageNanoCores *= uint64(time.Millisecond.Nanoseconds()) 303 return &cpu 304 } 305 306 func genTestSummaryMemory(seed int) *stats.MemoryStats { 307 return &stats.MemoryStats{ 308 Time: unversioned.NewTime(scrapeTime), 309 UsageBytes: uint64Val(seed, offsetMemUsageBytes), 310 WorkingSetBytes: uint64Val(seed, offsetMemWorkingSetBytes), 311 PageFaults: uint64Val(seed, offsetMemPageFaults), 312 MajorPageFaults: uint64Val(seed, offsetMemMajorPageFaults), 313 } 314 } 315 316 func genTestSummaryNetwork(seed int) *stats.NetworkStats { 317 return &stats.NetworkStats{ 318 Time: unversioned.NewTime(scrapeTime), 319 RxBytes: uint64Val(seed, offsetNetRxBytes), 320 RxErrors: uint64Val(seed, offsetNetRxErrors), 321 TxBytes: uint64Val(seed, offsetNetTxBytes), 322 TxErrors: uint64Val(seed, offsetNetTxErrors), 323 } 324 } 325 326 func genTestSummaryFsStats(seed int) *stats.FsStats { 327 return &stats.FsStats{ 328 AvailableBytes: uint64Val(seed, offsetFsAvailable), 329 CapacityBytes: uint64Val(seed, offsetFsCapacity), 330 UsedBytes: uint64Val(seed, offsetFsUsed), 331 } 332 } 333 334 // Convenience function for taking the address of a uint64 literal. 335 func uint64Val(seed, offset int) *uint64 { 336 val := uint64(seed + offset) 337 return &val 338 } 339 340 func checkIntMetric(t *testing.T, metrics *core.MetricSet, key string, metric core.Metric, value int) { 341 m, ok := metrics.MetricValues[metric.Name] 342 if !assert.True(t, ok, "missing %q:%q", key, metric.Name) { 343 return 344 } 345 assert.Equal(t, value, m.IntValue, "%q:%q", key, metric.Name) 346 } 347 348 func checkFsMetric(t *testing.T, metrics *core.MetricSet, key, label string, metric core.Metric, value int) { 349 for _, m := range metrics.LabeledMetrics { 350 if m.Name == metric.Name && m.Labels[core.LabelResourceID.Key] == label { 351 assert.Equal(t, value, m.IntValue, "%q:%q[%s]", key, metric.Name, label) 352 return 353 } 354 } 355 assert.Fail(t, "missing filesystem metric", "%q:[%q]:%q", key, metric.Name, label) 356 } 357 358 func TestScrapeSummaryMetrics(t *testing.T) { 359 summary := stats.Summary{ 360 Node: stats.NodeStats{ 361 NodeName: nodeInfo.NodeName, 362 StartTime: unversioned.NewTime(startTime), 363 }, 364 } 365 data, err := json.Marshal(&summary) 366 require.NoError(t, err) 367 368 server := httptest.NewServer(&util.FakeHandler{ 369 StatusCode: 200, 370 ResponseBody: string(data), 371 T: t, 372 }) 373 defer server.Close() 374 375 ms := testingSummaryMetricsSource() 376 split := strings.SplitN(strings.Replace(server.URL, "http://", "", 1), ":", 2) 377 ms.node.IP = split[0] 378 ms.node.Port, err = strconv.Atoi(split[1]) 379 require.NoError(t, err) 380 381 res := ms.ScrapeMetrics(time.Now(), time.Now()) 382 assert.Equal(t, res.MetricSets["node:test"].Labels[core.LabelMetricSetType.Key], core.MetricSetTypeNode) 383 } 384 385 func TestFallback(t *testing.T) { 386 server := httptest.NewServer(&util.FakeHandler{ 387 StatusCode: 404, 388 T: t, 389 }) 390 defer server.Close() 391 392 ms := testingSummaryMetricsSource() 393 split := strings.SplitN(strings.Replace(server.URL, "http://", "", 1), ":", 2) 394 ms.node.IP = split[0] 395 var err error 396 ms.node.Port, err = strconv.Atoi(split[1]) 397 require.NoError(t, err) 398 fallback := ms.fallback.(*fakeSource) 399 400 ms.ScrapeMetrics(time.Now(), time.Now()) 401 assert.True(t, fallback.scraped) 402 assert.True(t, ms.useFallback) 403 404 server.Close() // Second request should not hit the server. 405 fallback.scraped = false // reset 406 ms.ScrapeMetrics(time.Now(), time.Now()) 407 assert.True(t, fallback.scraped) 408 } 409 410 func TestSummarySupported(t *testing.T) { 411 tests := []struct { 412 version string 413 expectFallback bool 414 }{ 415 {"v1.2.0-alpha.8", false}, 416 {"v1.2.0", false}, 417 {"v1.2.0-alpha.6", true}, 418 {"v1.3.0-alpha.1", false}, 419 {"v1.1.8", true}, 420 {"v1.0.6", true}, 421 {"v-invalid", true}, 422 } 423 424 for _, test := range tests { 425 node := nodeInfo 426 node.KubeletVersion = test.version 427 source := NewSummaryMetricsSource(node, nil, nil).(*summaryMetricsSource) 428 assert.Equal(t, test.expectFallback, source.useFallback, test.version) 429 } 430 }