From a12bd878e0a33576e9692b81546aef93a95208a1 Mon Sep 17 00:00:00 2001 From: Jonathan Chauncey Date: Fri, 16 Sep 2016 13:31:41 -0400 Subject: [PATCH] feat(kubernetes): Add kubernetes input plugin closes #1774 --- CHANGELOG.md | 1 + plugins/inputs/all/all.go | 1 + plugins/inputs/kubernetes/README.md | 265 ++++++++++++++++ plugins/inputs/kubernetes/kubernetes.go | 244 +++++++++++++++ .../inputs/kubernetes/kubernetes_metrics.go | 93 ++++++ plugins/inputs/kubernetes/kubernetes_test.go | 289 ++++++++++++++++++ 6 files changed, 893 insertions(+) create mode 100644 plugins/inputs/kubernetes/README.md create mode 100644 plugins/inputs/kubernetes/kubernetes.go create mode 100644 plugins/inputs/kubernetes/kubernetes_metrics.go create mode 100644 plugins/inputs/kubernetes/kubernetes_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d13ce2ca..cd3b840cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ continue sending logs to /var/log/telegraf/telegraf.log. - [#1686](https://github.com/influxdata/telegraf/pull/1686): Mesos input plugin: very high-cardinality mesos-task metrics removed. - [#1838](https://github.com/influxdata/telegraf/pull/1838): Logging overhaul to centralize the logger & log levels, & provide a logfile config option. - [#1700](https://github.com/influxdata/telegraf/pull/1700): HAProxy plugin socket glob matching. +- [#1847](https://github.com/influxdata/telegraf/pull/1847): Add Kubernetes plugin for retrieving pod metrics. ### Bugfixes diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index 058b230d8..67b85905e 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -31,6 +31,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/iptables" _ "github.com/influxdata/telegraf/plugins/inputs/jolokia" _ "github.com/influxdata/telegraf/plugins/inputs/kafka_consumer" + _ "github.com/influxdata/telegraf/plugins/inputs/kubernetes" _ "github.com/influxdata/telegraf/plugins/inputs/leofs" _ "github.com/influxdata/telegraf/plugins/inputs/logparser" _ "github.com/influxdata/telegraf/plugins/inputs/lustre2" diff --git a/plugins/inputs/kubernetes/README.md b/plugins/inputs/kubernetes/README.md new file mode 100644 index 000000000..099cf1526 --- /dev/null +++ b/plugins/inputs/kubernetes/README.md @@ -0,0 +1,265 @@ +# Kubernetes Input Plugin + +**This plugin is experimental and may cause high cardinality issues with moderate to large Kubernetes deployments** + +This input plugin talks to the kubelet api using the `/stats/summary` endpoint to gather metrics about the running pods and containers for a single host. It is assumed that this plugin is running as part of a `daemonset` within a kubernetes installation. This means that telegraf is running on every node within the cluster. Therefore, you should configure this plugin to talk to its locally running kubelet. + +To find the ip address of the host you are running on you can issue a command like the following: +``` +$ curl -s $API_URL/api/v1/namespaces/$POD_NAMESPACE/pods/$HOSTNAME --header "Authorization: Bearer $TOKEN" --insecure | jq -r '.status.hostIP' +``` +In this case we used the downward API to pass in the `$POD_NAMESPACE` and `$HOSTNAME` is the hostname of the pod which is set by the kubernetes API. + +## Summary Data + +```json +{ + "node": { + "nodeName": "node1", + "systemContainers": [ + { + "name": "kubelet", + "startTime": "2016-08-25T18:46:52Z", + "cpu": { + "time": "2016-09-27T16:57:31Z", + "usageNanoCores": 56652446, + "usageCoreNanoSeconds": 101437561712262 + }, + "memory": { + "time": "2016-09-27T16:57:31Z", + "usageBytes": 62529536, + "workingSetBytes": 62349312, + "rssBytes": 47509504, + "pageFaults": 4769397409, + "majorPageFaults": 13 + }, + "rootfs": { + "availableBytes": 84379979776, + "capacityBytes": 105553100800 + }, + "logs": { + "availableBytes": 84379979776, + "capacityBytes": 105553100800 + }, + "userDefinedMetrics": null + }, + { + "name": "bar", + "startTime": "2016-08-25T18:46:52Z", + "cpu": { + "time": "2016-09-27T16:57:31Z", + "usageNanoCores": 56652446, + "usageCoreNanoSeconds": 101437561712262 + }, + "memory": { + "time": "2016-09-27T16:57:31Z", + "usageBytes": 62529536, + "workingSetBytes": 62349312, + "rssBytes": 47509504, + "pageFaults": 4769397409, + "majorPageFaults": 13 + }, + "rootfs": { + "availableBytes": 84379979776, + "capacityBytes": 105553100800 + }, + "logs": { + "availableBytes": 84379979776, + "capacityBytes": 105553100800 + }, + "userDefinedMetrics": null + } + ], + "startTime": "2016-08-25T18:46:52Z", + "cpu": { + "time": "2016-09-27T16:57:41Z", + "usageNanoCores": 576996212, + "usageCoreNanoSeconds": 774129887054161 + }, + "memory": { + "time": "2016-09-27T16:57:41Z", + "availableBytes": 10726387712, + "usageBytes": 12313182208, + "workingSetBytes": 5081538560, + "rssBytes": 35586048, + "pageFaults": 351742, + "majorPageFaults": 1236 + }, + "network": { + "time": "2016-09-27T16:57:41Z", + "rxBytes": 213281337459, + "rxErrors": 0, + "txBytes": 292869995684, + "txErrors": 0 + }, + "fs": { + "availableBytes": 84379979776, + "capacityBytes": 105553100800, + "usedBytes": 16754286592 + }, + "runtime": { + "imageFs": { + "availableBytes": 84379979776, + "capacityBytes": 105553100800, + "usedBytes": 5809371475 + } + } + }, + "pods": [ + { + "podRef": { + "name": "foopod", + "namespace": "foons", + "uid": "6d305b06-8419-11e6-825c-42010af000ae" + }, + "startTime": "2016-09-26T18:45:42Z", + "containers": [ + { + "name": "foocontainer", + "startTime": "2016-09-26T18:46:43Z", + "cpu": { + "time": "2016-09-27T16:57:32Z", + "usageNanoCores": 846503, + "usageCoreNanoSeconds": 56507553554 + }, + "memory": { + "time": "2016-09-27T16:57:32Z", + "usageBytes": 30789632, + "workingSetBytes": 30789632, + "rssBytes": 30695424, + "pageFaults": 10761, + "majorPageFaults": 0 + }, + "rootfs": { + "availableBytes": 84379979776, + "capacityBytes": 105553100800, + "usedBytes": 57344 + }, + "logs": { + "availableBytes": 84379979776, + "capacityBytes": 105553100800, + "usedBytes": 24576 + }, + "userDefinedMetrics": null + } + ], + "network": { + "time": "2016-09-27T16:57:34Z", + "rxBytes": 70749124, + "rxErrors": 0, + "txBytes": 47813506, + "txErrors": 0 + }, + "volume": [ + { + "availableBytes": 7903948800, + "capacityBytes": 7903961088, + "usedBytes": 12288, + "name": "volume1" + }, + { + "availableBytes": 7903956992, + "capacityBytes": 7903961088, + "usedBytes": 4096, + "name": "volume2" + }, + { + "availableBytes": 7903948800, + "capacityBytes": 7903961088, + "usedBytes": 12288, + "name": "volume3" + }, + { + "availableBytes": 7903952896, + "capacityBytes": 7903961088, + "usedBytes": 8192, + "name": "volume4" + } + ] + } + ] + } + ``` + + ### Daemonset YAML + +```yaml +apiVersion: extensions/v1beta1 +kind: DaemonSet +metadata: + name: telegraf + namespace: telegraf +spec: + template: + metadata: + labels: + app: telegraf + spec: + serviceAccount: telegraf + containers: + - name: telegraf + image: quay.io/org/image:latest + imagePullPolicy: IfNotPresent + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: "HOST_PROC" + value: "/rootfs/proc" + - name: "HOST_SYS" + value: "/rootfs/sys" + volumeMounts: + - name: sysro + mountPath: /rootfs/sys + readOnly: true + - name: procro + mountPath: /rootfs/proc + readOnly: true + - name: varrunutmpro + mountPath: /var/run/utmp + readOnly: true + - name: logger-redis-creds + mountPath: /var/run/secrets/deis/redis/creds + volumes: + - name: sysro + hostPath: + path: /sys + - name: procro + hostPath: + path: /proc + - name: varrunutmpro + hostPath: + path: /var/run/utmp +``` + +### Line Protocol + +#### kubernetes_pod_container +``` +kubernetes_pod_container,host=ip-10-0-0-0.ec2.internal, +container_name=deis-controller,namespace=deis, +node_name=ip-10-0-0-0.ec2.internal, pod_name=deis-controller-3058870187-xazsr, cpu_usage_core_nanoseconds=2432835i,cpu_usage_nanocores=0i, +logsfs_avaialble_bytes=121128271872i,logsfs_capacity_bytes=153567944704i, +logsfs_used_bytes=20787200i,memory_major_page_faults=0i, +memory_page_faults=175i,memory_rss_bytes=0i, +memory_usage_bytes=0i,memory_working_set_bytes=0i, +rootfs_available_bytes=121128271872i,rootfs_capacity_bytes=153567944704i, +rootfs_used_bytes=1110016i 1476477530000000000 + ``` + +#### kubernetes_pod_volume +``` +kubernetes_pod_volume,host=ip-10-0-0-0.ec2.internal,name=default-token-f7wts, +namespace=kube-system,node_name=ip-10-0-0-0.ec2.internal, +pod_name=kubernetes-dashboard-v1.1.1-t4x4t, available_bytes=8415240192i, +capacity_bytes=8415252480i,used_bytes=12288i 1476477530000000000 +``` + +#### kubernetes_pod_network +``` +kubernetes_pod_network,host=ip-10-0-0-0.ec2.internal,namespace=deis, +node_name=ip-10-0-0-0.ec2.internal,pod_name=deis-controller-3058870187-xazsr, +rx_bytes=120671099i,rx_errors=0i, +tx_bytes=102451983i,tx_errors=0i 1476477530000000000 +``` diff --git a/plugins/inputs/kubernetes/kubernetes.go b/plugins/inputs/kubernetes/kubernetes.go new file mode 100644 index 000000000..6b488a7d1 --- /dev/null +++ b/plugins/inputs/kubernetes/kubernetes.go @@ -0,0 +1,244 @@ +package kubernetes + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net" + "net/http" + "net/url" + "sync" + "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/internal" + "github.com/influxdata/telegraf/internal/errchan" + "github.com/influxdata/telegraf/plugins/inputs" +) + +// Kubernetes represents the config object for the plugin +type Kubernetes struct { + URL string + + // Bearer Token authorization file path + BearerToken string `toml:"bearer_token"` + + // Path to CA file + SSLCA string `toml:"ssl_ca"` + // Path to host cert file + SSLCert string `toml:"ssl_cert"` + // Path to cert key file + SSLKey string `toml:"ssl_key"` + // Use SSL but skip chain & host verification + InsecureSkipVerify bool +} + +var sampleConfig = ` + ## URL for the kubelet + url = "http://1.1.1.1:10255" + + ## Use bearer token for authorization + # bearer_token = /path/to/bearer/token + + ## Optional SSL Config + # ssl_ca = /path/to/cafile + # ssl_cert = /path/to/certfile + # ssl_key = /path/to/keyfile + ## Use SSL but skip chain & host verification + # insecure_skip_verify = false +` + +const ( + summaryEndpoint = `%s/stats/summary` +) + +func init() { + inputs.Add("kubernetes", func() telegraf.Input { + return &Kubernetes{} + }) +} + +//SampleConfig returns a sample config +func (k *Kubernetes) SampleConfig() string { + return sampleConfig +} + +//Description returns the description of this plugin +func (k *Kubernetes) Description() string { + return "Read metrics from the kubernetes kubelet api" +} + +//Gather collects kubernetes metrics from a given URL +func (k *Kubernetes) Gather(acc telegraf.Accumulator) error { + var wg sync.WaitGroup + errChan := errchan.New(1) + wg.Add(1) + go func(k *Kubernetes) { + defer wg.Done() + errChan.C <- k.gatherSummary(k.URL, acc) + }(k) + wg.Wait() + return errChan.Error() +} + +func buildURL(endpoint string, base string) (*url.URL, error) { + u := fmt.Sprintf(endpoint, base) + addr, err := url.Parse(u) + if err != nil { + return nil, fmt.Errorf("Unable to parse address '%s': %s", u, err) + } + return addr, nil +} + +func (k *Kubernetes) gatherSummary(baseURL string, acc telegraf.Accumulator) error { + url := fmt.Sprintf("%s/stats/summary", baseURL) + var req, err = http.NewRequest("GET", url, nil) + var token []byte + var resp *http.Response + + tlsCfg, err := internal.GetTLSConfig(k.SSLCert, k.SSLKey, k.SSLCA, k.InsecureSkipVerify) + if err != nil { + return err + } + + var rt http.RoundTripper = &http.Transport{ + Dial: (&net.Dialer{ + Timeout: 5 * time.Second, + KeepAlive: 30 * time.Second, + }).Dial, + TLSHandshakeTimeout: 5 * time.Second, + TLSClientConfig: tlsCfg, + ResponseHeaderTimeout: time.Duration(3 * time.Second), + DisableKeepAlives: false, + } + + if k.BearerToken != "" { + token, err = ioutil.ReadFile(k.BearerToken) + if err != nil { + return err + } + req.Header.Set("Authorization", "Bearer "+string(token)) + } + + resp, err = rt.RoundTrip(req) + if err != nil { + return fmt.Errorf("error making HTTP request to %s: %s", url, err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("%s returned HTTP status %s", url, resp.Status) + } + + summaryMetrics := &SummaryMetrics{} + err = json.NewDecoder(resp.Body).Decode(summaryMetrics) + if err != nil { + return fmt.Errorf(`Error parsing response: %s`, err) + } + buildSystemContainerMetrics(summaryMetrics, acc) + buildNodeMetrics(summaryMetrics, acc) + buildPodMetrics(summaryMetrics, acc) + return nil +} + +func buildSystemContainerMetrics(summaryMetrics *SummaryMetrics, acc telegraf.Accumulator) { + for _, container := range summaryMetrics.Node.SystemContainers { + tags := map[string]string{ + "node_name": summaryMetrics.Node.NodeName, + "container_name": container.Name, + } + fields := make(map[string]interface{}) + fields["cpu_usage_nanocores"] = container.CPU.UsageNanoCores + fields["cpu_usage_core_nanoseconds"] = container.CPU.UsageCoreNanoSeconds + fields["memory_usage_bytes"] = container.Memory.UsageBytes + fields["memory_working_set_bytes"] = container.Memory.WorkingSetBytes + fields["memory_rss_bytes"] = container.Memory.RSSBytes + fields["memory_page_faults"] = container.Memory.PageFaults + fields["memory_major_page_faults"] = container.Memory.MajorPageFaults + fields["rootfs_available_bytes"] = container.RootFS.AvailableBytes + fields["rootfs_capacity_bytes"] = container.RootFS.CapacityBytes + fields["logsfs_avaialble_bytes"] = container.LogsFS.AvailableBytes + fields["logsfs_capacity_bytes"] = container.LogsFS.CapacityBytes + acc.AddFields("kubernetes_system_container", fields, tags) + } +} + +func buildNodeMetrics(summaryMetrics *SummaryMetrics, acc telegraf.Accumulator) { + tags := map[string]string{ + "node_name": summaryMetrics.Node.NodeName, + } + fields := make(map[string]interface{}) + fields["cpu_usage_nanocores"] = summaryMetrics.Node.CPU.UsageNanoCores + fields["cpu_usage_core_nanoseconds"] = summaryMetrics.Node.CPU.UsageCoreNanoSeconds + fields["memory_available_bytes"] = summaryMetrics.Node.Memory.AvailableBytes + fields["memory_usage_bytes"] = summaryMetrics.Node.Memory.UsageBytes + fields["memory_working_set_bytes"] = summaryMetrics.Node.Memory.WorkingSetBytes + fields["memory_rss_bytes"] = summaryMetrics.Node.Memory.RSSBytes + fields["memory_page_faults"] = summaryMetrics.Node.Memory.PageFaults + fields["memory_major_page_faults"] = summaryMetrics.Node.Memory.MajorPageFaults + fields["network_rx_bytes"] = summaryMetrics.Node.Network.RXBytes + fields["network_rx_errors"] = summaryMetrics.Node.Network.RXErrors + fields["network_tx_bytes"] = summaryMetrics.Node.Network.TXBytes + fields["network_tx_errors"] = summaryMetrics.Node.Network.TXErrors + fields["fs_available_bytes"] = summaryMetrics.Node.FileSystem.AvailableBytes + fields["fs_capacity_bytes"] = summaryMetrics.Node.FileSystem.CapacityBytes + fields["fs_used_bytes"] = summaryMetrics.Node.FileSystem.UsedBytes + fields["runtime_image_fs_available_bytes"] = summaryMetrics.Node.Runtime.ImageFileSystem.AvailableBytes + fields["runtime_image_fs_capacity_bytes"] = summaryMetrics.Node.Runtime.ImageFileSystem.CapacityBytes + fields["runtime_image_fs_used_bytes"] = summaryMetrics.Node.Runtime.ImageFileSystem.UsedBytes + acc.AddFields("kubernetes_node", fields, tags) +} + +func buildPodMetrics(summaryMetrics *SummaryMetrics, acc telegraf.Accumulator) { + for _, pod := range summaryMetrics.Pods { + for _, container := range pod.Containers { + tags := map[string]string{ + "node_name": summaryMetrics.Node.NodeName, + "namespace": pod.PodRef.Namespace, + "container_name": container.Name, + "pod_name": pod.PodRef.Name, + } + fields := make(map[string]interface{}) + fields["cpu_usage_nanocores"] = container.CPU.UsageNanoCores + fields["cpu_usage_core_nanoseconds"] = container.CPU.UsageCoreNanoSeconds + fields["memory_usage_bytes"] = container.Memory.UsageBytes + fields["memory_working_set_bytes"] = container.Memory.WorkingSetBytes + fields["memory_rss_bytes"] = container.Memory.RSSBytes + fields["memory_page_faults"] = container.Memory.PageFaults + fields["memory_major_page_faults"] = container.Memory.MajorPageFaults + fields["rootfs_available_bytes"] = container.RootFS.AvailableBytes + fields["rootfs_capacity_bytes"] = container.RootFS.CapacityBytes + fields["rootfs_used_bytes"] = container.RootFS.UsedBytes + fields["logsfs_avaialble_bytes"] = container.LogsFS.AvailableBytes + fields["logsfs_capacity_bytes"] = container.LogsFS.CapacityBytes + fields["logsfs_used_bytes"] = container.LogsFS.UsedBytes + acc.AddFields("kubernetes_pod_container", fields, tags) + } + + for _, volume := range pod.Volumes { + tags := map[string]string{ + "node_name": summaryMetrics.Node.NodeName, + "pod_name": pod.PodRef.Name, + "namespace": pod.PodRef.Namespace, + "volume_name": volume.Name, + } + fields := make(map[string]interface{}) + fields["available_bytes"] = volume.AvailableBytes + fields["capacity_bytes"] = volume.CapacityBytes + fields["used_bytes"] = volume.UsedBytes + acc.AddFields("kubernetes_pod_volume", fields, tags) + } + + tags := map[string]string{ + "node_name": summaryMetrics.Node.NodeName, + "pod_name": pod.PodRef.Name, + "namespace": pod.PodRef.Namespace, + } + fields := make(map[string]interface{}) + fields["rx_bytes"] = pod.Network.RXBytes + fields["rx_errors"] = pod.Network.RXErrors + fields["tx_bytes"] = pod.Network.TXBytes + fields["tx_errors"] = pod.Network.TXErrors + acc.AddFields("kubernetes_pod_network", fields, tags) + } +} diff --git a/plugins/inputs/kubernetes/kubernetes_metrics.go b/plugins/inputs/kubernetes/kubernetes_metrics.go new file mode 100644 index 000000000..a767a604a --- /dev/null +++ b/plugins/inputs/kubernetes/kubernetes_metrics.go @@ -0,0 +1,93 @@ +package kubernetes + +import "time" + +// SummaryMetrics represents all the summary data about a paritcular node retrieved from a kubelet +type SummaryMetrics struct { + Node NodeMetrics `json:"node"` + Pods []PodMetrics `json:"pods"` +} + +// NodeMetrics represents detailed information about a node +type NodeMetrics struct { + NodeName string `json:"nodeName"` + SystemContainers []ContainerMetrics `json:"systemContainers"` + StartTime time.Time `json:"startTime"` + CPU CPUMetrics `json:"cpu"` + Memory MemoryMetrics `json:"memory"` + Network NetworkMetrics `json:"network"` + FileSystem FileSystemMetrics `json:"fs"` + Runtime RuntimeMetrics `json:"runtime"` +} + +// ContainerMetrics represents the metric data collect about a container from the kubelet +type ContainerMetrics struct { + Name string `json:"name"` + StartTime time.Time `json:"startTime"` + CPU CPUMetrics `json:"cpu"` + Memory MemoryMetrics `json:"memory"` + RootFS FileSystemMetrics `json:"rootfs"` + LogsFS FileSystemMetrics `json:"logs"` +} + +// RuntimeMetrics contains metric data on the runtime of the system +type RuntimeMetrics struct { + ImageFileSystem FileSystemMetrics `json:"imageFs"` +} + +// CPUMetrics represents the cpu usage data of a pod or node +type CPUMetrics struct { + Time time.Time `json:"time"` + UsageNanoCores int64 `json:"usageNanoCores"` + UsageCoreNanoSeconds int64 `json:"usageCoreNanoSeconds"` +} + +// PodMetrics contains metric data on a given pod +type PodMetrics struct { + PodRef PodReference `json:"podRef"` + StartTime time.Time `json:"startTime"` + Containers []ContainerMetrics `json:"containers"` + Network NetworkMetrics `json:"network"` + Volumes []VolumeMetrics `json:"volume"` +} + +// PodReference is how a pod is identified +type PodReference struct { + Name string `json:"name"` + Namespace string `json:"namespace"` +} + +// MemoryMetrics represents the memory metrics for a pod or node +type MemoryMetrics struct { + Time time.Time `json:"time"` + AvailableBytes int64 `json:"availableBytes"` + UsageBytes int64 `json:"usageBytes"` + WorkingSetBytes int64 `json:"workingSetBytes"` + RSSBytes int64 `json:"rssBytes"` + PageFaults int64 `json:"pageFaults"` + MajorPageFaults int64 `json:"majorPageFaults"` +} + +// FileSystemMetrics represents disk usage metrics for a pod or node +type FileSystemMetrics struct { + AvailableBytes int64 `json:"availableBytes"` + CapacityBytes int64 `json:"capacityBytes"` + UsedBytes int64 `json:"usedBytes"` +} + +// NetworkMetrics represents network usage data for a pod or node +type NetworkMetrics struct { + Time time.Time `json:"time"` + RXBytes int64 `json:"rxBytes"` + RXErrors int64 `json:"rxErrors"` + TXBytes int64 `json:"txBytes"` + TXErrors int64 `json:"txErrors"` +} + +// VolumeMetrics represents the disk usage data for a given volume +type VolumeMetrics struct { + Name string `json:"name"` + AvailableBytes int64 `json:"availableBytes"` + CapacityBytes int64 `json:"capacityBytes"` + UsedBytes int64 `json:"usedBytes"` +} diff --git a/plugins/inputs/kubernetes/kubernetes_test.go b/plugins/inputs/kubernetes/kubernetes_test.go new file mode 100644 index 000000000..14134c150 --- /dev/null +++ b/plugins/inputs/kubernetes/kubernetes_test.go @@ -0,0 +1,289 @@ +package kubernetes + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/influxdata/telegraf/testutil" + "github.com/stretchr/testify/require" +) + +func TestKubernetesStats(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + fmt.Fprintln(w, response) + })) + defer ts.Close() + + k := &Kubernetes{ + URL: ts.URL, + } + + var acc testutil.Accumulator + err := k.Gather(&acc) + require.NoError(t, err) + + fields := map[string]interface{}{ + "cpu_usage_nanocores": int64(56652446), + "cpu_usage_core_nanoseconds": int64(101437561712262), + "memory_usage_bytes": int64(62529536), + "memory_working_set_bytes": int64(62349312), + "memory_rss_bytes": int64(47509504), + "memory_page_faults": int64(4769397409), + "memory_major_page_faults": int64(13), + "rootfs_available_bytes": int64(84379979776), + "rootfs_capacity_bytes": int64(105553100800), + "logsfs_avaialble_bytes": int64(84379979776), + "logsfs_capacity_bytes": int64(105553100800), + } + tags := map[string]string{ + "node_name": "node1", + "container_name": "kubelet", + } + acc.AssertContainsTaggedFields(t, "kubernetes_system_container", fields, tags) + + fields = map[string]interface{}{ + "cpu_usage_nanocores": int64(576996212), + "cpu_usage_core_nanoseconds": int64(774129887054161), + "memory_usage_bytes": int64(12313182208), + "memory_working_set_bytes": int64(5081538560), + "memory_rss_bytes": int64(35586048), + "memory_page_faults": int64(351742), + "memory_major_page_faults": int64(1236), + "memory_available_bytes": int64(10726387712), + "network_rx_bytes": int64(213281337459), + "network_rx_errors": int64(0), + "network_tx_bytes": int64(292869995684), + "network_tx_errors": int64(0), + "fs_available_bytes": int64(84379979776), + "fs_capacity_bytes": int64(105553100800), + "fs_used_bytes": int64(16754286592), + "runtime_image_fs_available_bytes": int64(84379979776), + "runtime_image_fs_capacity_bytes": int64(105553100800), + "runtime_image_fs_used_bytes": int64(5809371475), + } + tags = map[string]string{ + "node_name": "node1", + } + acc.AssertContainsTaggedFields(t, "kubernetes_node", fields, tags) + + fields = map[string]interface{}{ + "cpu_usage_nanocores": int64(846503), + "cpu_usage_core_nanoseconds": int64(56507553554), + "memory_usage_bytes": int64(30789632), + "memory_working_set_bytes": int64(30789632), + "memory_rss_bytes": int64(30695424), + "memory_page_faults": int64(10761), + "memory_major_page_faults": int64(0), + "rootfs_available_bytes": int64(84379979776), + "rootfs_capacity_bytes": int64(105553100800), + "rootfs_used_bytes": int64(57344), + "logsfs_avaialble_bytes": int64(84379979776), + "logsfs_capacity_bytes": int64(105553100800), + "logsfs_used_bytes": int64(24576), + } + tags = map[string]string{ + "node_name": "node1", + "container_name": "foocontainer", + "namespace": "foons", + "pod_name": "foopod", + } + acc.AssertContainsTaggedFields(t, "kubernetes_pod_container", fields, tags) + + fields = map[string]interface{}{ + "available_bytes": int64(7903948800), + "capacity_bytes": int64(7903961088), + "used_bytes": int64(12288), + } + tags = map[string]string{ + "node_name": "node1", + "volume_name": "volume1", + "namespace": "foons", + "pod_name": "foopod", + } + acc.AssertContainsTaggedFields(t, "kubernetes_pod_volume", fields, tags) + + fields = map[string]interface{}{ + "rx_bytes": int64(70749124), + "rx_errors": int64(0), + "tx_bytes": int64(47813506), + "tx_errors": int64(0), + } + tags = map[string]string{ + "node_name": "node1", + "namespace": "foons", + "pod_name": "foopod", + } + acc.AssertContainsTaggedFields(t, "kubernetes_pod_network", fields, tags) + +} + +var response = ` +{ + "node": { + "nodeName": "node1", + "systemContainers": [ + { + "name": "kubelet", + "startTime": "2016-08-25T18:46:52Z", + "cpu": { + "time": "2016-09-27T16:57:31Z", + "usageNanoCores": 56652446, + "usageCoreNanoSeconds": 101437561712262 + }, + "memory": { + "time": "2016-09-27T16:57:31Z", + "usageBytes": 62529536, + "workingSetBytes": 62349312, + "rssBytes": 47509504, + "pageFaults": 4769397409, + "majorPageFaults": 13 + }, + "rootfs": { + "availableBytes": 84379979776, + "capacityBytes": 105553100800 + }, + "logs": { + "availableBytes": 84379979776, + "capacityBytes": 105553100800 + }, + "userDefinedMetrics": null + }, + { + "name": "bar", + "startTime": "2016-08-25T18:46:52Z", + "cpu": { + "time": "2016-09-27T16:57:31Z", + "usageNanoCores": 56652446, + "usageCoreNanoSeconds": 101437561712262 + }, + "memory": { + "time": "2016-09-27T16:57:31Z", + "usageBytes": 62529536, + "workingSetBytes": 62349312, + "rssBytes": 47509504, + "pageFaults": 4769397409, + "majorPageFaults": 13 + }, + "rootfs": { + "availableBytes": 84379979776, + "capacityBytes": 105553100800 + }, + "logs": { + "availableBytes": 84379979776, + "capacityBytes": 105553100800 + }, + "userDefinedMetrics": null + } + ], + "startTime": "2016-08-25T18:46:52Z", + "cpu": { + "time": "2016-09-27T16:57:41Z", + "usageNanoCores": 576996212, + "usageCoreNanoSeconds": 774129887054161 + }, + "memory": { + "time": "2016-09-27T16:57:41Z", + "availableBytes": 10726387712, + "usageBytes": 12313182208, + "workingSetBytes": 5081538560, + "rssBytes": 35586048, + "pageFaults": 351742, + "majorPageFaults": 1236 + }, + "network": { + "time": "2016-09-27T16:57:41Z", + "rxBytes": 213281337459, + "rxErrors": 0, + "txBytes": 292869995684, + "txErrors": 0 + }, + "fs": { + "availableBytes": 84379979776, + "capacityBytes": 105553100800, + "usedBytes": 16754286592 + }, + "runtime": { + "imageFs": { + "availableBytes": 84379979776, + "capacityBytes": 105553100800, + "usedBytes": 5809371475 + } + } + }, + "pods": [ + { + "podRef": { + "name": "foopod", + "namespace": "foons", + "uid": "6d305b06-8419-11e6-825c-42010af000ae" + }, + "startTime": "2016-09-26T18:45:42Z", + "containers": [ + { + "name": "foocontainer", + "startTime": "2016-09-26T18:46:43Z", + "cpu": { + "time": "2016-09-27T16:57:32Z", + "usageNanoCores": 846503, + "usageCoreNanoSeconds": 56507553554 + }, + "memory": { + "time": "2016-09-27T16:57:32Z", + "usageBytes": 30789632, + "workingSetBytes": 30789632, + "rssBytes": 30695424, + "pageFaults": 10761, + "majorPageFaults": 0 + }, + "rootfs": { + "availableBytes": 84379979776, + "capacityBytes": 105553100800, + "usedBytes": 57344 + }, + "logs": { + "availableBytes": 84379979776, + "capacityBytes": 105553100800, + "usedBytes": 24576 + }, + "userDefinedMetrics": null + } + ], + "network": { + "time": "2016-09-27T16:57:34Z", + "rxBytes": 70749124, + "rxErrors": 0, + "txBytes": 47813506, + "txErrors": 0 + }, + "volume": [ + { + "availableBytes": 7903948800, + "capacityBytes": 7903961088, + "usedBytes": 12288, + "name": "volume1" + }, + { + "availableBytes": 7903956992, + "capacityBytes": 7903961088, + "usedBytes": 4096, + "name": "volume2" + }, + { + "availableBytes": 7903948800, + "capacityBytes": 7903961088, + "usedBytes": 12288, + "name": "volume3" + }, + { + "availableBytes": 7903952896, + "capacityBytes": 7903961088, + "usedBytes": 8192, + "name": "volume4" + } + ] + } + ] + }`