go.ligato.io/vpp-agent/v3@v3.5.0/cmd/agentctl/client/client.go (about) 1 // Copyright (c) 2019 Cisco and/or its affiliates. 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 "context" 19 "crypto/tls" 20 "fmt" 21 "net" 22 "net/http" 23 "net/url" 24 "path" 25 "strconv" 26 "strings" 27 "time" 28 29 "github.com/docker/docker/api/types/versions" 30 clientv3 "go.etcd.io/etcd/client/v3" 31 "go.fd.io/govpp/proxy" 32 "go.ligato.io/cn-infra/v2/db/keyval" 33 "go.ligato.io/cn-infra/v2/db/keyval/etcd" 34 "go.ligato.io/cn-infra/v2/logging" 35 "go.ligato.io/cn-infra/v2/logging/logrus" 36 "google.golang.org/grpc" 37 "google.golang.org/grpc/credentials" 38 "google.golang.org/grpc/credentials/insecure" 39 40 "go.ligato.io/vpp-agent/v3/client" 41 "go.ligato.io/vpp-agent/v3/client/remoteclient" 42 "go.ligato.io/vpp-agent/v3/cmd/agentctl/api" 43 "go.ligato.io/vpp-agent/v3/cmd/agentctl/api/types" 44 "go.ligato.io/vpp-agent/v3/pkg/debug" 45 "go.ligato.io/vpp-agent/v3/proto/ligato/configurator" 46 "go.ligato.io/vpp-agent/v3/proto/ligato/generic" 47 ) 48 49 const ( 50 // DefaultAgentHost defines default host address for agent. 51 DefaultAgentHost = "127.0.0.1" 52 // DefaultPortGRPC defines default port for GRPC connection. 53 DefaultPortGRPC = 9111 54 // DefaultPortHTTP defines default port for HTTP connection. 55 DefaultPortHTTP = 9191 56 ) 57 58 // Constants for etcd connection. 59 const ( 60 // DefaultTimeout defines default timeout for client HTTP requests. 61 DefaultTimeout = time.Second * 120 62 // DefaultEtcdDialTimeout defines default timeout for dialing Etcd. 63 DefaultEtcdDialTimeout = time.Second * 3 64 // DefaultEtcdOpTimeout defines default timeout for a pending Etcd operation. 65 DefaultEtcdOpTimeout = time.Second * 10 66 ) 67 68 var _ APIClient = (*Client)(nil) 69 70 // Client is the API client that performs all operations 71 // against a Ligato agent. 72 type Client struct { 73 scheme string 74 host string 75 proto string 76 addr string 77 basePath string 78 79 grpcPort int 80 grpcAddr string 81 grpcTLS *tls.Config 82 httpPort int 83 httpAddr string 84 httpTLS *tls.Config 85 kvdbEndpoints []string 86 kvdbDialTimeout time.Duration 87 kvdbTLS *tls.Config 88 serviceLabel string 89 90 grpcClient *grpc.ClientConn 91 httpClient *http.Client 92 govppProxyClient *proxy.Client 93 94 customHTTPHeaders map[string]string 95 version string 96 manualOverride bool 97 negotiateVersion bool 98 negotiated bool 99 } 100 101 // NewClient returns client with host option. 102 func NewClient(host string) (*Client, error) { 103 return NewClientWithOpts(WithHost(host)) 104 } 105 106 // NewClientWithOpts returns client with ops applied. 107 func NewClientWithOpts(ops ...Opt) (*Client, error) { 108 c := &Client{ 109 host: DefaultAgentHost, 110 version: api.DefaultVersion, 111 proto: "tcp", 112 scheme: "http", 113 grpcPort: DefaultPortGRPC, 114 httpPort: DefaultPortHTTP, 115 } 116 for _, op := range ops { 117 if err := op(c); err != nil { 118 return nil, err 119 } 120 } 121 c.grpcAddr = net.JoinHostPort(c.host, strconv.Itoa(c.grpcPort)) 122 c.httpAddr = net.JoinHostPort(c.host, strconv.Itoa(c.httpPort)) 123 return c, nil 124 } 125 126 func (c *Client) AgentHost() string { 127 return c.host 128 } 129 130 func (c *Client) Version() string { 131 return c.version 132 } 133 134 // Close the transport used by the client 135 func (c *Client) Close() error { 136 if c.httpClient != nil { 137 if t, ok := c.httpClient.Transport.(*http.Transport); ok { 138 t.CloseIdleConnections() 139 } 140 } 141 if c.grpcClient != nil { 142 if err := c.grpcClient.Close(); err != nil { 143 return err 144 } 145 } 146 return nil 147 } 148 149 // GRPCConn returns configured gRPC client. 150 func (c *Client) GRPCConn() (*grpc.ClientConn, error) { 151 if c.grpcClient == nil { 152 conn, err := connectGrpc(c.grpcAddr, c.grpcTLS) 153 if err != nil { 154 return nil, err 155 } 156 c.grpcClient = conn 157 } 158 return c.grpcClient, nil 159 } 160 161 func (c *Client) GenericClient() (client.GenericClient, error) { 162 conn, err := c.GRPCConn() 163 if err != nil { 164 return nil, err 165 } 166 return remoteclient.NewClientGRPC(conn, remoteclient.UseRemoteRegistry("config")) 167 } 168 169 // ConfiguratorClient returns "config" with gRPC connection. 170 func (c *Client) ConfiguratorClient() (configurator.ConfiguratorServiceClient, error) { 171 conn, err := c.GRPCConn() 172 if err != nil { 173 return nil, err 174 } 175 return configurator.NewConfiguratorServiceClient(conn), nil 176 } 177 178 // MetaServiceClient creates new client for using meta service 179 func (c *Client) MetaServiceClient() (generic.MetaServiceClient, error) { 180 conn, err := c.GRPCConn() 181 if err != nil { 182 return nil, err 183 } 184 return generic.NewMetaServiceClient(conn), nil 185 } 186 187 // HTTPClient returns configured HTTP client. 188 func (c *Client) HTTPClient() *http.Client { 189 if c.httpClient == nil { 190 tr := http.DefaultTransport.(*http.Transport).Clone() 191 tr.TLSClientConfig = c.httpTLS 192 c.httpClient = &http.Client{ 193 Transport: tr, 194 Timeout: DefaultTimeout, 195 } 196 } 197 return c.httpClient 198 } 199 200 // GoVPPProxyClient returns configured GoVPP proxy client that is already connected to the exposed 201 // GoVPP proxy from vpp-agent 202 func (c *Client) GoVPPProxyClient() (*proxy.Client, error) { 203 if c.govppProxyClient == nil { 204 cc, err := proxy.Connect(c.httpAddr) 205 if err != nil { 206 return nil, fmt.Errorf("connecting to proxy failed due to: %v", err) 207 } 208 c.govppProxyClient = cc 209 } 210 return c.govppProxyClient, nil 211 } 212 213 // KVDBClient returns configured KVDB client. 214 func (c *Client) KVDBClient() (KVDBAPIClient, error) { 215 kvdb, err := connectEtcd(c.kvdbEndpoints, c.kvdbDialTimeout, c.kvdbTLS) 216 if err != nil { 217 return nil, fmt.Errorf("connecting to Etcd failed: %v", err) 218 } 219 return NewKVDBClient(kvdb, c.serviceLabel), nil 220 } 221 222 // ParseHostURL parses a url string, validates the string is a host url, and 223 // returns the parsed URL 224 func ParseHostURL(host string) (*url.URL, error) { 225 if !strings.Contains(host, "://") { 226 host = "tcp://" + host 227 } 228 protoAddrParts := strings.SplitN(host, "://", 2) 229 if len(protoAddrParts) == 1 { 230 return nil, fmt.Errorf("unable to parse agent host `%s`", host) 231 } 232 var basePath string 233 proto, addr := protoAddrParts[0], protoAddrParts[1] 234 if proto == "tcp" { 235 parsed, err := url.Parse("tcp://" + addr) 236 if err != nil { 237 return nil, err 238 } 239 addr = parsed.Host 240 basePath = parsed.Path 241 } 242 return &url.URL{ 243 Scheme: proto, 244 Host: addr, 245 Path: basePath, 246 }, nil 247 } 248 249 // getAPIPath returns the versioned request path to call the api. 250 // It appends the query parameters to the path if they are not empty. 251 func (c *Client) getAPIPath(ctx context.Context, p string, query url.Values) string { 252 var apiPath string 253 if c.negotiateVersion && !c.negotiated { 254 c.NegotiateAPIVersion(ctx) 255 } 256 if c.version != "" { 257 v := strings.TrimPrefix(c.version, "v") 258 apiPath = path.Join(c.basePath, "/v"+v, p) 259 } else { 260 apiPath = path.Join(c.basePath, p) 261 } 262 return (&url.URL{Path: apiPath, RawQuery: query.Encode()}).String() 263 } 264 265 func (c *Client) NegotiateAPIVersion(ctx context.Context) { 266 if !c.manualOverride { 267 version, _ := c.AgentVersion(ctx) 268 c.negotiateAPIVersionPing(version) 269 } 270 } 271 272 func (c *Client) NegotiateAPIVersionPing(p *types.Version) { 273 if !c.manualOverride { 274 c.negotiateAPIVersionPing(p) 275 } 276 } 277 278 // negotiateAPIVersionPing queries the API and updates the version to match the 279 // API version. Any errors are silently ignored. 280 func (c *Client) negotiateAPIVersionPing(p *types.Version) { 281 // try the latest version before versioning headers existed 282 if p.APIVersion == "" { 283 p.APIVersion = "0.1" 284 } 285 // if the client is not initialized with a version, start with the latest supported version 286 if c.version == "" { 287 c.version = api.DefaultVersion 288 } 289 // if server version is lower than the client version, downgrade 290 if versions.LessThan(p.APIVersion, c.version) { 291 c.version = p.APIVersion 292 } 293 // Store the results, so that automatic API version negotiation (if enabled) 294 // won't be performed on the next request. 295 if c.negotiateVersion { 296 c.negotiated = true 297 } 298 } 299 300 func connectGrpc(addr string, tc *tls.Config) (*grpc.ClientConn, error) { 301 dialOpt := grpc.WithTransportCredentials(insecure.NewCredentials()) 302 if tc != nil { 303 dialOpt = grpc.WithTransportCredentials(credentials.NewTLS(tc)) 304 } 305 logging.Debugf("dialing grpc address: %v", addr) 306 return grpc.Dial(addr, dialOpt) 307 } 308 309 func connectEtcd(endpoints []string, dialTimeout time.Duration, tc *tls.Config) (keyval.CoreBrokerWatcher, error) { 310 log := logrus.NewLogger("etcd-client") 311 if debug.IsEnabledFor("kvdb") { 312 log.SetLevel(logging.DebugLevel) 313 } else { 314 log.SetLevel(logging.WarnLevel) 315 } 316 dt := DefaultEtcdDialTimeout 317 if dialTimeout > 0 { 318 dt = dialTimeout 319 } 320 ctx, cancel := context.WithTimeout(context.Background(), dt) 321 defer cancel() 322 cfg := etcd.ClientConfig{ 323 Config: &clientv3.Config{ 324 Endpoints: endpoints, 325 DialTimeout: dt, 326 TLS: tc, 327 Context: ctx, 328 }, 329 OpTimeout: DefaultEtcdOpTimeout, 330 } 331 kvdb, err := etcd.NewEtcdConnectionWithBytes(cfg, log) 332 if err != nil { 333 return nil, err 334 } 335 return kvdb, nil 336 }