diff --git a/pkg/controller/proxyconfig/controller.go b/pkg/controller/proxyconfig/controller.go index 8c7e7ec8e8..cffa010528 100644 --- a/pkg/controller/proxyconfig/controller.go +++ b/pkg/controller/proxyconfig/controller.go @@ -154,3 +154,9 @@ func isSpecHTTPSProxySet(proxyConfig *configv1.ProxySpec) bool { func isSpecNoProxySet(proxyConfig *configv1.ProxySpec) bool { return len(proxyConfig.NoProxy) > 0 } + +// isSpecReadinessEndpointsSet returns true if spec.readinessEndpoints of +// proxyConfig is set. +func isSpecReadinessEndpointsSet(proxyConfig *configv1.ProxySpec) bool { + return len(proxyConfig.ReadinessEndpoints) > 0 +} diff --git a/pkg/controller/proxyconfig/validation.go b/pkg/controller/proxyconfig/validation.go index a81d03b18a..855b09ec13 100644 --- a/pkg/controller/proxyconfig/validation.go +++ b/pkg/controller/proxyconfig/validation.go @@ -3,7 +3,10 @@ package proxyconfig import ( "fmt" "net" + "net/http" + "net/url" "strings" + "time" configv1 "github.com/openshift/api/config/v1" "github.com/openshift/cluster-network-operator/pkg/util/validation" @@ -12,6 +15,11 @@ import ( const ( proxyHTTPScheme = "http" proxyHTTPSScheme = "https" + // proxyProbeMaxRetries is the number of times to attempt an http GET + // to a readinessEndpoints endpoint. + proxyProbeMaxRetries = 3 + // proxyProbeWaitTime is the time to wait before retrying a failed proxy probe. + proxyProbeWaitTime = 1 * time.Second // noProxyWildcard is the string used to as a wildcard attached to a // domain suffix in proxy.spec.noProxy to bypass proxying. noProxyWildcard = "*" @@ -52,5 +60,83 @@ func (r *ReconcileProxyConfig) ValidateProxyConfig(proxyConfig *configv1.ProxySp } } + if isSpecReadinessEndpointsSet(proxyConfig) { + for _, endpoint := range proxyConfig.ReadinessEndpoints { + scheme, err := validation.URI(endpoint) + if err != nil { + return fmt.Errorf("invalid URI for endpoint '%s': %v", endpoint, err) + } + switch { + case scheme == proxyHTTPScheme: + // TODO: Add case for proxyHTTPSScheme when CA support is merged. + if err := validateHTTPReadinessEndpoint(proxyConfig.HTTPProxy, endpoint); err != nil { + return fmt.Errorf("readinessEndpoint probe failed for endpoint '%s'", endpoint) + } + default: + // TODO: Update error to include proxyHTTPSScheme when CA support is merged. + return fmt.Errorf("readiness endpoints requires a '%s' URI sheme", proxyHTTPScheme) + } + } + } + return nil } + +// validateHTTPReadinessEndpoint validates an http readinessEndpoint endpoint. +func validateHTTPReadinessEndpoint(httpProxy, endpoint string) error { + if err := validateHTTPReadinessEndpointWithRetries(httpProxy, endpoint, proxyProbeMaxRetries); err != nil { + return err + } + + return nil +} + +// validateHTTPReadinessEndpointWithRetries tries to validate an http +// endpoint in a finite loop and returns the last result if it never succeeds. +func validateHTTPReadinessEndpointWithRetries(httpProxy, endpoint string, retries int) error { + var err error + for i := 0; i < retries; i++ { + err = runHTTPReadinessProbe(httpProxy, endpoint) + if err == nil { + return nil + } + time.Sleep(proxyProbeWaitTime) + } + + return err +} + +// runHTTPReadinessProbe issues an http GET request to endpoint and returns +// an error if a 2XX or 3XX http status code is not returned. The request +// is proxied if proxy environment variables exist. +func runHTTPReadinessProbe(httpProxy, endpoint string) error { + proxyURL, err := url.Parse(httpProxy) + if err != nil { + return fmt.Errorf("failed to parse httpProxy url '%s': %v", httpProxy, err) + } + + transport := &http.Transport{ + Proxy: http.ProxyURL(proxyURL), + } + + client := &http.Client{ + Transport: transport, + } + + request, err := http.NewRequest("GET", endpoint, nil) + if err != nil { + return fmt.Errorf("failed to create http request for '%s': %v", endpoint, err) + } + + resp, err := client.Do(request) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusBadRequest { + return nil + } + + return fmt.Errorf("http probe failed with statuscode: %d", resp.StatusCode) +}