...

Source file src/sigs.k8s.io/cli-utils/pkg/flowcontrol/flowcontrol.go

Documentation: sigs.k8s.io/cli-utils/pkg/flowcontrol

     1  // Copyright 2022 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package flowcontrol
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"net/http"
    10  	"net/url"
    11  
    12  	flowcontrolapi "k8s.io/api/flowcontrol/v1beta2"
    13  	"k8s.io/apimachinery/pkg/runtime/schema"
    14  	"k8s.io/client-go/rest"
    15  	"k8s.io/client-go/transport"
    16  )
    17  
    18  const pingPath = "/livez/ping"
    19  
    20  // IsEnabled returns true if the server has the PriorityAndFairness flow control
    21  // filter enabled. This check performs a GET request to the /version endpoint
    22  // and looks for the presence of the `X-Kubernetes-PF-FlowSchema-UID` header.
    23  func IsEnabled(ctx context.Context, config *rest.Config) (bool, error) {
    24  	// Build a RoundTripper from the provided REST client config.
    25  	// RoundTriper handles TLS, auth, auth proxy, user agent, impersonation, and
    26  	// debug logs. It also provides acess to the response headers, unlike the
    27  	// REST client. And we can't just use an HTTP client, because the version
    28  	// endpoint may or may not require auth, depending on RBAC config.
    29  	tcfg, err := config.TransportConfig()
    30  	if err != nil {
    31  		return false, fmt.Errorf("building transport config: %w", err)
    32  	}
    33  	roudtripper, err := transport.New(tcfg)
    34  	if err != nil {
    35  		return false, fmt.Errorf("building round tripper: %w", err)
    36  	}
    37  
    38  	// Build the base apiserver URL from the provided REST client config.
    39  	url, err := serverURL(config)
    40  	if err != nil {
    41  		return false, fmt.Errorf("building server URL: %w", err)
    42  	}
    43  
    44  	// Use the ping endpoint, because it's small and fast.
    45  	// It's alpha in v1.23+, but a 404 will still have the flowcontrol headers.
    46  	// Replacing the path is safe, because DefaultServerURL will have errored
    47  	// if it wasn't empty from the config.
    48  	url.Path = pingPath
    49  
    50  	// Build HEAD request with an empty body.
    51  	req, err := http.NewRequestWithContext(ctx, "HEAD", url.String(), nil)
    52  	if err != nil {
    53  		return false, fmt.Errorf("building request: %w", err)
    54  	}
    55  
    56  	if config.UserAgent != "" {
    57  		req.Header.Set("User-Agent", config.UserAgent)
    58  	}
    59  
    60  	// We don't care what the response body is.
    61  	// req.Header.Set("Accept", "text/plain")
    62  
    63  	// Perform the request.
    64  	resp, err := roudtripper.RoundTrip(req)
    65  	if err != nil {
    66  		return false, fmt.Errorf("making %s request: %w", pingPath, err)
    67  	}
    68  	// Probably nil for HEAD, but check anyway.
    69  	if resp.Body != nil {
    70  		// Always close the response body, to free up resources.
    71  		err := resp.Body.Close()
    72  		if err != nil {
    73  			return false, fmt.Errorf("closing response body: %v", err)
    74  		}
    75  	}
    76  
    77  	// If the response has one of the flowcontrol headers,
    78  	// that means the flowcontrol filter is enabled.
    79  	// There are two headers, but they're always both set by FlowControl.
    80  	// So we only need to check one.
    81  	// key = flowcontrolapi.ResponseHeaderMatchedPriorityLevelConfigurationUID
    82  	key := flowcontrolapi.ResponseHeaderMatchedFlowSchemaUID
    83  	if value := resp.Header.Get(key); value != "" {
    84  		// We don't care what the value is (always a UID).
    85  		// We just care that the header is present.
    86  		return true, nil
    87  	}
    88  	return false, nil
    89  }
    90  
    91  // serverUrl returns the base URL for the cluster based on the supplied config.
    92  // Host and Version are required. GroupVersion is ignored.
    93  // Based on `defaultServerUrlFor` from k8s.io/client-go@v0.23.2/rest/url_utils.go
    94  func serverURL(config *rest.Config) (*url.URL, error) {
    95  	// TODO: move the default to secure when the apiserver supports TLS by default
    96  	// config.Insecure is taken to mean "I want HTTPS but don't bother checking the certs against a CA."
    97  	hasCA := len(config.CAFile) != 0 || len(config.CAData) != 0
    98  	hasCert := len(config.CertFile) != 0 || len(config.CertData) != 0
    99  	defaultTLS := hasCA || hasCert || config.Insecure
   100  	host := config.Host
   101  	if host == "" {
   102  		host = "localhost"
   103  	}
   104  
   105  	hostURL, _, err := rest.DefaultServerURL(host, config.APIPath, schema.GroupVersion{}, defaultTLS)
   106  	return hostURL, err
   107  }
   108  

View as plain text