github.com/imran-kn/cilium-fork@v1.6.9/pkg/client/client.go (about)

     1  // Copyright 2016-2019 Authors of Cilium
     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 client
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"net"
    21  	"net/http"
    22  	"net/url"
    23  	"os"
    24  	"sort"
    25  	"strings"
    26  	"text/tabwriter"
    27  	"time"
    28  
    29  	clientapi "github.com/cilium/cilium/api/v1/client"
    30  	"github.com/cilium/cilium/api/v1/models"
    31  	"github.com/cilium/cilium/pkg/defaults"
    32  
    33  	runtime_client "github.com/go-openapi/runtime/client"
    34  	"github.com/go-openapi/strfmt"
    35  	"golang.org/x/net/context"
    36  )
    37  
    38  type Client struct {
    39  	clientapi.Cilium
    40  }
    41  
    42  // DefaultSockPath returns default UNIX domain socket path or
    43  // path set using CILIUM_SOCK env variable
    44  func DefaultSockPath() string {
    45  	// Check if environment variable points to socket
    46  	e := os.Getenv(defaults.SockPathEnv)
    47  	if e == "" {
    48  		// If unset, fall back to default value
    49  		e = defaults.SockPath
    50  	}
    51  	return "unix://" + e
    52  
    53  }
    54  
    55  func configureTransport(tr *http.Transport, proto, addr string) *http.Transport {
    56  	if tr == nil {
    57  		tr = &http.Transport{}
    58  	}
    59  
    60  	if proto == "unix" {
    61  		// No need for compression in local communications.
    62  		tr.DisableCompression = true
    63  		tr.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
    64  			return net.Dial(proto, addr)
    65  		}
    66  	} else {
    67  		tr.Proxy = http.ProxyFromEnvironment
    68  		tr.DialContext = (&net.Dialer{}).DialContext
    69  	}
    70  
    71  	return tr
    72  }
    73  
    74  // NewDefaultClient creates a client with default parameters connecting to UNIX domain socket.
    75  func NewDefaultClient() (*Client, error) {
    76  	return NewClient("")
    77  }
    78  
    79  // NewDefaultClientWithTimeout creates a client with default parameters connecting to UNIX
    80  // domain socket and waits for cilium-agent availability.
    81  func NewDefaultClientWithTimeout(timeout time.Duration) (*Client, error) {
    82  	timeoutAfter := time.After(timeout)
    83  	var c *Client
    84  	var err error
    85  	for {
    86  		select {
    87  		case <-timeoutAfter:
    88  			return nil, fmt.Errorf("failed to create cilium agent client after %f seconds timeout: %s", timeout.Seconds(), err)
    89  		default:
    90  		}
    91  
    92  		c, err = NewDefaultClient()
    93  		if err != nil {
    94  			time.Sleep(500 * time.Millisecond)
    95  			continue
    96  		}
    97  
    98  		for {
    99  			select {
   100  			case <-timeoutAfter:
   101  				return nil, fmt.Errorf("failed to create cilium agent client after %f seconds timeout: %s", timeout.Seconds(), err)
   102  			default:
   103  			}
   104  			// This is an API call that we do to the cilium-agent to check
   105  			// if it is up and running.
   106  			_, err = c.Daemon.GetConfig(nil)
   107  			if err != nil {
   108  				time.Sleep(500 * time.Millisecond)
   109  				continue
   110  			}
   111  			return c, nil
   112  		}
   113  	}
   114  }
   115  
   116  // NewClient creates a client for the given `host`.
   117  // If host is nil then use SockPath provided by CILIUM_SOCK
   118  // or the cilium default SockPath
   119  func NewClient(host string) (*Client, error) {
   120  	if host == "" {
   121  		host = DefaultSockPath()
   122  	}
   123  	tmp := strings.SplitN(host, "://", 2)
   124  	if len(tmp) != 2 {
   125  		return nil, fmt.Errorf("invalid host format '%s'", host)
   126  	}
   127  
   128  	switch tmp[0] {
   129  	case "tcp":
   130  		if _, err := url.Parse("tcp://" + tmp[1]); err != nil {
   131  			return nil, err
   132  		}
   133  		host = "http://" + tmp[1]
   134  	case "unix":
   135  		host = tmp[1]
   136  	}
   137  
   138  	transport := configureTransport(nil, tmp[0], host)
   139  	httpClient := &http.Client{Transport: transport}
   140  	clientTrans := runtime_client.NewWithClient(tmp[1], clientapi.DefaultBasePath,
   141  		clientapi.DefaultSchemes, httpClient)
   142  	return &Client{*clientapi.New(clientTrans, strfmt.Default)}, nil
   143  }
   144  
   145  // Hint tries to improve the error message displayed to the user.
   146  func Hint(err error) error {
   147  	if err == nil {
   148  		return err
   149  	}
   150  
   151  	if err == context.DeadlineExceeded {
   152  		return fmt.Errorf("Cilium API client timeout exceeded")
   153  	}
   154  
   155  	e, _ := url.PathUnescape(err.Error())
   156  	if strings.Contains(err.Error(), defaults.SockPath) {
   157  		return fmt.Errorf("%s\nIs the agent running?", e)
   158  	}
   159  	return fmt.Errorf("%s", e)
   160  }
   161  
   162  func timeSince(since time.Time) string {
   163  	out := "never"
   164  	if !since.IsZero() {
   165  		// Poor man's implementtion of time.Truncate(). Can be refined
   166  		// when we rebase to go 1.9
   167  		t := time.Since(since)
   168  		t -= t % time.Second
   169  		out = t.String() + " ago"
   170  	}
   171  
   172  	return out
   173  }
   174  
   175  func stateUnhealthy(state string) bool {
   176  	return state == models.StatusStateWarning ||
   177  		state == models.StatusStateFailure
   178  }
   179  
   180  func statusUnhealthy(s *models.Status) bool {
   181  	if s != nil {
   182  		return stateUnhealthy(s.State)
   183  	}
   184  	return false
   185  }
   186  
   187  // FormatStatusResponseBrief writes a one-line status to the writer. If
   188  // everything ok, this is "ok", otherwise a message of the form "error in ..."
   189  func FormatStatusResponseBrief(w io.Writer, sr *models.StatusResponse) {
   190  	msg := ""
   191  
   192  	switch {
   193  	case statusUnhealthy(sr.Kvstore):
   194  		msg = fmt.Sprintf("kvstore: %s", sr.Kvstore.Msg)
   195  	case statusUnhealthy(sr.ContainerRuntime):
   196  		msg = fmt.Sprintf("container runtime: %s", sr.ContainerRuntime.Msg)
   197  	case sr.Kubernetes != nil && stateUnhealthy(sr.Kubernetes.State):
   198  		msg = fmt.Sprintf("kubernetes: %s", sr.Kubernetes.Msg)
   199  	case statusUnhealthy(sr.Cilium):
   200  		msg = fmt.Sprintf("cilium: %s", sr.Cilium.Msg)
   201  	case sr.Cluster != nil && statusUnhealthy(sr.Cluster.CiliumHealth):
   202  		msg = fmt.Sprintf("cilium-health: %s", sr.Cluster.CiliumHealth.Msg)
   203  	}
   204  
   205  	// Only bother looking at controller failures if everything else is ok
   206  	if msg == "" {
   207  		for _, ctrl := range sr.Controllers {
   208  			if ctrl.Status == nil {
   209  				continue
   210  			}
   211  			if ctrl.Status.LastFailureMsg != "" {
   212  				msg = fmt.Sprintf("controller %s: %s",
   213  					ctrl.Name, ctrl.Status.LastFailureMsg)
   214  				break
   215  			}
   216  		}
   217  	}
   218  
   219  	if msg == "" {
   220  		fmt.Fprintf(w, "OK\n")
   221  	} else {
   222  		fmt.Fprintf(w, "error in %s\n", msg)
   223  	}
   224  }
   225  
   226  // FormatStatusResponse writes a StatusResponse as a string to the writer.
   227  //
   228  // The parameters 'allAddresses', 'allControllers', 'allNodes', respectively,
   229  // cause all details about that aspect of the status to be printed to the
   230  // terminal. For each of these, if they are false then only a summary will be
   231  // printed, with perhaps some detail if there are errors.
   232  func FormatStatusResponse(w io.Writer, sr *models.StatusResponse, allAddresses, allControllers, allNodes, allRedirects bool) {
   233  	if sr.Kvstore != nil {
   234  		fmt.Fprintf(w, "KVStore:\t%s\t%s\n", sr.Kvstore.State, sr.Kvstore.Msg)
   235  	}
   236  	if sr.ContainerRuntime != nil {
   237  		fmt.Fprintf(w, "ContainerRuntime:\t%s\t%s\n",
   238  			sr.ContainerRuntime.State, sr.ContainerRuntime.Msg)
   239  	}
   240  	if sr.Kubernetes != nil {
   241  		fmt.Fprintf(w, "Kubernetes:\t%s\t%s\n", sr.Kubernetes.State, sr.Kubernetes.Msg)
   242  		if sr.Kubernetes.State != models.K8sStatusStateDisabled {
   243  			sort.Strings(sr.Kubernetes.K8sAPIVersions)
   244  			fmt.Fprintf(w, "Kubernetes APIs:\t[\"%s\"]\n", strings.Join(sr.Kubernetes.K8sAPIVersions, "\", \""))
   245  		}
   246  	}
   247  	if sr.Cilium != nil {
   248  		fmt.Fprintf(w, "Cilium:\t%s\t%s\n", sr.Cilium.State, sr.Cilium.Msg)
   249  	}
   250  
   251  	if sr.Stale != nil {
   252  		sortedProbes := make([]string, 0, len(sr.Stale))
   253  		for probe := range sr.Stale {
   254  			sortedProbes = append(sortedProbes, probe)
   255  		}
   256  		sort.Strings(sortedProbes)
   257  
   258  		stalesStr := make([]string, 0, len(sr.Stale))
   259  		for _, probe := range sortedProbes {
   260  			stalesStr = append(stalesStr, fmt.Sprintf("%q since %s", probe, sr.Stale[probe]))
   261  		}
   262  
   263  		fmt.Fprintf(w, "Stale status:\t%s\n", strings.Join(stalesStr, ", "))
   264  	}
   265  
   266  	if nm := sr.NodeMonitor; nm != nil {
   267  		fmt.Fprintf(w, "NodeMonitor:\tListening for events on %d CPUs with %dx%d of shared memory\n",
   268  			nm.Cpus, nm.Npages, nm.Pagesize)
   269  		if nm.Lost != 0 || nm.Unknown != 0 {
   270  			fmt.Fprintf(w, "\t%d events lost, %d unknown notifications\n", nm.Lost, nm.Unknown)
   271  		}
   272  	} else {
   273  		fmt.Fprintf(w, "NodeMonitor:\tDisabled\n")
   274  	}
   275  
   276  	if sr.Cluster != nil {
   277  		if sr.Cluster.CiliumHealth != nil {
   278  			ch := sr.Cluster.CiliumHealth
   279  			fmt.Fprintf(w, "Cilium health daemon:\t%s\t%s\n", ch.State, ch.Msg)
   280  		}
   281  	}
   282  
   283  	if sr.IPAM != nil {
   284  		fmt.Fprintf(w, "IPAM:\t%s\n", sr.IPAM.Status)
   285  		if allAddresses {
   286  			fmt.Fprintf(w, "Allocated addresses:\n")
   287  			out := []string{}
   288  			for ip, owner := range sr.IPAM.Allocations {
   289  				out = append(out, fmt.Sprintf("  %s (%s)", ip, owner))
   290  			}
   291  			sort.Strings(out)
   292  			for _, line := range out {
   293  				fmt.Fprintln(w, line)
   294  			}
   295  		}
   296  	}
   297  
   298  	if sr.Controllers != nil {
   299  		nFailing, out := 0, []string{"  Name\tLast success\tLast error\tCount\tMessage\n"}
   300  		for _, ctrl := range sr.Controllers {
   301  			status := ctrl.Status
   302  			if status == nil {
   303  				continue
   304  			}
   305  
   306  			if status.ConsecutiveFailureCount > 0 {
   307  				nFailing++
   308  			} else if !allControllers {
   309  				continue
   310  			}
   311  
   312  			failSince := timeSince(time.Time(status.LastFailureTimestamp))
   313  			successSince := timeSince(time.Time(status.LastSuccessTimestamp))
   314  
   315  			err := "no error"
   316  			if status.LastFailureMsg != "" {
   317  				err = status.LastFailureMsg
   318  			}
   319  
   320  			out = append(out, fmt.Sprintf("  %s\t%s\t%s\t%d\t%s\t\n",
   321  				ctrl.Name, successSince, failSince, status.ConsecutiveFailureCount, err))
   322  		}
   323  
   324  		nOK := len(sr.Controllers) - nFailing
   325  		fmt.Fprintf(w, "Controller Status:\t%d/%d healthy\n", nOK, len(sr.Controllers))
   326  		if len(out) > 1 {
   327  			tab := tabwriter.NewWriter(w, 0, 0, 3, ' ', 0)
   328  			sort.Strings(out)
   329  			for _, s := range out {
   330  				fmt.Fprint(tab, s)
   331  			}
   332  			tab.Flush()
   333  		}
   334  
   335  	}
   336  
   337  	if sr.Proxy != nil {
   338  		fmt.Fprintf(w, "Proxy Status:\tOK, ip %s, port-range %s\n",
   339  			sr.Proxy.IP, sr.Proxy.PortRange)
   340  	} else {
   341  		fmt.Fprintf(w, "Proxy Status:\tNo managed proxy redirect\n")
   342  	}
   343  }