mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-29 22:46:12 +00:00
Merge pull request #109120 from kevindelgado/ss-validation-metrics
Track field validation in metrics
This commit is contained in:
commit
c7fc0f9125
@ -122,6 +122,19 @@ var (
|
|||||||
},
|
},
|
||||||
[]string{"verb", "group", "version", "resource", "subresource", "scope", "component"},
|
[]string{"verb", "group", "version", "resource", "subresource", "scope", "component"},
|
||||||
)
|
)
|
||||||
|
fieldValidationRequestLatencies = compbasemetrics.NewHistogramVec(
|
||||||
|
&compbasemetrics.HistogramOpts{
|
||||||
|
Name: "field_validation_request_duration_seconds",
|
||||||
|
Help: "Response latency distribution in seconds for each field validation value and whether field validation is enabled or not",
|
||||||
|
// This metric is supplementary to the requestLatencies metric.
|
||||||
|
// It measures request durations for the various field validation
|
||||||
|
// values.
|
||||||
|
Buckets: []float64{0.05, 0.1, 0.2, 0.4, 0.6, 0.8, 1.0, 1.25, 1.5, 2, 3,
|
||||||
|
4, 5, 6, 8, 10, 15, 20, 30, 45, 60},
|
||||||
|
StabilityLevel: compbasemetrics.ALPHA,
|
||||||
|
},
|
||||||
|
[]string{"field_validation", "enabled"},
|
||||||
|
)
|
||||||
responseSizes = compbasemetrics.NewHistogramVec(
|
responseSizes = compbasemetrics.NewHistogramVec(
|
||||||
&compbasemetrics.HistogramOpts{
|
&compbasemetrics.HistogramOpts{
|
||||||
Name: "apiserver_response_sizes",
|
Name: "apiserver_response_sizes",
|
||||||
@ -261,6 +274,7 @@ var (
|
|||||||
longRunningRequestGauge,
|
longRunningRequestGauge,
|
||||||
requestLatencies,
|
requestLatencies,
|
||||||
requestSloLatencies,
|
requestSloLatencies,
|
||||||
|
fieldValidationRequestLatencies,
|
||||||
responseSizes,
|
responseSizes,
|
||||||
droppedRequests,
|
droppedRequests,
|
||||||
TLSHandshakeErrors,
|
TLSHandshakeErrors,
|
||||||
@ -510,6 +524,10 @@ func MonitorRequest(req *http.Request, verb, group, version, resource, subresour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
requestLatencies.WithContext(req.Context()).WithLabelValues(reportedVerb, dryRun, group, version, resource, subresource, scope, component).Observe(elapsedSeconds)
|
requestLatencies.WithContext(req.Context()).WithLabelValues(reportedVerb, dryRun, group, version, resource, subresource, scope, component).Observe(elapsedSeconds)
|
||||||
|
fieldValidation := cleanFieldValidation(req.URL)
|
||||||
|
fieldValidationEnabled := strconv.FormatBool(utilfeature.DefaultFeatureGate.Enabled(features.ServerSideFieldValidation))
|
||||||
|
fieldValidationRequestLatencies.WithContext(req.Context()).WithLabelValues(fieldValidation, fieldValidationEnabled)
|
||||||
|
|
||||||
if wd, ok := request.LatencyTrackersFrom(req.Context()); ok {
|
if wd, ok := request.LatencyTrackersFrom(req.Context()); ok {
|
||||||
sloLatency := elapsedSeconds - (wd.MutatingWebhookTracker.GetLatency() + wd.ValidatingWebhookTracker.GetLatency()).Seconds()
|
sloLatency := elapsedSeconds - (wd.MutatingWebhookTracker.GetLatency() + wd.ValidatingWebhookTracker.GetLatency()).Seconds()
|
||||||
requestSloLatencies.WithContext(req.Context()).WithLabelValues(reportedVerb, group, version, resource, subresource, scope, component).Observe(sloLatency)
|
requestSloLatencies.WithContext(req.Context()).WithLabelValues(reportedVerb, group, version, resource, subresource, scope, component).Observe(sloLatency)
|
||||||
@ -650,6 +668,21 @@ func cleanDryRun(u *url.URL) string {
|
|||||||
return strings.Join(utilsets.NewString(dryRun...).List(), ",")
|
return strings.Join(utilsets.NewString(dryRun...).List(), ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanFieldValidation(u *url.URL) string {
|
||||||
|
// avoid allocating when we don't see dryRun in the query
|
||||||
|
if !strings.Contains(u.RawQuery, "fieldValidation") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
fieldValidation := u.Query()["fieldValidation"]
|
||||||
|
if len(fieldValidation) != 1 {
|
||||||
|
return "invalid"
|
||||||
|
}
|
||||||
|
if errs := validation.ValidateFieldValidation(nil, fieldValidation[0]); len(errs) > 0 {
|
||||||
|
return "invalid"
|
||||||
|
}
|
||||||
|
return fieldValidation[0]
|
||||||
|
}
|
||||||
|
|
||||||
var _ http.ResponseWriter = (*ResponseWriterDelegator)(nil)
|
var _ http.ResponseWriter = (*ResponseWriterDelegator)(nil)
|
||||||
var _ responsewriter.UserProvidedDecorator = (*ResponseWriterDelegator)(nil)
|
var _ responsewriter.UserProvidedDecorator = (*ResponseWriterDelegator)(nil)
|
||||||
|
|
||||||
|
@ -204,6 +204,62 @@ func TestCleanScope(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCleanFieldValidation(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
url *url.URL
|
||||||
|
expectedFieldValidation string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty field validation",
|
||||||
|
url: &url.URL{},
|
||||||
|
expectedFieldValidation: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ignore field validation",
|
||||||
|
url: &url.URL{
|
||||||
|
RawQuery: "fieldValidation=Ignore",
|
||||||
|
},
|
||||||
|
expectedFieldValidation: "Ignore",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "warn field validation",
|
||||||
|
url: &url.URL{
|
||||||
|
RawQuery: "fieldValidation=Warn",
|
||||||
|
},
|
||||||
|
expectedFieldValidation: "Warn",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "strict field validation",
|
||||||
|
url: &url.URL{
|
||||||
|
RawQuery: "fieldValidation=Strict",
|
||||||
|
},
|
||||||
|
expectedFieldValidation: "Strict",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid field validation",
|
||||||
|
url: &url.URL{
|
||||||
|
RawQuery: "fieldValidation=foo",
|
||||||
|
},
|
||||||
|
expectedFieldValidation: "invalid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple field validation",
|
||||||
|
url: &url.URL{
|
||||||
|
RawQuery: "fieldValidation=Strict&fieldValidation=Ignore",
|
||||||
|
},
|
||||||
|
expectedFieldValidation: "invalid",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
if fieldValidation := cleanFieldValidation(test.url); fieldValidation != test.expectedFieldValidation {
|
||||||
|
t.Errorf("failed to clean field validation, expected: %s, got: %s", test.expectedFieldValidation, fieldValidation)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestResponseWriterDecorator(t *testing.T) {
|
func TestResponseWriterDecorator(t *testing.T) {
|
||||||
decorator := &ResponseWriterDelegator{
|
decorator := &ResponseWriterDelegator{
|
||||||
ResponseWriter: &responsewriter.FakeResponseWriter{},
|
ResponseWriter: &responsewriter.FakeResponseWriter{},
|
||||||
|
Loading…
Reference in New Issue
Block a user