request/client.go (88 lines of code) (raw):
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package request
import (
"fmt"
"math"
"net/http"
"github.com/Azure/kperf/api/types"
"github.com/Azure/kperf/request/unstructuredscheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
// NewClients creates N rest.Interface.
//
// FIXME(weifu):
//
// 1. Is it possible to build one http2 client with multiple connections?
// 2. How to monitor HTTP2 GOAWAY frame?
func NewClients(kubeCfgPath string, connsNum int, opts ...ClientCfgOpt) ([]rest.Interface, error) {
var cfg = defaultClientCfg
for _, opt := range opts {
opt(&cfg)
}
restCfg, err := clientcmd.BuildConfigFromFlags("", kubeCfgPath)
if err != nil {
return nil, err
}
restCfg.NegotiatedSerializer = unstructuredscheme.NewNegotiatedSerializer()
// NOTE:
//
// Make transport uncacheable. With default proxy function, client-go
// will create new transport even if multiple clients use the same TLS
// configuration. If not, all the clients will share one transport.
// If protocol is HTTP2, there will be only one connection.
//
// REF: https://github.com/kubernetes/client-go/blob/c5938c6876a62f53c1f4ee55b879ca5c74253ae8/transport/cache.go#L154
restCfg.Proxy = http.ProxyFromEnvironment
err = cfg.apply(restCfg)
if err != nil {
return nil, err
}
restClients := make([]rest.Interface, 0, connsNum)
for i := 0; i < connsNum; i++ {
cfgShallowCopy := *restCfg
restCli, err := rest.UnversionedRESTClientFor(&cfgShallowCopy)
if err != nil {
return nil, err
}
restClients = append(restClients, restCli)
}
return restClients, nil
}
// defaultClientCfg is default setting for http client.
var defaultClientCfg = clientCfg{
qps: float64(math.MaxInt32),
contentType: types.ContentTypeJSON,
}
type clientCfg struct {
userAgent string
qps float64
contentType types.ContentType
disableHTTP2 bool
}
// apply sets value to k8s.io/client-go/rest.Config.
func (cfg *clientCfg) apply(restCfg *rest.Config) error {
// set qps
restCfg.QPS = float32(cfg.qps)
// set user agent
restCfg.UserAgent = cfg.userAgent
if restCfg.UserAgent == "" {
restCfg.UserAgent = rest.DefaultKubernetesUserAgent()
}
// set the content type
switch cfg.contentType {
case types.ContentTypeJSON:
restCfg.ContentType = "application/json"
case types.ContentTypeProtobuffer:
restCfg.ContentType = "application/vnd.kubernetes.protobuf"
default:
return fmt.Errorf("invalid content type: %s", cfg.contentType)
}
// disable HTTP2
if cfg.disableHTTP2 {
restCfg.NextProtos = []string{"http/1.1"}
}
return nil
}
// ClientCfgOpt is used to update default client setting.
type ClientCfgOpt func(*clientCfg)
// WithClientQPSOpt updates QPS value.
func WithClientQPSOpt(qps float64) ClientCfgOpt {
return func(cfg *clientCfg) {
if qps > 0 {
cfg.qps = qps
}
}
}
// WithClientUserAgentOpt updates user agent.
func WithClientUserAgentOpt(ua string) ClientCfgOpt {
return func(cfg *clientCfg) {
cfg.userAgent = ua
}
}
// WithClientContentTypeOpt updates content type of response.
func WithClientContentTypeOpt(ct types.ContentType) ClientCfgOpt {
return func(cfg *clientCfg) {
cfg.contentType = ct
}
}
// WithClientDisableHTTP2Opt disables HTTP2 protocol.
func WithClientDisableHTTP2Opt(b bool) ClientCfgOpt {
return func(cfg *clientCfg) {
cfg.disableHTTP2 = b
}
}