github.com/google/cadvisor@v0.49.1/container/crio/client.go (about) 1 // Copyright 2017 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 crio 16 17 import ( 18 "context" 19 "encoding/json" 20 "flag" 21 "fmt" 22 "io" 23 "net" 24 "net/http" 25 "sync" 26 "syscall" 27 "time" 28 ) 29 30 var crioClientTimeout = flag.Duration("crio_client_timeout", time.Duration(0), "CRI-O client timeout. Default is no timeout.") 31 32 const ( 33 CrioSocket = "/var/run/crio/crio.sock" 34 maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path) 35 ) 36 37 var ( 38 theClient CrioClient 39 clientErr error 40 crioClientOnce sync.Once 41 ) 42 43 // Info represents CRI-O information as sent by the CRI-O server 44 type Info struct { 45 StorageDriver string `json:"storage_driver"` 46 StorageRoot string `json:"storage_root"` 47 StorageImage string `json:"storage_image"` 48 } 49 50 // ContainerInfo represents a given container information 51 type ContainerInfo struct { 52 Name string `json:"name"` 53 Pid int `json:"pid"` 54 Image string `json:"image"` 55 CreatedTime int64 `json:"created_time"` 56 Labels map[string]string `json:"labels"` 57 Annotations map[string]string `json:"annotations"` 58 LogPath string `json:"log_path"` 59 Root string `json:"root"` 60 IP string `json:"ip_address"` 61 IPs []string `json:"ip_addresses"` 62 } 63 64 type CrioClient interface { 65 Info() (Info, error) 66 ContainerInfo(string) (*ContainerInfo, error) 67 } 68 69 type crioClientImpl struct { 70 client *http.Client 71 } 72 73 func configureUnixTransport(tr *http.Transport, proto, addr string) error { 74 if len(addr) > maxUnixSocketPathSize { 75 return fmt.Errorf("Unix socket path %q is too long", addr) 76 } 77 // No need for compression in local communications. 78 tr.DisableCompression = true 79 tr.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) { 80 return net.DialTimeout(proto, addr, 32*time.Second) 81 } 82 return nil 83 } 84 85 // Client returns a new configured CRI-O client 86 func Client() (CrioClient, error) { 87 crioClientOnce.Do(func() { 88 tr := new(http.Transport) 89 theClient = nil 90 if clientErr = configureUnixTransport(tr, "unix", CrioSocket); clientErr != nil { 91 return 92 } 93 theClient = &crioClientImpl{ 94 client: &http.Client{ 95 Transport: tr, 96 Timeout: *crioClientTimeout, 97 }, 98 } 99 }) 100 return theClient, clientErr 101 } 102 103 func getRequest(path string) (*http.Request, error) { 104 req, err := http.NewRequest("GET", path, nil) 105 if err != nil { 106 return nil, err 107 } 108 // For local communications over a unix socket, it doesn't matter what 109 // the host is. We just need a valid and meaningful host name. 110 req.Host = "crio" 111 req.URL.Host = CrioSocket 112 req.URL.Scheme = "http" 113 return req, nil 114 } 115 116 // Info returns generic info from the CRI-O server 117 func (c *crioClientImpl) Info() (Info, error) { 118 info := Info{} 119 req, err := getRequest("/info") 120 if err != nil { 121 return info, err 122 } 123 resp, err := c.client.Do(req) 124 if err != nil { 125 return info, err 126 } 127 defer resp.Body.Close() 128 if err := json.NewDecoder(resp.Body).Decode(&info); err != nil { 129 return info, err 130 } 131 return info, nil 132 } 133 134 // ContainerInfo returns information about a given container 135 func (c *crioClientImpl) ContainerInfo(id string) (*ContainerInfo, error) { 136 req, err := getRequest("/containers/" + id) 137 if err != nil { 138 return nil, err 139 } 140 cInfo := ContainerInfo{} 141 resp, err := c.client.Do(req) 142 if err != nil { 143 return nil, err 144 } 145 defer resp.Body.Close() 146 147 // golang's http.Do doesn't return an error if non 200 response code is returned 148 // handle this case here, rather than failing to decode the body 149 if resp.StatusCode != http.StatusOK { 150 respBody, err := io.ReadAll(resp.Body) 151 if err != nil { 152 return nil, fmt.Errorf("Error finding container %s: Status %d", id, resp.StatusCode) 153 } 154 return nil, fmt.Errorf("Error finding container %s: Status %d returned error %s", id, resp.StatusCode, string(respBody)) 155 } 156 157 if err := json.NewDecoder(resp.Body).Decode(&cInfo); err != nil { 158 return nil, err 159 } 160 if len(cInfo.IP) > 0 { 161 return &cInfo, nil 162 } 163 if len(cInfo.IPs) > 0 { 164 cInfo.IP = cInfo.IPs[0] 165 } 166 return &cInfo, nil 167 }