From 77d253c889d96753ba0a8b2f3e466b06f26f11c5 Mon Sep 17 00:00:00 2001 From: mattmoor-sockpuppet Date: Tue, 9 Jul 2019 07:58:52 -0700 Subject: [PATCH] Auto-update dependencies (#48) Produced via: `dep ensure -update github.com/knative/test-infra knative.dev/pkg` /assign @mattmoor --- Gopkg.lock | 6 +- vendor/knative.dev/pkg/Gopkg.lock | 4 +- .../pkg/metrics/metricstest/metricstest.go | 161 ++++++++++++++++++ vendor/knative.dev/pkg/metrics/record.go | 11 ++ .../knative.dev/pkg/webhook/stats_reporter.go | 151 ++++++++++++++++ vendor/knative.dev/pkg/webhook/webhook.go | 13 +- 6 files changed, 337 insertions(+), 9 deletions(-) create mode 100644 vendor/knative.dev/pkg/metrics/metricstest/metricstest.go create mode 100644 vendor/knative.dev/pkg/webhook/stats_reporter.go diff --git a/Gopkg.lock b/Gopkg.lock index 4ad02a06..0a05e905 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -261,7 +261,7 @@ "tools/dep-collector", ] pruneopts = "UT" - revision = "2b0eeafd5300d91d0c96a719bd230fb3b3dd96ce" + revision = "f7f13f8e3a8af17943e1a44102607f1d3fc4751d" [[projects]] digest = "1:5985ef4caf91ece5d54817c11ea25f182697534f8ae6521eadcd628c142ac4b6" @@ -930,7 +930,7 @@ [[projects]] branch = "master" - digest = "1:c1c7cfe29d74a22c0fbc3ab02907361f8f5cd7e4a6757b9452f64d21682b040b" + digest = "1:ed15fad369d1d253ebcae3defd279859d26e54d83e1e601fb3cf40d192377404" name = "knative.dev/pkg" packages = [ "apis", @@ -949,7 +949,7 @@ "metrics/metricskey", ] pruneopts = "T" - revision = "c9bcf7e03fb535c28eae2b5ec1cf70e9363e32a7" + revision = "84d3910c565e397fa044f246398f94712da53303" [solve-meta] analyzer-name = "dep" diff --git a/vendor/knative.dev/pkg/Gopkg.lock b/vendor/knative.dev/pkg/Gopkg.lock index 1b2724eb..6dadbf6f 100644 --- a/vendor/knative.dev/pkg/Gopkg.lock +++ b/vendor/knative.dev/pkg/Gopkg.lock @@ -300,14 +300,14 @@ [[projects]] branch = "master" - digest = "1:bba4889bf4bf0a2f132ed8a2bbc4725c2877bc7b7dee37cdc551351b862a3559" + digest = "1:1bc2182db9fca862cd2b3a028aad73e473705be1c3f2454429a37b8626e16eda" name = "github.com/knative/test-infra" packages = [ "scripts", "tools/dep-collector", ] pruneopts = "UT" - revision = "91d37e4abc3047fc32ff6c0adcdea4fd29d47602" + revision = "2b0eeafd5300d91d0c96a719bd230fb3b3dd96ce" [[projects]] digest = "1:56dbf15e091bf7926cb33a57cb6bdfc658fc6d3498d2f76f10a97ce7856f1fde" diff --git a/vendor/knative.dev/pkg/metrics/metricstest/metricstest.go b/vendor/knative.dev/pkg/metrics/metricstest/metricstest.go new file mode 100644 index 00000000..91416da2 --- /dev/null +++ b/vendor/knative.dev/pkg/metrics/metricstest/metricstest.go @@ -0,0 +1,161 @@ +/* +Copyright 2019 The Knative 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 metricstest + +import ( + "testing" + + "go.opencensus.io/stats/view" +) + +// CheckStatsReported checks that there is a view registered with the given name for each string in names, +// and that each view has at least one record. +func CheckStatsReported(t *testing.T, names ...string) { + t.Helper() + for _, name := range names { + d, err := view.RetrieveData(name) + if err != nil { + t.Errorf("For metric %s: Reporter.Report() error = %v", name, err) + } + if len(d) < 1 { + t.Errorf("For metric %s: No data reported when data was expected, view data is empty.", name) + } + } +} + +// CheckStatsNotReported checks that there are no records for any views that a name matching a string in names. +// Names that do not match registered views are considered not reported. +func CheckStatsNotReported(t *testing.T, names ...string) { + t.Helper() + for _, name := range names { + d, err := view.RetrieveData(name) + // err == nil means a valid stat exists matching "name" + // len(d) > 0 means a component recorded metrics for that stat + if err == nil && len(d) > 0 { + t.Errorf("For metric %s: Unexpected data reported when no data was expected. Reporter len(d) = %d", name, len(d)) + } + } +} + +// CheckCountData checks the view with a name matching string name to verify that the CountData stats +// reported are tagged with the tags in wantTags and that wantValue matches reported count. +func CheckCountData(t *testing.T, name string, wantTags map[string]string, wantValue int64) { + t.Helper() + if row := checkExactlyOneRow(t, name, wantTags); row != nil { + checkRowTags(t, row, name, wantTags) + + if s, ok := row.Data.(*view.CountData); !ok { + t.Errorf("For metric %s: Reporter expected a CountData type", name) + } else if s.Value != wantValue { + t.Errorf("For metric %s: value = %v, want: %d", name, s.Value, wantValue) + } + } +} + +// CheckDistributionData checks the view with a name matching string name to verify that the DistributionData stats reported +// are tagged with the tags in wantTags and that expectedCount number of records were reported. +// It also checks that expectedMin and expectedMax match the minimum and maximum reported values, respectively. +func CheckDistributionData(t *testing.T, name string, wantTags map[string]string, expectedCount int64, expectedMin float64, expectedMax float64) { + t.Helper() + if row := checkExactlyOneRow(t, name, wantTags); row != nil { + checkRowTags(t, row, name, wantTags) + + if s, ok := row.Data.(*view.DistributionData); !ok { + t.Errorf("For metric %s: Reporter expected a DistributionData type", name) + } else { + if s.Count != expectedCount { + t.Errorf("For metric %s: reporter count = %d, want = %d", name, s.Count, expectedCount) + } + if s.Min != expectedMin { + t.Errorf("For metric %s: reporter count = %f, want = %f", name, s.Min, expectedMin) + } + if s.Max != expectedMax { + t.Errorf("For metric %s: reporter count = %f, want = %f", name, s.Max, expectedMax) + } + } + } +} + +// CheckLastValueData checks the view with a name matching string name to verify that the LastValueData stats +// reported are tagged with the tags in wantTags and that wantValue matches reported last value. +func CheckLastValueData(t *testing.T, name string, wantTags map[string]string, wantValue float64) { + t.Helper() + if row := checkExactlyOneRow(t, name, wantTags); row != nil { + checkRowTags(t, row, name, wantTags) + + if s, ok := row.Data.(*view.LastValueData); !ok { + t.Errorf("For metric %s: Reporter.Report() expected a LastValueData type", name) + } else if s.Value != wantValue { + t.Errorf("For metric %s: Reporter.Report() expected %v got %v", name, s.Value, wantValue) + } + } +} + +// CheckSumData checks the view with a name matching string name to verify that the SumData stats +// reported are tagged with the tags in wantTags and that wantValue matches the reported sum. +func CheckSumData(t *testing.T, name string, wantTags map[string]string, wantValue float64) { + t.Helper() + if row := checkExactlyOneRow(t, name, wantTags); row != nil { + checkRowTags(t, row, name, wantTags) + + if s, ok := row.Data.(*view.SumData); !ok { + t.Errorf("For metric %s: Reporter expected a SumData type", name) + } else if s.Value != wantValue { + t.Errorf("For metric %s: value = %v, want: %v", name, s.Value, wantValue) + } + } +} + +// Unregister unregisters the metrics that were registered. +// This is useful for testing since golang execute test iterations within the same process and +// opencensus views maintain global state. At the beginning of each test, tests should +// unregister for all metrics and then re-register for the same metrics. This effectively clears +// out any existing data and avoids a panic due to re-registering a metric. +// +// In normal process shutdown, metrics do not need to be unregistered. +func Unregister(names ...string) { + for _, n := range names { + if v := view.Find(n); v != nil { + view.Unregister(v) + } + } +} + +func checkExactlyOneRow(t *testing.T, name string, wantTags map[string]string) *view.Row { + t.Helper() + d, err := view.RetrieveData(name) + if err != nil { + t.Errorf("For metric %s: Reporter.Report() error = %v", name, err) + return nil + } + if len(d) != 1 { + t.Errorf("For metric %s: Reporter.Report() len(d)=%v, want 1", name, len(d)) + } + + return d[0] +} + +func checkRowTags(t *testing.T, row *view.Row, name string, wantTags map[string]string) { + t.Helper() + for _, got := range row.Tags { + n := got.Key.Name() + if want, ok := wantTags[n]; !ok { + t.Errorf("For metric %s: Reporter got an extra tag %v: %v", name, n, got.Value) + } else if got.Value != want { + t.Errorf("For metric %s: Reporter expected a different tag value for key: %s, got: %s, want: %s", name, n, got.Value, want) + } + } +} diff --git a/vendor/knative.dev/pkg/metrics/record.go b/vendor/knative.dev/pkg/metrics/record.go index 4e435ee5..1b045ea0 100644 --- a/vendor/knative.dev/pkg/metrics/record.go +++ b/vendor/knative.dev/pkg/metrics/record.go @@ -54,3 +54,14 @@ func Record(ctx context.Context, ms stats.Measurement) { stats.Record(ctx, ms) } } + +// Buckets125 generates an array of buckets with approximate powers-of-two +// buckets that also aligns with powers of 10 on every 3rd step. This can +// be used to create a view.Distribution. +func Buckets125(low, high float64) []float64 { + buckets := []float64{low} + for last := low; last < high; last = last * 10 { + buckets = append(buckets, 2*last, 5*last, 10*last) + } + return buckets +} diff --git a/vendor/knative.dev/pkg/webhook/stats_reporter.go b/vendor/knative.dev/pkg/webhook/stats_reporter.go new file mode 100644 index 00000000..a1bd4d53 --- /dev/null +++ b/vendor/knative.dev/pkg/webhook/stats_reporter.go @@ -0,0 +1,151 @@ +/* +Copyright 2019 The Knative 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 webhook + +import ( + "context" + "strconv" + "time" + + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" + "go.opencensus.io/tag" + admissionv1beta1 "k8s.io/api/admission/v1beta1" + "knative.dev/pkg/metrics" +) + +const ( + requestCountName = "request_count" + requestLatenciesName = "request_latencies" +) + +var ( + requestCountM = stats.Int64( + requestCountName, + "The number of requests that are routed to webhook", + stats.UnitDimensionless) + responseTimeInMsecM = stats.Float64( + requestLatenciesName, + "The response time in milliseconds", + stats.UnitMilliseconds) + + // Create the tag keys that will be used to add tags to our measurements. + // Tag keys must conform to the restrictions described in + // go.opencensus.io/tag/validate.go. Currently those restrictions are: + // - length between 1 and 255 inclusive + // - characters are printable US-ASCII + requestOperationKey = mustNewTagKey("request_operation") + kindGroupKey = mustNewTagKey("kind_group") + kindVersionKey = mustNewTagKey("kind_version") + kindKindKey = mustNewTagKey("kind_kind") + resourceGroupKey = mustNewTagKey("resource_group") + resourceVersionKey = mustNewTagKey("resource_version") + resourceResourceKey = mustNewTagKey("resource_resource") + resourceNameKey = mustNewTagKey("resource_name") + resourceNamespaceKey = mustNewTagKey("resource_namespace") + admissionAllowedKey = mustNewTagKey("admission_allowed") +) + +func init() { + register() +} + +// StatsReporter reports webhook metrics +type StatsReporter interface { + ReportRequest(request *admissionv1beta1.AdmissionRequest, response *admissionv1beta1.AdmissionResponse, d time.Duration) error +} + +// reporter implements StatsReporter interface +type reporter struct { + ctx context.Context +} + +// NewStatsReporter creaters a reporter for webhook metrics +func NewStatsReporter() (StatsReporter, error) { + ctx, err := tag.New( + context.Background(), + ) + if err != nil { + return nil, err + } + + return &reporter{ctx: ctx}, nil +} + +// Captures req count metric, recording the count and the duration +func (r *reporter) ReportRequest(req *admissionv1beta1.AdmissionRequest, resp *admissionv1beta1.AdmissionResponse, d time.Duration) error { + ctx, err := tag.New( + r.ctx, + tag.Insert(requestOperationKey, string(req.Operation)), + tag.Insert(kindGroupKey, req.Kind.Group), + tag.Insert(kindVersionKey, req.Kind.Version), + tag.Insert(kindKindKey, req.Kind.Kind), + tag.Insert(resourceGroupKey, req.Resource.Group), + tag.Insert(resourceVersionKey, req.Resource.Version), + tag.Insert(resourceResourceKey, req.Resource.Resource), + tag.Insert(resourceNameKey, req.Name), + tag.Insert(resourceNamespaceKey, req.Namespace), + tag.Insert(admissionAllowedKey, strconv.FormatBool(resp.Allowed)), + ) + if err != nil { + return err + } + + metrics.Record(ctx, requestCountM.M(1)) + // Convert time.Duration in nanoseconds to milliseconds + metrics.Record(ctx, responseTimeInMsecM.M(float64(d/time.Millisecond))) + return nil +} + +func register() { + tagKeys := []tag.Key{ + requestOperationKey, + kindGroupKey, + kindVersionKey, + kindKindKey, + resourceGroupKey, + resourceVersionKey, + resourceResourceKey, + resourceNamespaceKey, + resourceNameKey, + admissionAllowedKey} + + if err := view.Register( + &view.View{ + Description: requestCountM.Description(), + Measure: requestCountM, + Aggregation: view.Count(), + TagKeys: tagKeys, + }, + &view.View{ + Description: responseTimeInMsecM.Description(), + Measure: responseTimeInMsecM, + Aggregation: view.Distribution(metrics.Buckets125(1, 100000)...), // [1 2 5 10 20 50 100 200 500 1000 2000 5000 10000 20000 50000 100000]ms + TagKeys: tagKeys, + }, + ); err != nil { + panic(err) + } +} + +func mustNewTagKey(s string) tag.Key { + tagKey, err := tag.NewKey(s) + if err != nil { + panic(err) + } + return tagKey +} diff --git a/vendor/knative.dev/pkg/webhook/webhook.go b/vendor/knative.dev/pkg/webhook/webhook.go index 7f23b47e..08ff6b90 100644 --- a/vendor/knative.dev/pkg/webhook/webhook.go +++ b/vendor/knative.dev/pkg/webhook/webhook.go @@ -114,10 +114,11 @@ type ResourceDefaulter func(patches *[]jsonpatch.JsonPatchOperation, crd Generic // AdmissionController implements the external admission webhook for validation of // pilot configuration. type AdmissionController struct { - Client kubernetes.Interface - Options ControllerOptions - Handlers map[schema.GroupVersionKind]GenericCRD - Logger *zap.SugaredLogger + Client kubernetes.Interface + Options ControllerOptions + Handlers map[schema.GroupVersionKind]GenericCRD + Logger *zap.SugaredLogger + StatsReporter StatsReporter WithContext func(context.Context) context.Context DisallowUnknownFields bool @@ -408,6 +409,7 @@ func (ac *AdmissionController) register( // ServeHTTP implements the external admission webhook for mutating // serving resources. func (ac *AdmissionController) ServeHTTP(w http.ResponseWriter, r *http.Request) { + var ttStart = time.Now() logger := ac.Logger logger.Infof("Webhook ServeHTTP request=%#v", r) @@ -452,6 +454,9 @@ func (ac *AdmissionController) ServeHTTP(w http.ResponseWriter, r *http.Request) http.Error(w, fmt.Sprintf("could encode response: %v", err), http.StatusInternalServerError) return } + + // Only report valid requests + ac.StatsReporter.ReportRequest(review.Request, response.Response, time.Since(ttStart)) } func makeErrorStatus(reason string, args ...interface{}) *admissionv1beta1.AdmissionResponse {