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 }