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  }