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  }