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 }