Changes to accommodate client-go changes and kube vendor update

to v1.18.0

Signed-off-by: Humble Chirammal <hchiramm@redhat.com>
This commit is contained in:
Humble Chirammal
2020-04-14 12:34:33 +05:30
committed by mergify[bot]
parent 4c96ad3c85
commit 34fc1d847e
1083 changed files with 50505 additions and 155846 deletions

View File

@ -19,16 +19,12 @@ package connection
import (
"context"
"errors"
"fmt"
"io/ioutil"
"net"
"strings"
"time"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/kubernetes-csi/csi-lib-utils/metrics"
"github.com/kubernetes-csi/csi-lib-utils/protosanitizer"
"google.golang.org/grpc"
"k8s.io/klog"
@ -63,8 +59,8 @@ const terminationLogPath = "/dev/termination-log"
//
// For other connections, the default behavior from gRPC is used and
// loss of connection is not detected reliably.
func Connect(address string, options ...Option) (*grpc.ClientConn, error) {
return connect(address, []grpc.DialOption{}, options)
func Connect(address string, metricsManager metrics.CSIMetricsManager, options ...Option) (*grpc.ClientConn, error) {
return connect(address, metricsManager, []grpc.DialOption{}, options)
}
// Option is the type of all optional parameters for Connect.
@ -98,7 +94,10 @@ type options struct {
}
// connect is the internal implementation of Connect. It has more options to enable testing.
func connect(address string, dialOptions []grpc.DialOption, connectOptions []Option) (*grpc.ClientConn, error) {
func connect(
address string,
metricsManager metrics.CSIMetricsManager,
dialOptions []grpc.DialOption, connectOptions []Option) (*grpc.ClientConn, error) {
var o options
for _, option := range connectOptions {
option(&o)
@ -108,7 +107,10 @@ func connect(address string, dialOptions []grpc.DialOption, connectOptions []Opt
grpc.WithInsecure(), // Don't use TLS, it's usually local Unix domain socket in a container.
grpc.WithBackoffMaxDelay(time.Second), // Retry every second after failure.
grpc.WithBlock(), // Block until connection succeeds.
grpc.WithUnaryInterceptor(LogGRPC), // Log all messages.
grpc.WithChainUnaryInterceptor(
LogGRPC, // Log all messages.
extendedCSIMetricsManager{metricsManager}.recordMetricsInterceptor, // Record metrics for each gRPC call.
),
)
unixPrefix := "unix://"
if strings.HasPrefix(address, "/") {
@ -185,126 +187,25 @@ func LogGRPC(ctx context.Context, method string, req, reply interface{}, cc *grp
return err
}
// GetDriverName returns name of CSI driver.
func GetDriverName(ctx context.Context, conn *grpc.ClientConn) (string, error) {
client := csi.NewIdentityClient(conn)
req := csi.GetPluginInfoRequest{}
rsp, err := client.GetPluginInfo(ctx, &req)
if err != nil {
return "", err
}
name := rsp.GetName()
if name == "" {
return "", fmt.Errorf("driver name is empty")
}
return name, nil
type extendedCSIMetricsManager struct {
metrics.CSIMetricsManager
}
// PluginCapabilitySet is set of CSI plugin capabilities. Only supported capabilities are in the map.
type PluginCapabilitySet map[csi.PluginCapability_Service_Type]bool
// GetPluginCapabilities returns set of supported capabilities of CSI driver.
func GetPluginCapabilities(ctx context.Context, conn *grpc.ClientConn) (PluginCapabilitySet, error) {
client := csi.NewIdentityClient(conn)
req := csi.GetPluginCapabilitiesRequest{}
rsp, err := client.GetPluginCapabilities(ctx, &req)
if err != nil {
return nil, err
}
caps := PluginCapabilitySet{}
for _, cap := range rsp.GetCapabilities() {
if cap == nil {
continue
}
srv := cap.GetService()
if srv == nil {
continue
}
t := srv.GetType()
caps[t] = true
}
return caps, nil
}
// ControllerCapabilitySet is set of CSI controller capabilities. Only supported capabilities are in the map.
type ControllerCapabilitySet map[csi.ControllerServiceCapability_RPC_Type]bool
// GetControllerCapabilities returns set of supported controller capabilities of CSI driver.
func GetControllerCapabilities(ctx context.Context, conn *grpc.ClientConn) (ControllerCapabilitySet, error) {
client := csi.NewControllerClient(conn)
req := csi.ControllerGetCapabilitiesRequest{}
rsp, err := client.ControllerGetCapabilities(ctx, &req)
if err != nil {
return nil, err
}
caps := ControllerCapabilitySet{}
for _, cap := range rsp.GetCapabilities() {
if cap == nil {
continue
}
rpc := cap.GetRpc()
if rpc == nil {
continue
}
t := rpc.GetType()
caps[t] = true
}
return caps, nil
}
// ProbeForever calls Probe() of a CSI driver and waits until the driver becomes ready.
// Any error other than timeout is returned.
func ProbeForever(conn *grpc.ClientConn, singleProbeTimeout time.Duration) error {
for {
klog.Info("Probing CSI driver for readiness")
ready, err := probeOnce(conn, singleProbeTimeout)
if err != nil {
st, ok := status.FromError(err)
if !ok {
// This is not gRPC error. The probe must have failed before gRPC
// method was called, otherwise we would get gRPC error.
return fmt.Errorf("CSI driver probe failed: %s", err)
}
if st.Code() != codes.DeadlineExceeded {
return fmt.Errorf("CSI driver probe failed: %s", err)
}
// Timeout -> driver is not ready. Fall through to sleep() below.
klog.Warning("CSI driver probe timed out")
} else {
if ready {
return nil
}
klog.Warning("CSI driver is not ready")
}
// Timeout was returned or driver is not ready.
time.Sleep(probeInterval)
}
}
// probeOnce is a helper to simplify defer cancel()
func probeOnce(conn *grpc.ClientConn, timeout time.Duration) (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return Probe(ctx, conn)
}
// Probe calls driver Probe() just once and returns its result without any processing.
func Probe(ctx context.Context, conn *grpc.ClientConn) (ready bool, err error) {
client := csi.NewIdentityClient(conn)
req := csi.ProbeRequest{}
rsp, err := client.Probe(ctx, &req)
if err != nil {
return false, err
}
r := rsp.GetReady()
if r == nil {
// "If not present, the caller SHALL assume that the plugin is in a ready state"
return true, nil
}
return r.GetValue(), nil
// recordMetricsInterceptor is a gPRC unary interceptor for recording metrics for CSI operations.
func (cmm extendedCSIMetricsManager) recordMetricsInterceptor(
ctx context.Context,
method string,
req, reply interface{},
cc *grpc.ClientConn,
invoker grpc.UnaryInvoker,
opts ...grpc.CallOption) error {
start := time.Now()
err := invoker(ctx, method, req, reply, cc, opts...)
duration := time.Since(start)
cmm.RecordMetrics(
method, /* operationName */
err, /* operationErr */
duration, /* operationDuration */
)
return err
}

View File

@ -0,0 +1,199 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package metrics
import (
"bufio"
"fmt"
"net/http"
"strings"
"time"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"k8s.io/component-base/metrics"
"k8s.io/klog"
)
const (
// Common metric strings
subsystem = "csi_sidecar"
labelCSIDriverName = "driver_name"
labelCSIOperationName = "method_name"
labelGrpcStatusCode = "grpc_status_code"
unknownCSIDriverName = "unknown-driver"
// CSI Operation Latency with status code total - Histogram Metric
operationsLatencyMetricName = "operations_seconds"
operationsLatencyHelp = "Container Storage Interface operation duration with gRPC error code status total"
)
var (
operationsLatencyBuckets = []float64{.1, .25, .5, 1, 2.5, 5, 10, 15, 25, 50, 120, 300, 600}
)
// CSIMetricsManager exposes functions for recording metrics for CSI operations.
type CSIMetricsManager interface {
// GetRegistry() returns the metrics.KubeRegistry used by this metrics manager.
GetRegistry() metrics.KubeRegistry
// RecordMetrics must be called upon CSI Operation completion to record
// the operation's metric.
// operationName - Name of the CSI operation.
// operationErr - Error, if any, that resulted from execution of operation.
// operationDuration - time it took for the operation to complete
RecordMetrics(
operationName string,
operationErr error,
operationDuration time.Duration)
// SetDriverName is called to update the CSI driver name. This should be done
// as soon as possible, otherwise metrics recorded by this manager will be
// recorded with an "unknown-driver" driver_name.
// driverName - Name of the CSI driver against which this operation was executed.
SetDriverName(driverName string)
// StartMetricsEndpoint starts the metrics endpoint at the specified address/path
// for this metrics manager.
// If the metricsAddress is an empty string, this will be a no op.
StartMetricsEndpoint(metricsAddress, metricsPath string)
}
// NewCSIMetricsManager creates and registers metrics for for CSI Sidecars and
// returns an object that can be used to trigger the metrics.
// driverName - Name of the CSI driver against which this operation was executed.
// If unknown, leave empty, and use SetDriverName method to update later.
func NewCSIMetricsManager(driverName string) CSIMetricsManager {
cmm := csiMetricsManager{
registry: metrics.NewKubeRegistry(),
csiOperationsLatencyMetric: metrics.NewHistogramVec(
&metrics.HistogramOpts{
Subsystem: subsystem,
Name: operationsLatencyMetricName,
Help: operationsLatencyHelp,
Buckets: operationsLatencyBuckets,
StabilityLevel: metrics.ALPHA,
},
[]string{labelCSIDriverName, labelCSIOperationName, labelGrpcStatusCode},
),
}
cmm.SetDriverName(driverName)
cmm.registerMetrics()
return &cmm
}
var _ CSIMetricsManager = &csiMetricsManager{}
type csiMetricsManager struct {
registry metrics.KubeRegistry
driverName string
csiOperationsMetric *metrics.CounterVec
csiOperationsLatencyMetric *metrics.HistogramVec
}
func (cmm *csiMetricsManager) GetRegistry() metrics.KubeRegistry {
return cmm.registry
}
// RecordMetrics must be called upon CSI Operation completion to record
// the operation's metric.
// operationName - Name of the CSI operation.
// operationErr - Error, if any, that resulted from execution of operation.
// operationDuration - time it took for the operation to complete
func (cmm *csiMetricsManager) RecordMetrics(
operationName string,
operationErr error,
operationDuration time.Duration) {
cmm.csiOperationsLatencyMetric.WithLabelValues(
cmm.driverName, operationName, getErrorCode(operationErr)).Observe(operationDuration.Seconds())
}
// SetDriverName is called to update the CSI driver name. This should be done
// as soon as possible, otherwise metrics recorded by this manager will be
// recorded with an "unknown-driver" driver_name.
func (cmm *csiMetricsManager) SetDriverName(driverName string) {
if driverName == "" {
cmm.driverName = unknownCSIDriverName
} else {
cmm.driverName = driverName
}
}
// StartMetricsEndpoint starts the metrics endpoint at the specified address/path
// for this metrics manager on a new go routine.
// If the metricsAddress is an empty string, this will be a no op.
func (cmm *csiMetricsManager) StartMetricsEndpoint(metricsAddress, metricsPath string) {
if metricsAddress == "" {
klog.Warningf("metrics endpoint will not be started because `metrics-address` was not specified.")
return
}
http.Handle(metricsPath, metrics.HandlerFor(
cmm.GetRegistry(),
metrics.HandlerOpts{
ErrorHandling: metrics.ContinueOnError}))
// Spawn a new go routine to listen on specified endpoint
go func() {
err := http.ListenAndServe(metricsAddress, nil)
if err != nil {
klog.Fatalf("Failed to start prometheus metrics endpoint on specified address (%q) and path (%q): %s", metricsAddress, metricsPath, err)
}
}()
}
// VerifyMetricsMatch is a helper function that verifies that the expected and
// actual metrics are identical excluding metricToIgnore.
// This method is only used by tests. Ideally it should be in the _test file,
// but *_test.go files are compiled into the package only when running go test
// for that package and this method is used by metrics_test as well as
// connection_test. If there are more consumers in the future, we can consider
// moving it to a new, standalone package.
func VerifyMetricsMatch(expectedMetrics, actualMetrics string, metricToIgnore string) error {
gotScanner := bufio.NewScanner(strings.NewReader(strings.TrimSpace(actualMetrics)))
wantScanner := bufio.NewScanner(strings.NewReader(strings.TrimSpace(expectedMetrics)))
for gotScanner.Scan() {
wantScanner.Scan()
wantLine := strings.TrimSpace(wantScanner.Text())
gotLine := strings.TrimSpace(gotScanner.Text())
if wantLine != gotLine && (metricToIgnore == "" || !strings.HasPrefix(gotLine, metricToIgnore)) {
return fmt.Errorf("\r\nMetric Want: %q\r\nMetric Got: %q\r\n", wantLine, gotLine)
}
}
return nil
}
func (cmm *csiMetricsManager) registerMetrics() {
cmm.registry.MustRegister(cmm.csiOperationsLatencyMetric)
}
func getErrorCode(err error) string {
if err == nil {
return codes.OK.String()
}
st, ok := status.FromError(err)
if !ok {
// This is not gRPC error. The operation must have failed before gRPC
// method was called, otherwise we would get gRPC error.
return "unknown-non-grpc"
}
return st.Code().String()
}