github.com/jonaz/heapster@v1.3.0-beta.0.0.20170208112634-cd3c15ca3d29/metrics/sources/kubelet/kubelet_client.go (about)

     1  // Copyright 2014 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  // This file implements a cadvisor datasource, that collects metrics from an instance
    16  // of cadvisor running on a specific host.
    17  
    18  package kubelet
    19  
    20  import (
    21  	"bytes"
    22  	"encoding/json"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"net/http"
    26  	"net/url"
    27  	"time"
    28  
    29  	cadvisor "github.com/google/cadvisor/info/v1"
    30  	"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/stats"
    31  	kube_client "k8s.io/kubernetes/pkg/kubelet/client"
    32  )
    33  
    34  type Host struct {
    35  	IP       string
    36  	Port     int
    37  	Resource string
    38  }
    39  
    40  type KubeletClient struct {
    41  	config *kube_client.KubeletClientConfig
    42  	client *http.Client
    43  }
    44  
    45  type ErrNotFound struct {
    46  	endpoint string
    47  }
    48  
    49  func (err *ErrNotFound) Error() string {
    50  	return fmt.Sprintf("%q not found", err.endpoint)
    51  }
    52  
    53  func IsNotFoundError(err error) bool {
    54  	_, isNotFound := err.(*ErrNotFound)
    55  	return isNotFound
    56  }
    57  
    58  func sampleContainerStats(stats []*cadvisor.ContainerStats) []*cadvisor.ContainerStats {
    59  	if len(stats) == 0 {
    60  		return []*cadvisor.ContainerStats{}
    61  	}
    62  	return []*cadvisor.ContainerStats{stats[len(stats)-1]}
    63  }
    64  
    65  func (self *KubeletClient) postRequestAndGetValue(client *http.Client, req *http.Request, value interface{}) error {
    66  	response, err := client.Do(req)
    67  	if err != nil {
    68  		return err
    69  	}
    70  	defer response.Body.Close()
    71  	body, err := ioutil.ReadAll(response.Body)
    72  	if err != nil {
    73  		return fmt.Errorf("failed to read response body - %v", err)
    74  	}
    75  	if response.StatusCode == http.StatusNotFound {
    76  		return &ErrNotFound{req.URL.String()}
    77  	} else if response.StatusCode != http.StatusOK {
    78  		return fmt.Errorf("request failed - %q, response: %q", response.Status, string(body))
    79  	}
    80  	err = json.Unmarshal(body, value)
    81  	if err != nil {
    82  		return fmt.Errorf("failed to parse output. Response: %q. Error: %v", string(body), err)
    83  	}
    84  	return nil
    85  }
    86  
    87  func (self *KubeletClient) parseStat(containerInfo *cadvisor.ContainerInfo) *cadvisor.ContainerInfo {
    88  	containerInfo.Stats = sampleContainerStats(containerInfo.Stats)
    89  	if len(containerInfo.Aliases) > 0 {
    90  		containerInfo.Name = containerInfo.Aliases[0]
    91  	}
    92  	return containerInfo
    93  }
    94  
    95  // TODO(vmarmol): Use Kubernetes' if we export it as an API.
    96  type statsRequest struct {
    97  	// The name of the container for which to request stats.
    98  	// Default: /
    99  	ContainerName string `json:"containerName,omitempty"`
   100  
   101  	// Max number of stats to return.
   102  	// If start and end time are specified this limit is ignored.
   103  	// Default: 60
   104  	NumStats int `json:"num_stats,omitempty"`
   105  
   106  	// Start time for which to query information.
   107  	// If ommitted, the beginning of time is assumed.
   108  	Start time.Time `json:"start,omitempty"`
   109  
   110  	// End time for which to query information.
   111  	// If ommitted, current time is assumed.
   112  	End time.Time `json:"end,omitempty"`
   113  
   114  	// Whether to also include information from subcontainers.
   115  	// Default: false.
   116  	Subcontainers bool `json:"subcontainers,omitempty"`
   117  }
   118  
   119  // Get stats for all non-Kubernetes containers.
   120  func (self *KubeletClient) GetAllRawContainers(host Host, start, end time.Time) ([]cadvisor.ContainerInfo, error) {
   121  	scheme := "http"
   122  	if self.config != nil && self.config.EnableHttps {
   123  		scheme = "https"
   124  	}
   125  
   126  	url := fmt.Sprintf("%s://%s:%d/stats/container/", scheme, host.IP, host.Port)
   127  
   128  	return self.getAllContainers(url, start, end)
   129  }
   130  
   131  func (self *KubeletClient) GetSummary(host Host) (*stats.Summary, error) {
   132  	url := url.URL{
   133  		Scheme: "http",
   134  		Host:   fmt.Sprintf("%s:%d", host.IP, host.Port),
   135  		Path:   "/stats/summary/",
   136  	}
   137  	if self.config != nil && self.config.EnableHttps {
   138  		url.Scheme = "https"
   139  	}
   140  
   141  	req, err := http.NewRequest("GET", url.String(), nil)
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  	summary := &stats.Summary{}
   146  	client := self.client
   147  	if client == nil {
   148  		client = http.DefaultClient
   149  	}
   150  	err = self.postRequestAndGetValue(client, req, summary)
   151  	return summary, err
   152  }
   153  
   154  func (self *KubeletClient) GetPort() int {
   155  	return int(self.config.Port)
   156  }
   157  
   158  func (self *KubeletClient) getAllContainers(url string, start, end time.Time) ([]cadvisor.ContainerInfo, error) {
   159  	// Request data from all subcontainers.
   160  	request := statsRequest{
   161  		ContainerName: "/",
   162  		NumStats:      1,
   163  		Start:         start,
   164  		End:           end,
   165  		Subcontainers: true,
   166  	}
   167  	body, err := json.Marshal(request)
   168  	if err != nil {
   169  		return nil, err
   170  	}
   171  	req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  	req.Header.Set("Content-Type", "application/json")
   176  
   177  	var containers map[string]cadvisor.ContainerInfo
   178  	client := self.client
   179  	if client == nil {
   180  		client = http.DefaultClient
   181  	}
   182  	err = self.postRequestAndGetValue(client, req, &containers)
   183  	if err != nil {
   184  		return nil, fmt.Errorf("failed to get all container stats from Kubelet URL %q: %v", url, err)
   185  	}
   186  
   187  	result := make([]cadvisor.ContainerInfo, 0, len(containers))
   188  	for _, containerInfo := range containers {
   189  		cont := self.parseStat(&containerInfo)
   190  		if cont != nil {
   191  			result = append(result, *cont)
   192  		}
   193  	}
   194  	return result, nil
   195  }
   196  
   197  func NewKubeletClient(kubeletConfig *kube_client.KubeletClientConfig) (*KubeletClient, error) {
   198  	transport, err := kube_client.MakeTransport(kubeletConfig)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  	c := &http.Client{
   203  		Transport: transport,
   204  		Timeout:   kubeletConfig.HTTPTimeout,
   205  	}
   206  	return &KubeletClient{
   207  		config: kubeletConfig,
   208  		client: c,
   209  	}, nil
   210  }