mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-06 02:34:03 +00:00
admission: make metrics compositional and move to metrics sub-package
This commit is contained in:
parent
d82ae45a4c
commit
baba0c827b
@ -136,7 +136,7 @@ func TestIgnoreAdmission(t *testing.T) {
|
|||||||
t.Errorf("unexpected error initializing handler: %v", err)
|
t.Errorf("unexpected error initializing handler: %v", err)
|
||||||
}
|
}
|
||||||
informerFactory.Start(wait.NeverStop)
|
informerFactory.Start(wait.NeverStop)
|
||||||
chainHandler := admission.NewChainHandler(admission.NewNamedHandler("ns", handler))
|
chainHandler := admission.NewChainHandler(handler)
|
||||||
|
|
||||||
pod := newPod(namespace)
|
pod := newPod(namespace)
|
||||||
err = chainHandler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
err = chainHandler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||||
|
@ -78,7 +78,7 @@ func mockVolumeLabels(labels map[string]string) *mockVolumes {
|
|||||||
// TestAdmission
|
// TestAdmission
|
||||||
func TestAdmission(t *testing.T) {
|
func TestAdmission(t *testing.T) {
|
||||||
pvHandler := NewPersistentVolumeLabel()
|
pvHandler := NewPersistentVolumeLabel()
|
||||||
handler := admission.NewChainHandler(admission.NewNamedHandler("pv", pvHandler))
|
handler := admission.NewChainHandler(pvHandler)
|
||||||
ignoredPV := api.PersistentVolume{
|
ignoredPV := api.PersistentVolume{
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "noncloud", Namespace: "myns"},
|
ObjectMeta: metav1.ObjectMeta{Name: "noncloud", Namespace: "myns"},
|
||||||
Spec: api.PersistentVolumeSpec{
|
Spec: api.PersistentVolumeSpec{
|
||||||
|
@ -866,6 +866,10 @@
|
|||||||
"ImportPath": "k8s.io/apiserver/pkg/admission/initializer",
|
"ImportPath": "k8s.io/apiserver/pkg/admission/initializer",
|
||||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "k8s.io/apiserver/pkg/admission/metrics",
|
||||||
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apiserver/pkg/admission/plugin/initialization",
|
"ImportPath": "k8s.io/apiserver/pkg/admission/plugin/initialization",
|
||||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
@ -13,15 +13,10 @@ go_test(
|
|||||||
"config_test.go",
|
"config_test.go",
|
||||||
"errors_test.go",
|
"errors_test.go",
|
||||||
"handler_test.go",
|
"handler_test.go",
|
||||||
"metrics_test.go",
|
|
||||||
"testutil_test.go",
|
|
||||||
],
|
],
|
||||||
importpath = "k8s.io/apiserver/pkg/admission",
|
importpath = "k8s.io/apiserver/pkg/admission",
|
||||||
library = ":go_default_library",
|
library = ":go_default_library",
|
||||||
deps = [
|
deps = [
|
||||||
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
|
|
||||||
"//vendor/github.com/prometheus/client_model/go:go_default_library",
|
|
||||||
"//vendor/k8s.io/api/admissionregistration/v1alpha1:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
@ -38,15 +33,12 @@ go_library(
|
|||||||
"errors.go",
|
"errors.go",
|
||||||
"handler.go",
|
"handler.go",
|
||||||
"interfaces.go",
|
"interfaces.go",
|
||||||
"metrics.go",
|
|
||||||
"plugins.go",
|
"plugins.go",
|
||||||
],
|
],
|
||||||
importpath = "k8s.io/apiserver/pkg/admission",
|
importpath = "k8s.io/apiserver/pkg/admission",
|
||||||
deps = [
|
deps = [
|
||||||
"//vendor/github.com/ghodss/yaml:go_default_library",
|
"//vendor/github.com/ghodss/yaml:go_default_library",
|
||||||
"//vendor/github.com/golang/glog:go_default_library",
|
"//vendor/github.com/golang/glog:go_default_library",
|
||||||
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
|
|
||||||
"//vendor/k8s.io/api/admissionregistration/v1alpha1:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library",
|
||||||
@ -76,6 +68,7 @@ filegroup(
|
|||||||
":package-srcs",
|
":package-srcs",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/admission/configuration:all-srcs",
|
"//staging/src/k8s.io/apiserver/pkg/admission/configuration:all-srcs",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/admission/initializer:all-srcs",
|
"//staging/src/k8s.io/apiserver/pkg/admission/initializer:all-srcs",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/admission/metrics:all-srcs",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/initialization:all-srcs",
|
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/initialization:all-srcs",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle:all-srcs",
|
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle:all-srcs",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config:all-srcs",
|
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config:all-srcs",
|
||||||
|
@ -16,46 +16,23 @@ limitations under the License.
|
|||||||
|
|
||||||
package admission
|
package admission
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// chainAdmissionHandler is an instance of admission.NamedHandler that performs admission control using
|
// chainAdmissionHandler is an instance of admission.NamedHandler that performs admission control using
|
||||||
// a chain of admission handlers
|
// a chain of admission handlers
|
||||||
type chainAdmissionHandler []NamedHandler
|
type chainAdmissionHandler []Interface
|
||||||
|
|
||||||
// NewChainHandler creates a new chain handler from an array of handlers. Used for testing.
|
// NewChainHandler creates a new chain handler from an array of handlers. Used for testing.
|
||||||
func NewChainHandler(handlers ...NamedHandler) chainAdmissionHandler {
|
func NewChainHandler(handlers ...Interface) chainAdmissionHandler {
|
||||||
return chainAdmissionHandler(handlers)
|
return chainAdmissionHandler(handlers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNamedHandler(name string, i Interface) NamedHandler {
|
|
||||||
return &pluginHandler{
|
|
||||||
Interface: i,
|
|
||||||
name: name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
stepValidate = "validate"
|
|
||||||
stepAdmit = "admit"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Admit performs an admission control check using a chain of handlers, and returns immediately on first error
|
// Admit performs an admission control check using a chain of handlers, and returns immediately on first error
|
||||||
func (admissionHandler chainAdmissionHandler) Admit(a Attributes) error {
|
func (admissionHandler chainAdmissionHandler) Admit(a Attributes) error {
|
||||||
start := time.Now()
|
|
||||||
err := admissionHandler.admit(a)
|
|
||||||
Metrics.ObserveAdmissionStep(time.Since(start), err != nil, a, stepAdmit)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (admissionHandler chainAdmissionHandler) admit(a Attributes) error {
|
|
||||||
for _, handler := range admissionHandler {
|
for _, handler := range admissionHandler {
|
||||||
if !handler.Handles(a.GetOperation()) {
|
if !handler.Handles(a.GetOperation()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if mutator, ok := handler.(MutationInterface); ok {
|
if mutator, ok := handler.(MutationInterface); ok {
|
||||||
t := time.Now()
|
|
||||||
err := mutator.Admit(a)
|
err := mutator.Admit(a)
|
||||||
Metrics.ObserveAdmissionController(time.Since(t), err != nil, handler, a, stepAdmit)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -66,21 +43,12 @@ func (admissionHandler chainAdmissionHandler) admit(a Attributes) error {
|
|||||||
|
|
||||||
// Validate performs an admission control check using a chain of handlers, and returns immediately on first error
|
// Validate performs an admission control check using a chain of handlers, and returns immediately on first error
|
||||||
func (admissionHandler chainAdmissionHandler) Validate(a Attributes) error {
|
func (admissionHandler chainAdmissionHandler) Validate(a Attributes) error {
|
||||||
start := time.Now()
|
|
||||||
err := admissionHandler.validate(a)
|
|
||||||
Metrics.ObserveAdmissionStep(time.Since(start), err != nil, a, stepValidate)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (admissionHandler chainAdmissionHandler) validate(a Attributes) (err error) {
|
|
||||||
for _, handler := range admissionHandler {
|
for _, handler := range admissionHandler {
|
||||||
if !handler.Handles(a.GetOperation()) {
|
if !handler.Handles(a.GetOperation()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if validator, ok := handler.(ValidationInterface); ok {
|
if validator, ok := handler.(ValidationInterface); ok {
|
||||||
t := time.Now()
|
|
||||||
err := validator.Validate(a)
|
err := validator.Validate(a)
|
||||||
Metrics.ObserveAdmissionController(time.Since(t), err != nil, handler, a, stepValidate)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,45 @@ limitations under the License.
|
|||||||
package admission
|
package admission
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type FakeHandler struct {
|
||||||
|
*Handler
|
||||||
|
name string
|
||||||
|
admit, admitCalled bool
|
||||||
|
validate, validateCalled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *FakeHandler) Admit(a Attributes) (err error) {
|
||||||
|
h.admitCalled = true
|
||||||
|
if h.admit {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Don't admit")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *FakeHandler) Validate(a Attributes) (err error) {
|
||||||
|
h.validateCalled = true
|
||||||
|
if h.validate {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Don't validate")
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeHandler(name string, accept bool, ops ...Operation) *FakeHandler {
|
||||||
|
return &FakeHandler{
|
||||||
|
name: name,
|
||||||
|
admit: accept,
|
||||||
|
validate: accept,
|
||||||
|
Handler: NewHandler(ops...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAdmitAndValidate(t *testing.T) {
|
func TestAdmitAndValidate(t *testing.T) {
|
||||||
sysns := metav1.NamespaceSystem
|
sysns := metav1.NamespaceSystem
|
||||||
otherns := "default"
|
otherns := "default"
|
||||||
@ -38,10 +71,10 @@ func TestAdmitAndValidate(t *testing.T) {
|
|||||||
name: "all accept",
|
name: "all accept",
|
||||||
ns: sysns,
|
ns: sysns,
|
||||||
operation: Create,
|
operation: Create,
|
||||||
chain: []NamedHandler{
|
chain: []Interface{
|
||||||
makeNamedHandler("a", true, Update, Delete, Create),
|
makeHandler("a", true, Update, Delete, Create),
|
||||||
makeNamedHandler("b", true, Delete, Create),
|
makeHandler("b", true, Delete, Create),
|
||||||
makeNamedHandler("c", true, Create),
|
makeHandler("c", true, Create),
|
||||||
},
|
},
|
||||||
calls: map[string]bool{"a": true, "b": true, "c": true},
|
calls: map[string]bool{"a": true, "b": true, "c": true},
|
||||||
accept: true,
|
accept: true,
|
||||||
@ -50,10 +83,10 @@ func TestAdmitAndValidate(t *testing.T) {
|
|||||||
name: "ignore handler",
|
name: "ignore handler",
|
||||||
ns: otherns,
|
ns: otherns,
|
||||||
operation: Create,
|
operation: Create,
|
||||||
chain: []NamedHandler{
|
chain: []Interface{
|
||||||
makeNamedHandler("a", true, Update, Delete, Create),
|
makeHandler("a", true, Update, Delete, Create),
|
||||||
makeNamedHandler("b", false, Delete),
|
makeHandler("b", false, Delete),
|
||||||
makeNamedHandler("c", true, Create),
|
makeHandler("c", true, Create),
|
||||||
},
|
},
|
||||||
calls: map[string]bool{"a": true, "c": true},
|
calls: map[string]bool{"a": true, "c": true},
|
||||||
accept: true,
|
accept: true,
|
||||||
@ -62,10 +95,10 @@ func TestAdmitAndValidate(t *testing.T) {
|
|||||||
name: "ignore all",
|
name: "ignore all",
|
||||||
ns: sysns,
|
ns: sysns,
|
||||||
operation: Connect,
|
operation: Connect,
|
||||||
chain: []NamedHandler{
|
chain: []Interface{
|
||||||
makeNamedHandler("a", true, Update, Delete, Create),
|
makeHandler("a", true, Update, Delete, Create),
|
||||||
makeNamedHandler("b", false, Delete),
|
makeHandler("b", false, Delete),
|
||||||
makeNamedHandler("c", true, Create),
|
makeHandler("c", true, Create),
|
||||||
},
|
},
|
||||||
calls: map[string]bool{},
|
calls: map[string]bool{},
|
||||||
accept: true,
|
accept: true,
|
||||||
@ -74,17 +107,16 @@ func TestAdmitAndValidate(t *testing.T) {
|
|||||||
name: "reject one",
|
name: "reject one",
|
||||||
ns: otherns,
|
ns: otherns,
|
||||||
operation: Delete,
|
operation: Delete,
|
||||||
chain: []NamedHandler{
|
chain: []Interface{
|
||||||
makeNamedHandler("a", true, Update, Delete, Create),
|
makeHandler("a", true, Update, Delete, Create),
|
||||||
makeNamedHandler("b", false, Delete),
|
makeHandler("b", false, Delete),
|
||||||
makeNamedHandler("c", true, Create),
|
makeHandler("c", true, Create),
|
||||||
},
|
},
|
||||||
calls: map[string]bool{"a": true, "b": true},
|
calls: map[string]bool{"a": true, "b": true},
|
||||||
accept: false,
|
accept: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
Metrics.reset()
|
|
||||||
t.Logf("testcase = %s", test.name)
|
t.Logf("testcase = %s", test.name)
|
||||||
// call admit and check that validate was not called at all
|
// call admit and check that validate was not called at all
|
||||||
err := test.chain.Admit(NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, test.ns, "", schema.GroupVersionResource{}, "", test.operation, nil))
|
err := test.chain.Admit(NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, test.ns, "", schema.GroupVersionResource{}, "", test.operation, nil))
|
||||||
@ -94,25 +126,19 @@ func TestAdmitAndValidate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, h := range test.chain {
|
for _, h := range test.chain {
|
||||||
fake := h.(*FakeHandler)
|
fake := h.(*FakeHandler)
|
||||||
_, shouldBeCalled := test.calls[h.Name()]
|
_, shouldBeCalled := test.calls[fake.name]
|
||||||
if shouldBeCalled != fake.admitCalled {
|
if shouldBeCalled != fake.admitCalled {
|
||||||
t.Errorf("admit handler %s not called as expected: %v", h.Name(), fake.admitCalled)
|
t.Errorf("admit handler %s not called as expected: %v", fake.name, fake.admitCalled)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if fake.validateCalled {
|
if fake.validateCalled {
|
||||||
t.Errorf("validate handler %s called during admit", h.Name())
|
t.Errorf("validate handler %s called during admit", fake.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset value for validation test
|
// reset value for validation test
|
||||||
fake.admitCalled = false
|
fake.admitCalled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
labelFilter := map[string]string{
|
|
||||||
"type": "admit",
|
|
||||||
}
|
|
||||||
|
|
||||||
checkAdmitAndValidateMetrics(t, labelFilter, test.accept, test.calls)
|
|
||||||
Metrics.reset()
|
|
||||||
// call validate and check that admit was not called at all
|
// call validate and check that admit was not called at all
|
||||||
err = test.chain.Validate(NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, test.ns, "", schema.GroupVersionResource{}, "", test.operation, nil))
|
err = test.chain.Validate(NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, test.ns, "", schema.GroupVersionResource{}, "", test.operation, nil))
|
||||||
accepted = (err == nil)
|
accepted = (err == nil)
|
||||||
@ -122,63 +148,24 @@ func TestAdmitAndValidate(t *testing.T) {
|
|||||||
for _, h := range test.chain {
|
for _, h := range test.chain {
|
||||||
fake := h.(*FakeHandler)
|
fake := h.(*FakeHandler)
|
||||||
|
|
||||||
_, shouldBeCalled := test.calls[h.Name()]
|
_, shouldBeCalled := test.calls[fake.name]
|
||||||
if shouldBeCalled != fake.validateCalled {
|
if shouldBeCalled != fake.validateCalled {
|
||||||
t.Errorf("validate handler %s not called as expected: %v", h.Name(), fake.validateCalled)
|
t.Errorf("validate handler %s not called as expected: %v", fake.name, fake.validateCalled)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if fake.admitCalled {
|
if fake.admitCalled {
|
||||||
t.Errorf("mutating handler unexpectedly called: %s", h.Name())
|
t.Errorf("mutating handler unexpectedly called: %s", fake.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
labelFilter = map[string]string{
|
|
||||||
"type": "validate",
|
|
||||||
}
|
|
||||||
|
|
||||||
checkAdmitAndValidateMetrics(t, labelFilter, test.accept, test.calls)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkAdmitAndValidateMetrics(t *testing.T, labelFilter map[string]string, accept bool, calls map[string]bool) {
|
|
||||||
acceptFilter := map[string]string{"rejected": "false"}
|
|
||||||
for k, v := range labelFilter {
|
|
||||||
acceptFilter[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
rejectFilter := map[string]string{"rejected": "true"}
|
|
||||||
for k, v := range labelFilter {
|
|
||||||
rejectFilter[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
if accept {
|
|
||||||
// Ensure exactly one admission end-to-end admission accept should have been recorded.
|
|
||||||
expectHistogramCountTotal(t, "apiserver_admission_step_admission_latencies_seconds", acceptFilter, 1)
|
|
||||||
|
|
||||||
// Ensure the expected count of admission controllers have been executed.
|
|
||||||
expectHistogramCountTotal(t, "apiserver_admission_controller_admission_latencies_seconds", acceptFilter, len(calls))
|
|
||||||
} else {
|
|
||||||
// When not accepted, ensure exactly one end-to-end rejection has been recorded.
|
|
||||||
expectHistogramCountTotal(t, "apiserver_admission_step_admission_latencies_seconds", rejectFilter, 1)
|
|
||||||
if len(calls) > 0 {
|
|
||||||
if len(calls) > 1 {
|
|
||||||
// When not accepted, ensure that all but the last controller had been accepted, since
|
|
||||||
// the chain stops execution at the first rejection.
|
|
||||||
expectHistogramCountTotal(t, "apiserver_admission_controller_admission_latencies_seconds", acceptFilter, len(calls)-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// When not accepted, ensure exactly one controller has been rejected.
|
|
||||||
expectHistogramCountTotal(t, "apiserver_admission_controller_admission_latencies_seconds", rejectFilter, 1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandles(t *testing.T) {
|
func TestHandles(t *testing.T) {
|
||||||
chain := chainAdmissionHandler{
|
chain := chainAdmissionHandler{
|
||||||
makeNamedHandler("a", true, Update, Delete, Create),
|
makeHandler("a", true, Update, Delete, Create),
|
||||||
makeNamedHandler("b", true, Delete, Create),
|
makeHandler("b", true, Delete, Create),
|
||||||
makeNamedHandler("c", true, Create),
|
makeHandler("c", true, Create),
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
42
staging/src/k8s.io/apiserver/pkg/admission/metrics/BUILD
Normal file
42
staging/src/k8s.io/apiserver/pkg/admission/metrics/BUILD
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["metrics.go"],
|
||||||
|
importpath = "k8s.io/apiserver/pkg/admission/metrics",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = [
|
||||||
|
"metrics_test.go",
|
||||||
|
"testutil_test.go",
|
||||||
|
],
|
||||||
|
importpath = "k8s.io/apiserver/pkg/admission/metrics",
|
||||||
|
library = ":go_default_library",
|
||||||
|
deps = [
|
||||||
|
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
|
||||||
|
"//vendor/github.com/prometheus/client_model/go:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
@ -14,16 +14,16 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package admission
|
package metrics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/api/admissionregistration/v1alpha1"
|
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/admission"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -39,10 +39,64 @@ var (
|
|||||||
Metrics = newAdmissionMetrics()
|
Metrics = newAdmissionMetrics()
|
||||||
)
|
)
|
||||||
|
|
||||||
// NamedHandler requires each admission.Interface be named, primarly for metrics tracking purposes.
|
// ObserverFunc is a func that emits metrics.
|
||||||
type NamedHandler interface {
|
type ObserverFunc func(elapsed time.Duration, rejected bool, attr admission.Attributes, stepType string, extraLabels ...string)
|
||||||
Interface
|
|
||||||
Name() string
|
const (
|
||||||
|
stepValidate = "validate"
|
||||||
|
stepAdmit = "admit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithControllerMetrics is a decorator for named admission handlers.
|
||||||
|
func WithControllerMetrics(i admission.Interface, name string) admission.Interface {
|
||||||
|
return WithMetrics(i, Metrics.ObserveAdmissionController, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStepMetrics is a decorator for a whole admission phase, i.e. admit or validation.admission step.
|
||||||
|
func WithStepMetrics(i admission.Interface) admission.Interface {
|
||||||
|
return WithMetrics(i, Metrics.ObserveAdmissionStep)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMetrics is a decorator for admission handlers with a generic observer func.
|
||||||
|
func WithMetrics(i admission.Interface, observer ObserverFunc, extraLabels ...string) admission.Interface {
|
||||||
|
return &pluginHandlerWithMetrics{
|
||||||
|
Interface: i,
|
||||||
|
observer: observer,
|
||||||
|
extraLabels: extraLabels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pluginHandlerWithMetrics decorates a admission handler with metrics.
|
||||||
|
type pluginHandlerWithMetrics struct {
|
||||||
|
admission.Interface
|
||||||
|
observer ObserverFunc
|
||||||
|
extraLabels []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admit performs a mutating admission control check and emit metrics.
|
||||||
|
func (p pluginHandlerWithMetrics) Admit(a admission.Attributes) error {
|
||||||
|
mutatingHandler, ok := p.Interface.(admission.MutationInterface)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
err := mutatingHandler.Admit(a)
|
||||||
|
p.observer(time.Since(start), err != nil, a, stepAdmit, p.extraLabels...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate performs a non-mutating admission control check and emits metrics.
|
||||||
|
func (p pluginHandlerWithMetrics) Validate(a admission.Attributes) error {
|
||||||
|
validatingHandler, ok := p.Interface.(admission.ValidationInterface)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
err := validatingHandler.Validate(a)
|
||||||
|
p.observer(time.Since(start), err != nil, a, stepValidate, p.extraLabels...)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdmissionMetrics instruments admission with prometheus metrics.
|
// AdmissionMetrics instruments admission with prometheus metrics.
|
||||||
@ -83,22 +137,21 @@ func (m *AdmissionMetrics) reset() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ObserveAdmissionStep records admission related metrics for a admission step, identified by step type.
|
// ObserveAdmissionStep records admission related metrics for a admission step, identified by step type.
|
||||||
func (m *AdmissionMetrics) ObserveAdmissionStep(elapsed time.Duration, rejected bool, attr Attributes, stepType string) {
|
func (m *AdmissionMetrics) ObserveAdmissionStep(elapsed time.Duration, rejected bool, attr admission.Attributes, stepType string, extraLabels ...string) {
|
||||||
gvr := attr.GetResource()
|
gvr := attr.GetResource()
|
||||||
m.step.observe(elapsed, stepType, string(attr.GetOperation()), gvr.Group, gvr.Version, gvr.Resource, attr.GetSubresource(), strconv.FormatBool(rejected))
|
m.step.observe(elapsed, append(extraLabels, stepType, string(attr.GetOperation()), gvr.Group, gvr.Version, gvr.Resource, attr.GetSubresource(), strconv.FormatBool(rejected))...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObserveAdmissionController records admission related metrics for a built-in admission controller, identified by it's plugin handler name.
|
// ObserveAdmissionController records admission related metrics for a built-in admission controller, identified by it's plugin handler name.
|
||||||
func (m *AdmissionMetrics) ObserveAdmissionController(elapsed time.Duration, rejected bool, handler NamedHandler, attr Attributes, stepType string) {
|
func (m *AdmissionMetrics) ObserveAdmissionController(elapsed time.Duration, rejected bool, attr admission.Attributes, stepType string, extraLabels ...string) {
|
||||||
gvr := attr.GetResource()
|
gvr := attr.GetResource()
|
||||||
m.controller.observe(elapsed, handler.Name(), stepType, string(attr.GetOperation()), gvr.Group, gvr.Version, gvr.Resource, attr.GetSubresource(), strconv.FormatBool(rejected))
|
m.controller.observe(elapsed, append(extraLabels, stepType, string(attr.GetOperation()), gvr.Group, gvr.Version, gvr.Resource, attr.GetSubresource(), strconv.FormatBool(rejected))...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObserveWebhook records admission related metrics for a admission webhook.
|
// ObserveWebhook records admission related metrics for a admission webhook.
|
||||||
func (m *AdmissionMetrics) ObserveWebhook(elapsed time.Duration, rejected bool, hook *v1alpha1.Webhook, attr Attributes) {
|
func (m *AdmissionMetrics) ObserveWebhook(elapsed time.Duration, rejected bool, attr admission.Attributes, stepType string, extraLabels ...string) {
|
||||||
t := "admit" // TODO: pass in type (validate|admit) once mutating webhook functionality has been implemented
|
|
||||||
gvr := attr.GetResource()
|
gvr := attr.GetResource()
|
||||||
m.webhook.observe(elapsed, hook.Name, t, string(attr.GetOperation()), gvr.Group, gvr.Version, gvr.Resource, attr.GetSubresource(), strconv.FormatBool(rejected))
|
m.webhook.observe(elapsed, append(extraLabels, stepType, string(attr.GetOperation()), gvr.Group, gvr.Version, gvr.Resource, attr.GetSubresource(), strconv.FormatBool(rejected))...)
|
||||||
}
|
}
|
||||||
|
|
||||||
type metricSet struct {
|
type metricSet struct {
|
@ -0,0 +1,250 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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 (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apiserver/pkg/admission"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
kind = schema.GroupVersionKind{Group: "kgroup", Version: "kversion", Kind: "kind"}
|
||||||
|
resource = schema.GroupVersionResource{Group: "rgroup", Version: "rversion", Resource: "resource"}
|
||||||
|
attr = admission.NewAttributesRecord(nil, nil, kind, "ns", "name", resource, "subresource", admission.Create, nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestObserveAdmissionStep(t *testing.T) {
|
||||||
|
Metrics.reset()
|
||||||
|
handler := WithStepMetrics(&mutatingAndValidatingFakeHandler{admission.NewHandler(admission.Create), true, true})
|
||||||
|
handler.(admission.MutationInterface).Admit(attr)
|
||||||
|
handler.(admission.ValidationInterface).Validate(attr)
|
||||||
|
wantLabels := map[string]string{
|
||||||
|
"operation": string(admission.Create),
|
||||||
|
"group": resource.Group,
|
||||||
|
"version": resource.Version,
|
||||||
|
"resource": resource.Resource,
|
||||||
|
"subresource": "subresource",
|
||||||
|
"type": "admit",
|
||||||
|
"rejected": "false",
|
||||||
|
}
|
||||||
|
expectHistogramCountTotal(t, "apiserver_admission_step_admission_latencies_seconds", wantLabels, 1)
|
||||||
|
expectFindMetric(t, "apiserver_admission_step_admission_latencies_seconds_summary", wantLabels)
|
||||||
|
|
||||||
|
wantLabels["type"] = "validate"
|
||||||
|
expectHistogramCountTotal(t, "apiserver_admission_step_admission_latencies_seconds", wantLabels, 1)
|
||||||
|
expectFindMetric(t, "apiserver_admission_step_admission_latencies_seconds_summary", wantLabels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObserveAdmissionController(t *testing.T) {
|
||||||
|
Metrics.reset()
|
||||||
|
handler := WithControllerMetrics(&mutatingAndValidatingFakeHandler{admission.NewHandler(admission.Create), true, true}, "a")
|
||||||
|
handler.(admission.MutationInterface).Admit(attr)
|
||||||
|
handler.(admission.ValidationInterface).Validate(attr)
|
||||||
|
wantLabels := map[string]string{
|
||||||
|
"name": "a",
|
||||||
|
"operation": string(admission.Create),
|
||||||
|
"group": resource.Group,
|
||||||
|
"version": resource.Version,
|
||||||
|
"resource": resource.Resource,
|
||||||
|
"subresource": "subresource",
|
||||||
|
"type": "validate",
|
||||||
|
"rejected": "false",
|
||||||
|
}
|
||||||
|
expectHistogramCountTotal(t, "apiserver_admission_controller_admission_latencies_seconds", wantLabels, 1)
|
||||||
|
expectFindMetric(t, "apiserver_admission_controller_admission_latencies_seconds_summary", wantLabels)
|
||||||
|
|
||||||
|
wantLabels["type"] = "validate"
|
||||||
|
expectHistogramCountTotal(t, "apiserver_admission_controller_admission_latencies_seconds", wantLabels, 1)
|
||||||
|
expectFindMetric(t, "apiserver_admission_controller_admission_latencies_seconds_summary", wantLabels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObserveWebhook(t *testing.T) {
|
||||||
|
Metrics.reset()
|
||||||
|
Metrics.ObserveWebhook(2*time.Second, false, attr, stepAdmit, "x")
|
||||||
|
wantLabels := map[string]string{
|
||||||
|
"name": "x",
|
||||||
|
"operation": string(admission.Create),
|
||||||
|
"group": resource.Group,
|
||||||
|
"version": resource.Version,
|
||||||
|
"resource": resource.Resource,
|
||||||
|
"subresource": "subresource",
|
||||||
|
"type": "admit",
|
||||||
|
"rejected": "false",
|
||||||
|
}
|
||||||
|
expectHistogramCountTotal(t, "apiserver_admission_webhook_admission_latencies_seconds", wantLabels, 1)
|
||||||
|
expectFindMetric(t, "apiserver_admission_webhook_admission_latencies_seconds_summary", wantLabels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithMetrics(t *testing.T) {
|
||||||
|
Metrics.reset()
|
||||||
|
|
||||||
|
type Test struct {
|
||||||
|
name string
|
||||||
|
ns string
|
||||||
|
operation admission.Operation
|
||||||
|
handler admission.Interface
|
||||||
|
admit, validate bool
|
||||||
|
}
|
||||||
|
for _, test := range []Test{
|
||||||
|
{
|
||||||
|
"both-interfaces-admit-and-validate",
|
||||||
|
"some-ns",
|
||||||
|
admission.Create,
|
||||||
|
&mutatingAndValidatingFakeHandler{admission.NewHandler(admission.Create, admission.Update), true, true},
|
||||||
|
true, true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"both-interfaces-dont-admit",
|
||||||
|
"some-ns",
|
||||||
|
admission.Create,
|
||||||
|
&mutatingAndValidatingFakeHandler{admission.NewHandler(admission.Create, admission.Update), false, true},
|
||||||
|
false, true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"both-interfaces-admit-dont-validate",
|
||||||
|
"some-ns",
|
||||||
|
admission.Create,
|
||||||
|
&mutatingAndValidatingFakeHandler{admission.NewHandler(admission.Create, admission.Update), true, false},
|
||||||
|
true, false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"validate-interfaces-validate",
|
||||||
|
"some-ns",
|
||||||
|
admission.Create,
|
||||||
|
&validatingFakeHandler{admission.NewHandler(admission.Create, admission.Update), true},
|
||||||
|
true, true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"validate-interfaces-dont-validate",
|
||||||
|
"some-ns",
|
||||||
|
admission.Create,
|
||||||
|
&validatingFakeHandler{admission.NewHandler(admission.Create, admission.Update), true},
|
||||||
|
true, false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mutating-interfaces-admit",
|
||||||
|
"some-ns",
|
||||||
|
admission.Create,
|
||||||
|
&mutatingFakeHandler{admission.NewHandler(admission.Create, admission.Update), true},
|
||||||
|
true, true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mutating-interfaces-dont-admit",
|
||||||
|
"some-ns",
|
||||||
|
admission.Create,
|
||||||
|
&mutatingFakeHandler{admission.NewHandler(admission.Create, admission.Update), false},
|
||||||
|
true, false,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
Metrics.reset()
|
||||||
|
|
||||||
|
h := WithMetrics(test.handler, Metrics.ObserveAdmissionController, test.name)
|
||||||
|
|
||||||
|
// test mutation
|
||||||
|
err := h.(admission.MutationInterface).Admit(admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, test.ns, "", schema.GroupVersionResource{}, "", test.operation, nil))
|
||||||
|
if test.admit && err != nil {
|
||||||
|
t.Errorf("expected admit to succeed, but failed: %v", err)
|
||||||
|
continue
|
||||||
|
} else if !test.admit && err == nil {
|
||||||
|
t.Errorf("expected admit to fail, but it succeeded")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := map[string]string{"rejected": "false"}
|
||||||
|
if !test.admit {
|
||||||
|
filter["rejected"] = "true"
|
||||||
|
}
|
||||||
|
if _, mutating := test.handler.(admission.MutationInterface); mutating {
|
||||||
|
expectHistogramCountTotal(t, "apiserver_admission_controller_admission_latencies_seconds", filter, 1)
|
||||||
|
} else {
|
||||||
|
expectHistogramCountTotal(t, "apiserver_admission_controller_admission_latencies_seconds", filter, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
// skip validation step if mutation failed
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// test validation
|
||||||
|
err = h.(admission.ValidationInterface).Validate(admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, test.ns, "", schema.GroupVersionResource{}, "", test.operation, nil))
|
||||||
|
if test.validate && err != nil {
|
||||||
|
t.Errorf("expected admit to succeed, but failed: %v", err)
|
||||||
|
continue
|
||||||
|
} else if !test.validate && err == nil {
|
||||||
|
t.Errorf("expected validation to fail, but it succeeded")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
filter = map[string]string{"rejected": "false"}
|
||||||
|
if !test.admit {
|
||||||
|
filter["rejected"] = "true"
|
||||||
|
}
|
||||||
|
if _, validating := test.handler.(admission.ValidationInterface); validating {
|
||||||
|
expectHistogramCountTotal(t, "apiserver_admission_controller_admission_latencies_seconds", filter, 1)
|
||||||
|
} else {
|
||||||
|
expectHistogramCountTotal(t, "apiserver_admission_controller_admission_latencies_seconds", filter, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mutatingAndValidatingFakeHandler struct {
|
||||||
|
*admission.Handler
|
||||||
|
admit bool
|
||||||
|
validate bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *mutatingAndValidatingFakeHandler) Admit(a admission.Attributes) (err error) {
|
||||||
|
if h.admit {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("don't admit")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *mutatingAndValidatingFakeHandler) Validate(a admission.Attributes) (err error) {
|
||||||
|
if h.validate {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("don't validate")
|
||||||
|
}
|
||||||
|
|
||||||
|
type validatingFakeHandler struct {
|
||||||
|
*admission.Handler
|
||||||
|
validate bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *validatingFakeHandler) Validate(a admission.Attributes) (err error) {
|
||||||
|
if h.validate {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("don't validate")
|
||||||
|
}
|
||||||
|
|
||||||
|
type mutatingFakeHandler struct {
|
||||||
|
*admission.Handler
|
||||||
|
admit bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *mutatingFakeHandler) Amit(a admission.Attributes) (err error) {
|
||||||
|
if h.admit {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("don't admit")
|
||||||
|
}
|
@ -14,93 +14,15 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package admission
|
package metrics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
ptype "github.com/prometheus/client_model/go"
|
ptype "github.com/prometheus/client_model/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FakeHandler provide a mock implement both MutationInterface and ValidationInterface that tracks which
|
|
||||||
// methods have been called and always returns an error if admit is false.
|
|
||||||
type FakeHandler struct {
|
|
||||||
*Handler
|
|
||||||
name string
|
|
||||||
admit bool
|
|
||||||
admitCalled bool
|
|
||||||
validateCalled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *FakeHandler) Name() string {
|
|
||||||
return h.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *FakeHandler) Admit(a Attributes) (err error) {
|
|
||||||
h.admitCalled = true
|
|
||||||
if h.admit {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Don't admit")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *FakeHandler) Validate(a Attributes) (err error) {
|
|
||||||
h.validateCalled = true
|
|
||||||
if h.admit {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Don't admit")
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeHandler(admit bool, ops ...Operation) *FakeHandler {
|
|
||||||
return &FakeHandler{
|
|
||||||
admit: admit,
|
|
||||||
Handler: NewHandler(ops...),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeNamedHandler(name string, admit bool, ops ...Operation) NamedHandler {
|
|
||||||
return &FakeHandler{
|
|
||||||
name: name,
|
|
||||||
admit: admit,
|
|
||||||
Handler: NewHandler(ops...),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FakeValidatingHandler provide a mock of ValidationInterface that tracks which
|
|
||||||
// methods have been called and always returns an error if validate is false.
|
|
||||||
type FakeValidatingHandler struct {
|
|
||||||
*Handler
|
|
||||||
validate, validateCalled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *FakeValidatingHandler) Validate(a Attributes) (err error) {
|
|
||||||
h.validateCalled = true
|
|
||||||
if h.validate {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Don't validate")
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeValidatingHandler(validate bool, ops ...Operation) *FakeValidatingHandler {
|
|
||||||
return &FakeValidatingHandler{
|
|
||||||
validate: validate,
|
|
||||||
Handler: NewHandler(ops...),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeValidatingNamedHandler(name string, validate bool, ops ...Operation) NamedHandler {
|
|
||||||
return &pluginHandler{
|
|
||||||
Interface: &FakeValidatingHandler{
|
|
||||||
validate: validate,
|
|
||||||
Handler: NewHandler(ops...),
|
|
||||||
},
|
|
||||||
name: name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func labelsMatch(metric *ptype.Metric, labelFilter map[string]string) bool {
|
func labelsMatch(metric *ptype.Metric, labelFilter map[string]string) bool {
|
||||||
for _, lp := range metric.GetLabel() {
|
for _, lp := range metric.GetLabel() {
|
||||||
if value, ok := labelFilter[lp.GetName()]; ok && lp.GetValue() != value {
|
if value, ok := labelFilter[lp.GetName()]; ok && lp.GetValue() != value {
|
@ -1,84 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 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 admission
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"k8s.io/api/admissionregistration/v1alpha1"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
kind = schema.GroupVersionKind{Group: "kgroup", Version: "kversion", Kind: "kind"}
|
|
||||||
resource = schema.GroupVersionResource{Group: "rgroup", Version: "rversion", Resource: "resource"}
|
|
||||||
attr = NewAttributesRecord(nil, nil, kind, "ns", "name", resource, "subresource", Create, nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestObserveAdmissionStep(t *testing.T) {
|
|
||||||
Metrics.reset()
|
|
||||||
Metrics.ObserveAdmissionStep(2*time.Second, false, attr, "admit")
|
|
||||||
wantLabels := map[string]string{
|
|
||||||
"operation": string(Create),
|
|
||||||
"group": resource.Group,
|
|
||||||
"version": resource.Version,
|
|
||||||
"resource": resource.Resource,
|
|
||||||
"subresource": "subresource",
|
|
||||||
"type": "admit",
|
|
||||||
"rejected": "false",
|
|
||||||
}
|
|
||||||
expectHistogramCountTotal(t, "apiserver_admission_step_admission_latencies_seconds", wantLabels, 1)
|
|
||||||
expectFindMetric(t, "apiserver_admission_step_admission_latencies_seconds_summary", wantLabels)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestObserveAdmissionController(t *testing.T) {
|
|
||||||
Metrics.reset()
|
|
||||||
handler := makeValidatingNamedHandler("a", true, Create)
|
|
||||||
Metrics.ObserveAdmissionController(2*time.Second, false, handler, attr, "validate")
|
|
||||||
wantLabels := map[string]string{
|
|
||||||
"name": "a",
|
|
||||||
"operation": string(Create),
|
|
||||||
"group": resource.Group,
|
|
||||||
"version": resource.Version,
|
|
||||||
"resource": resource.Resource,
|
|
||||||
"subresource": "subresource",
|
|
||||||
"type": "validate",
|
|
||||||
"rejected": "false",
|
|
||||||
}
|
|
||||||
expectHistogramCountTotal(t, "apiserver_admission_controller_admission_latencies_seconds", wantLabels, 1)
|
|
||||||
expectFindMetric(t, "apiserver_admission_controller_admission_latencies_seconds_summary", wantLabels)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestObserveWebhook(t *testing.T) {
|
|
||||||
Metrics.reset()
|
|
||||||
hook := &v1alpha1.Webhook{Name: "x"}
|
|
||||||
Metrics.ObserveWebhook(2*time.Second, false, hook, attr)
|
|
||||||
wantLabels := map[string]string{
|
|
||||||
"name": "x",
|
|
||||||
"operation": string(Create),
|
|
||||||
"group": resource.Group,
|
|
||||||
"version": resource.Version,
|
|
||||||
"resource": resource.Resource,
|
|
||||||
"subresource": "subresource",
|
|
||||||
"type": "admit",
|
|
||||||
"rejected": "false",
|
|
||||||
}
|
|
||||||
expectHistogramCountTotal(t, "apiserver_admission_webhook_admission_latencies_seconds", wantLabels, 1)
|
|
||||||
expectFindMetric(t, "apiserver_admission_webhook_admission_latencies_seconds_summary", wantLabels)
|
|
||||||
}
|
|
@ -23,6 +23,7 @@ go_library(
|
|||||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/admission/configuration:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/admission/configuration:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/admission/metrics:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:go_default_library",
|
||||||
|
@ -39,6 +39,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/apiserver/pkg/admission/configuration"
|
"k8s.io/apiserver/pkg/admission/configuration"
|
||||||
genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
|
genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
|
||||||
|
admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/config"
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/config"
|
||||||
webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
|
webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/namespace"
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/namespace"
|
||||||
@ -238,7 +239,7 @@ func (a *MutatingWebhook) Admit(attr admission.Attributes) error {
|
|||||||
for _, hook := range relevantHooks {
|
for _, hook := range relevantHooks {
|
||||||
t := time.Now()
|
t := time.Now()
|
||||||
err := a.callAttrMutatingHook(ctx, hook, versionedAttr)
|
err := a.callAttrMutatingHook(ctx, hook, versionedAttr)
|
||||||
admission.Metrics.ObserveWebhook(time.Since(t), err != nil, hook, attr)
|
admissionmetrics.Metrics.ObserveWebhook(time.Since(t), err != nil, attr, "admit", hook.Name)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ go_library(
|
|||||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/admission/configuration:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/admission/configuration:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/admission/metrics:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:go_default_library",
|
||||||
|
@ -38,6 +38,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/apiserver/pkg/admission/configuration"
|
"k8s.io/apiserver/pkg/admission/configuration"
|
||||||
genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
|
genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
|
||||||
|
admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/config"
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/config"
|
||||||
webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
|
webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/namespace"
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/namespace"
|
||||||
@ -238,7 +239,7 @@ func (a *ValidatingAdmissionWebhook) Admit(attr admission.Attributes) error {
|
|||||||
|
|
||||||
t := time.Now()
|
t := time.Now()
|
||||||
err := a.callHook(ctx, hook, versionedAttr)
|
err := a.callHook(ctx, hook, versionedAttr)
|
||||||
admission.Metrics.ObserveWebhook(time.Since(t), err != nil, hook, attr)
|
admissionmetrics.Metrics.ObserveWebhook(time.Since(t), err != nil, attr, "validating", hook.Name)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -39,16 +39,6 @@ type Plugins struct {
|
|||||||
registry map[string]Factory
|
registry map[string]Factory
|
||||||
}
|
}
|
||||||
|
|
||||||
// pluginHandler associates name with a admission.Interface handler.
|
|
||||||
type pluginHandler struct {
|
|
||||||
Interface
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *pluginHandler) Name() string {
|
|
||||||
return h.name
|
|
||||||
}
|
|
||||||
|
|
||||||
// All registered admission options.
|
// All registered admission options.
|
||||||
var (
|
var (
|
||||||
// PluginEnabledFn checks whether a plugin is enabled. By default, if you ask about it, it's enabled.
|
// PluginEnabledFn checks whether a plugin is enabled. By default, if you ask about it, it's enabled.
|
||||||
@ -128,10 +118,12 @@ func splitStream(config io.Reader) (io.Reader, io.Reader, error) {
|
|||||||
return bytes.NewBuffer(configBytes), bytes.NewBuffer(configBytes), nil
|
return bytes.NewBuffer(configBytes), bytes.NewBuffer(configBytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Decorator func(handler Interface, name string) Interface
|
||||||
|
|
||||||
// NewFromPlugins returns an admission.Interface that will enforce admission control decisions of all
|
// NewFromPlugins returns an admission.Interface that will enforce admission control decisions of all
|
||||||
// the given plugins.
|
// the given plugins.
|
||||||
func (ps *Plugins) NewFromPlugins(pluginNames []string, configProvider ConfigProvider, pluginInitializer PluginInitializer) (Interface, error) {
|
func (ps *Plugins) NewFromPlugins(pluginNames []string, configProvider ConfigProvider, pluginInitializer PluginInitializer, decorator Decorator) (Interface, error) {
|
||||||
handlers := []NamedHandler{}
|
handlers := []Interface{}
|
||||||
for _, pluginName := range pluginNames {
|
for _, pluginName := range pluginNames {
|
||||||
pluginConfig, err := configProvider.ConfigFor(pluginName)
|
pluginConfig, err := configProvider.ConfigFor(pluginName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -143,8 +135,11 @@ func (ps *Plugins) NewFromPlugins(pluginNames []string, configProvider ConfigPro
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if plugin != nil {
|
if plugin != nil {
|
||||||
handler := &pluginHandler{Interface: plugin, name: pluginName}
|
if decorator != nil {
|
||||||
handlers = append(handlers, handler)
|
handlers = append(handlers, decorator(plugin, pluginName))
|
||||||
|
} else {
|
||||||
|
handlers = append(handlers, plugin)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return chainAdmissionHandler(handlers), nil
|
return chainAdmissionHandler(handlers), nil
|
||||||
|
@ -30,6 +30,7 @@ go_library(
|
|||||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/admission/metrics:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/admission/plugin/initialization:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/admission/plugin/initialization:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating:go_default_library",
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/apiserver/pkg/admission/initializer"
|
"k8s.io/apiserver/pkg/admission/initializer"
|
||||||
|
admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/initialization"
|
"k8s.io/apiserver/pkg/admission/plugin/initialization"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle"
|
"k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle"
|
||||||
mutatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating"
|
mutatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating"
|
||||||
@ -105,12 +106,12 @@ func (a *AdmissionOptions) ApplyTo(
|
|||||||
pluginInitializers = append(pluginInitializers, genericInitializer)
|
pluginInitializers = append(pluginInitializers, genericInitializer)
|
||||||
initializersChain = append(initializersChain, pluginInitializers...)
|
initializersChain = append(initializersChain, pluginInitializers...)
|
||||||
|
|
||||||
admissionChain, err := a.Plugins.NewFromPlugins(pluginNames, pluginsConfigProvider, initializersChain)
|
admissionChain, err := a.Plugins.NewFromPlugins(pluginNames, pluginsConfigProvider, initializersChain, admissionmetrics.WithControllerMetrics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.AdmissionControl = admissionChain
|
c.AdmissionControl = admissionmetrics.WithStepMetrics(admissionChain)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -834,6 +834,10 @@
|
|||||||
"ImportPath": "k8s.io/apiserver/pkg/admission/initializer",
|
"ImportPath": "k8s.io/apiserver/pkg/admission/initializer",
|
||||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "k8s.io/apiserver/pkg/admission/metrics",
|
||||||
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apiserver/pkg/admission/plugin/initialization",
|
"ImportPath": "k8s.io/apiserver/pkg/admission/plugin/initialization",
|
||||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
@ -830,6 +830,10 @@
|
|||||||
"ImportPath": "k8s.io/apiserver/pkg/admission/initializer",
|
"ImportPath": "k8s.io/apiserver/pkg/admission/initializer",
|
||||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "k8s.io/apiserver/pkg/admission/metrics",
|
||||||
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apiserver/pkg/admission/plugin/initialization",
|
"ImportPath": "k8s.io/apiserver/pkg/admission/plugin/initialization",
|
||||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
Loading…
Reference in New Issue
Block a user