Replace godep with dep
This commit is contained in:
parent
1e7489927c
commit
bf5616c65b
14883 changed files with 3937406 additions and 361781 deletions
58
vendor/k8s.io/apiserver/plugin/pkg/audit/webhook/BUILD
generated
vendored
Normal file
58
vendor/k8s.io/apiserver/plugin/pkg/audit/webhook/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"webhook_test.go",
|
||||
"webhook_v1alpha1_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/require: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/serializer/json:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/audit:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd/api/v1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["webhook.go"],
|
||||
deps = [
|
||||
"//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/audit/install:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/audit:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/webhook:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
361
vendor/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook.go
generated
vendored
Normal file
361
vendor/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook.go
generated
vendored
Normal file
|
|
@ -0,0 +1,361 @@
|
|||
/*
|
||||
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 webhook implements the audit.Backend interface using HTTP webhooks.
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apimachinery/announced"
|
||||
"k8s.io/apimachinery/pkg/apimachinery/registered"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/apis/audit/install"
|
||||
auditv1alpha1 "k8s.io/apiserver/pkg/apis/audit/v1alpha1"
|
||||
auditv1beta1 "k8s.io/apiserver/pkg/apis/audit/v1beta1"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
)
|
||||
|
||||
const (
|
||||
// ModeBatch indicates that the webhook should buffer audit events
|
||||
// internally, sending batch updates either once a certain number of
|
||||
// events have been received or a certain amount of time has passed.
|
||||
ModeBatch = "batch"
|
||||
// ModeBlocking causes the webhook to block on every attempt to process
|
||||
// a set of events. This causes requests to the API server to wait for a
|
||||
// round trip to the external audit service before sending a response.
|
||||
ModeBlocking = "blocking"
|
||||
)
|
||||
|
||||
// AllowedModes is the modes known by this webhook.
|
||||
var AllowedModes = []string{
|
||||
ModeBatch,
|
||||
ModeBlocking,
|
||||
}
|
||||
|
||||
const (
|
||||
// Default configuration values for ModeBatch.
|
||||
//
|
||||
// TODO(ericchiang): Make these value configurable. Maybe through a
|
||||
// kubeconfig extension?
|
||||
defaultBatchBufferSize = 1000 // Buffer up to 1000 events before blocking.
|
||||
defaultBatchMaxSize = 100 // Only send 100 events at a time.
|
||||
defaultBatchMaxWait = time.Minute // Send events at least once a minute.
|
||||
)
|
||||
|
||||
// The plugin name reported in error metrics.
|
||||
const pluginName = "webhook"
|
||||
|
||||
// NewBackend returns an audit backend that sends events over HTTP to an external service.
|
||||
// The mode indicates the caching behavior of the webhook. Either blocking (ModeBlocking)
|
||||
// or buffered with batch POSTs (ModeBatch).
|
||||
func NewBackend(kubeConfigFile string, mode string, groupVersion schema.GroupVersion) (audit.Backend, error) {
|
||||
switch mode {
|
||||
case ModeBatch:
|
||||
return newBatchWebhook(kubeConfigFile, groupVersion)
|
||||
case ModeBlocking:
|
||||
return newBlockingWebhook(kubeConfigFile, groupVersion)
|
||||
default:
|
||||
return nil, fmt.Errorf("webhook mode %q is not in list of known modes (%s)",
|
||||
mode, strings.Join(AllowedModes, ","))
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// NOTE: Copied from other webhook implementations
|
||||
//
|
||||
// Can we make these passable to NewGenericWebhook?
|
||||
groupFactoryRegistry = make(announced.APIGroupFactoryRegistry)
|
||||
// TODO(audit): figure out a general way to let the client choose their preferred version
|
||||
registry = registered.NewOrDie("")
|
||||
)
|
||||
|
||||
func init() {
|
||||
allGVs := []schema.GroupVersion{auditv1alpha1.SchemeGroupVersion, auditv1beta1.SchemeGroupVersion}
|
||||
registry.RegisterVersions(allGVs)
|
||||
if err := registry.EnableVersions(allGVs...); err != nil {
|
||||
panic(fmt.Sprintf("failed to enable version %v", allGVs))
|
||||
}
|
||||
install.Install(groupFactoryRegistry, registry, audit.Scheme)
|
||||
}
|
||||
|
||||
func loadWebhook(configFile string, groupVersion schema.GroupVersion) (*webhook.GenericWebhook, error) {
|
||||
return webhook.NewGenericWebhook(registry, audit.Codecs, configFile, []schema.GroupVersion{groupVersion}, 0)
|
||||
}
|
||||
|
||||
func newBlockingWebhook(configFile string, groupVersion schema.GroupVersion) (*blockingBackend, error) {
|
||||
w, err := loadWebhook(configFile, groupVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &blockingBackend{w}, nil
|
||||
}
|
||||
|
||||
type blockingBackend struct {
|
||||
w *webhook.GenericWebhook
|
||||
}
|
||||
|
||||
func (b *blockingBackend) Run(stopCh <-chan struct{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *blockingBackend) Shutdown() {
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
func (b *blockingBackend) ProcessEvents(ev ...*auditinternal.Event) {
|
||||
if err := b.processEvents(ev...); err != nil {
|
||||
audit.HandlePluginError(pluginName, err, ev...)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *blockingBackend) processEvents(ev ...*auditinternal.Event) error {
|
||||
var list auditinternal.EventList
|
||||
for _, e := range ev {
|
||||
list.Items = append(list.Items, *e)
|
||||
}
|
||||
// NOTE: No exponential backoff because this is the blocking webhook
|
||||
// mode. Any attempts to retry will block API server requests.
|
||||
return b.w.RestClient.Post().Body(&list).Do().Error()
|
||||
}
|
||||
|
||||
func newBatchWebhook(configFile string, groupVersion schema.GroupVersion) (*batchBackend, error) {
|
||||
w, err := loadWebhook(configFile, groupVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &batchBackend{
|
||||
w: w,
|
||||
buffer: make(chan *auditinternal.Event, defaultBatchBufferSize),
|
||||
maxBatchSize: defaultBatchMaxSize,
|
||||
maxBatchWait: defaultBatchMaxWait,
|
||||
shutdownCh: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type batchBackend struct {
|
||||
w *webhook.GenericWebhook
|
||||
|
||||
// Channel to buffer events in memory before sending them on the webhook.
|
||||
buffer chan *auditinternal.Event
|
||||
// Maximum number of events that can be sent at once.
|
||||
maxBatchSize int
|
||||
// Amount of time to wait after sending events before force sending another set.
|
||||
//
|
||||
// Receiving maxBatchSize events will always trigger a send, regardless of
|
||||
// if this amount of time has been reached.
|
||||
maxBatchWait time.Duration
|
||||
|
||||
// Channel to signal that the sending routine has stopped and therefore
|
||||
// it's safe to assume that no new requests will be initiated.
|
||||
shutdownCh chan struct{}
|
||||
|
||||
// The sending routine locks reqMutex for reading before initiating a new
|
||||
// goroutine to send a request. This goroutine then unlocks reqMutex for
|
||||
// reading when completed. The Shutdown method locks reqMutex for writing
|
||||
// after the sending routine has exited. When reqMutex is locked for writing,
|
||||
// all requests have been completed and no new will be spawned, since the
|
||||
// sending routine is not running anymore.
|
||||
reqMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (b *batchBackend) Run(stopCh <-chan struct{}) error {
|
||||
go func() {
|
||||
// Signal that the sending routine has exited.
|
||||
defer close(b.shutdownCh)
|
||||
|
||||
b.runSendingRoutine(stopCh)
|
||||
|
||||
// Handle the events that were received after the last buffer
|
||||
// scraping and before this line. Since the buffer is closed, no new
|
||||
// events will come through.
|
||||
for {
|
||||
if last := func() bool {
|
||||
// Recover from any panic in order to try to send all remaining events.
|
||||
// Note, that in case of a panic, the return value will be false and
|
||||
// the loop execution will continue.
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
events := b.collectLastEvents()
|
||||
b.sendBatchEvents(events)
|
||||
return len(events) == 0
|
||||
}(); last {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *batchBackend) Shutdown() {
|
||||
<-b.shutdownCh
|
||||
|
||||
// Write locking reqMutex will guarantee that all requests will be completed
|
||||
// by the time the goroutine continues the execution. Since this line is
|
||||
// executed after shutdownCh was closed, no new requests will follow this
|
||||
// lock, because read lock is called in the same goroutine that closes
|
||||
// shutdownCh before exiting.
|
||||
b.reqMutex.Lock()
|
||||
b.reqMutex.Unlock()
|
||||
}
|
||||
|
||||
// runSendingRoutine runs a loop that collects events from the buffer. When
|
||||
// stopCh is closed, runSendingRoutine stops and closes the buffer.
|
||||
func (b *batchBackend) runSendingRoutine(stopCh <-chan struct{}) {
|
||||
defer close(b.buffer)
|
||||
|
||||
for {
|
||||
func() {
|
||||
// Recover from any panics caused by this function so a panic in the
|
||||
// goroutine can't bring down the main routine.
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
t := time.NewTimer(b.maxBatchWait)
|
||||
defer t.Stop() // Release ticker resources
|
||||
|
||||
b.sendBatchEvents(b.collectEvents(stopCh, t.C))
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-stopCh:
|
||||
return
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collectEvents attempts to collect some number of events in a batch.
|
||||
//
|
||||
// The following things can cause collectEvents to stop and return the list
|
||||
// of events:
|
||||
//
|
||||
// * Some maximum number of events are received.
|
||||
// * Timer has passed, all queued events are sent.
|
||||
// * StopCh is closed, all queued events are sent.
|
||||
//
|
||||
func (b *batchBackend) collectEvents(stopCh <-chan struct{}, timer <-chan time.Time) []auditinternal.Event {
|
||||
var events []auditinternal.Event
|
||||
|
||||
L:
|
||||
for i := 0; i < b.maxBatchSize; i++ {
|
||||
select {
|
||||
case ev, ok := <-b.buffer:
|
||||
// Buffer channel was closed and no new events will follow.
|
||||
if !ok {
|
||||
break L
|
||||
}
|
||||
events = append(events, *ev)
|
||||
case <-timer:
|
||||
// Timer has expired. Send whatever events are in the queue.
|
||||
break L
|
||||
case <-stopCh:
|
||||
// Webhook has shut down. Send the last events.
|
||||
break L
|
||||
}
|
||||
}
|
||||
|
||||
return events
|
||||
}
|
||||
|
||||
// collectLastEvents assumes that the buffer was closed. It collects the first
|
||||
// maxBatchSize events from the closed buffer into a batch and returns them.
|
||||
func (b *batchBackend) collectLastEvents() []auditinternal.Event {
|
||||
var events []auditinternal.Event
|
||||
|
||||
for i := 0; i < b.maxBatchSize; i++ {
|
||||
ev, ok := <-b.buffer
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
events = append(events, *ev)
|
||||
}
|
||||
|
||||
return events
|
||||
}
|
||||
|
||||
// sendBatchEvents sends a POST requests with the event list in a goroutine
|
||||
// and logs any error encountered.
|
||||
func (b *batchBackend) sendBatchEvents(events []auditinternal.Event) {
|
||||
if len(events) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
list := auditinternal.EventList{Items: events}
|
||||
|
||||
// Locking reqMutex for read will guarantee that the shutdown process will
|
||||
// block until the goroutine started below is finished. At the same time, it
|
||||
// will not prevent other batches from being proceed further this point.
|
||||
b.reqMutex.RLock()
|
||||
go func() {
|
||||
// Execute the webhook POST in a goroutine to keep it from blocking.
|
||||
// This lets the webhook continue to drain the queue immediatly.
|
||||
|
||||
defer b.reqMutex.RUnlock()
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
err := webhook.WithExponentialBackoff(0, func() error {
|
||||
return b.w.RestClient.Post().Body(&list).Do().Error()
|
||||
})
|
||||
if err != nil {
|
||||
impacted := make([]*auditinternal.Event, len(events))
|
||||
for i := range events {
|
||||
impacted[i] = &events[i]
|
||||
}
|
||||
audit.HandlePluginError(pluginName, err, impacted...)
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
func (b *batchBackend) ProcessEvents(ev ...*auditinternal.Event) {
|
||||
for i, e := range ev {
|
||||
// Per the audit.Backend interface these events are reused after being
|
||||
// sent to the Sink. Deep copy and send the copy to the queue.
|
||||
event := e.DeepCopy()
|
||||
|
||||
// The following mechanism is in place to support the situation when audit
|
||||
// events are still coming after the backend was shut down.
|
||||
var sendErr error
|
||||
func() {
|
||||
// If the backend was shut down and the buffer channel was closed, an
|
||||
// attempt to add an event to it will result in panic that we should
|
||||
// recover from.
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
sendErr = errors.New("audit webhook shut down")
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case b.buffer <- event:
|
||||
default:
|
||||
sendErr = errors.New("audit webhook queue blocked")
|
||||
}
|
||||
}()
|
||||
if sendErr != nil {
|
||||
audit.HandlePluginError(pluginName, sendErr, ev[i:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
405
vendor/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook_test.go
generated
vendored
Normal file
405
vendor/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,405 @@
|
|||
/*
|
||||
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 webhook
|
||||
|
||||
import (
|
||||
stdjson "encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
auditv1beta1 "k8s.io/apiserver/pkg/apis/audit/v1beta1"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/client-go/tools/clientcmd/api/v1"
|
||||
)
|
||||
|
||||
// newWebhookHandler returns a handler which recieves webhook events and decodes the
|
||||
// request body. The caller passes a callback which is called on each webhook POST.
|
||||
// The object passed to cb is of the same type as list.
|
||||
func newWebhookHandler(t *testing.T, list runtime.Object, cb func(events runtime.Object)) http.Handler {
|
||||
s := json.NewSerializer(json.DefaultMetaFactory, audit.Scheme, audit.Scheme, false)
|
||||
return &testWebhookHandler{
|
||||
t: t,
|
||||
list: list,
|
||||
onEvents: cb,
|
||||
serializer: s,
|
||||
}
|
||||
}
|
||||
|
||||
type testWebhookHandler struct {
|
||||
t *testing.T
|
||||
|
||||
list runtime.Object
|
||||
onEvents func(events runtime.Object)
|
||||
|
||||
serializer runtime.Serializer
|
||||
}
|
||||
|
||||
func (t *testWebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
err := func() error {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read webhook request body: %v", err)
|
||||
}
|
||||
|
||||
obj, _, err := t.serializer.Decode(body, nil, t.list.DeepCopyObject())
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode request body: %v", err)
|
||||
}
|
||||
if reflect.TypeOf(obj).Elem() != reflect.TypeOf(t.list).Elem() {
|
||||
return fmt.Errorf("expected %T, got %T", t.list, obj)
|
||||
}
|
||||
t.onEvents(obj)
|
||||
return nil
|
||||
}()
|
||||
|
||||
if err == nil {
|
||||
io.WriteString(w, "{}")
|
||||
return
|
||||
}
|
||||
// In a goroutine, can't call Fatal.
|
||||
assert.NoError(t.t, err, "failed to read request body")
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func newTestBlockingWebhook(t *testing.T, endpoint string, groupVersion schema.GroupVersion) *blockingBackend {
|
||||
return newWebhook(t, endpoint, ModeBlocking, groupVersion).(*blockingBackend)
|
||||
}
|
||||
|
||||
func newTestBatchWebhook(t *testing.T, endpoint string, groupVersion schema.GroupVersion) *batchBackend {
|
||||
return newWebhook(t, endpoint, ModeBatch, groupVersion).(*batchBackend)
|
||||
}
|
||||
|
||||
func newWebhook(t *testing.T, endpoint string, mode string, groupVersion schema.GroupVersion) audit.Backend {
|
||||
config := v1.Config{
|
||||
Clusters: []v1.NamedCluster{
|
||||
{Cluster: v1.Cluster{Server: endpoint, InsecureSkipTLSVerify: true}},
|
||||
},
|
||||
}
|
||||
f, err := ioutil.TempFile("", "k8s_audit_webhook_test_")
|
||||
require.NoError(t, err, "creating temp file")
|
||||
|
||||
defer func() {
|
||||
f.Close()
|
||||
os.Remove(f.Name())
|
||||
}()
|
||||
|
||||
// NOTE(ericchiang): Do we need to use a proper serializer?
|
||||
require.NoError(t, stdjson.NewEncoder(f).Encode(config), "writing kubeconfig")
|
||||
|
||||
backend, err := NewBackend(f.Name(), mode, groupVersion)
|
||||
require.NoError(t, err, "initializing backend")
|
||||
|
||||
return backend
|
||||
}
|
||||
|
||||
func TestWebhook(t *testing.T) {
|
||||
gotEvents := false
|
||||
defer func() { require.True(t, gotEvents, "no events received") }()
|
||||
|
||||
s := httptest.NewServer(newWebhookHandler(t, &auditv1beta1.EventList{}, func(events runtime.Object) {
|
||||
gotEvents = true
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
backend := newTestBlockingWebhook(t, s.URL, auditv1beta1.SchemeGroupVersion)
|
||||
|
||||
// Ensure this doesn't return a serialization error.
|
||||
event := &auditinternal.Event{}
|
||||
require.NoError(t, backend.processEvents(event), "failed to send events")
|
||||
}
|
||||
|
||||
// waitForEmptyBuffer indicates when the sendBatchEvents method has read from the
|
||||
// existing buffer. This lets test coordinate closing a timer and stop channel
|
||||
// until the for loop has read from the buffer.
|
||||
func waitForEmptyBuffer(b *batchBackend) {
|
||||
for len(b.buffer) != 0 {
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchWebhookMaxEvents(t *testing.T) {
|
||||
nRest := 10
|
||||
events := make([]*auditinternal.Event, defaultBatchMaxSize+nRest) // greater than max size.
|
||||
for i := range events {
|
||||
events[i] = &auditinternal.Event{}
|
||||
}
|
||||
|
||||
got := make(chan int, 2)
|
||||
s := httptest.NewServer(newWebhookHandler(t, &auditv1beta1.EventList{}, func(events runtime.Object) {
|
||||
got <- len(events.(*auditv1beta1.EventList).Items)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
backend := newTestBatchWebhook(t, s.URL, auditv1beta1.SchemeGroupVersion)
|
||||
|
||||
backend.ProcessEvents(events...)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
timer := make(chan time.Time, 1)
|
||||
|
||||
backend.sendBatchEvents(backend.collectEvents(stopCh, timer))
|
||||
require.Equal(t, defaultBatchMaxSize, <-got, "did not get batch max size")
|
||||
|
||||
go func() {
|
||||
waitForEmptyBuffer(backend) // wait for the buffer to empty
|
||||
timer <- time.Now() // Trigger the wait timeout
|
||||
}()
|
||||
|
||||
backend.sendBatchEvents(backend.collectEvents(stopCh, timer))
|
||||
require.Equal(t, nRest, <-got, "failed to get the rest of the events")
|
||||
}
|
||||
|
||||
func TestBatchWebhookStopCh(t *testing.T) {
|
||||
events := make([]*auditinternal.Event, 1) // less than max size.
|
||||
for i := range events {
|
||||
events[i] = &auditinternal.Event{}
|
||||
}
|
||||
|
||||
expected := len(events)
|
||||
got := make(chan int, 2)
|
||||
s := httptest.NewServer(newWebhookHandler(t, &auditv1beta1.EventList{}, func(events runtime.Object) {
|
||||
got <- len(events.(*auditv1beta1.EventList).Items)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
backend := newTestBatchWebhook(t, s.URL, auditv1beta1.SchemeGroupVersion)
|
||||
backend.ProcessEvents(events...)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
timer := make(chan time.Time)
|
||||
|
||||
go func() {
|
||||
waitForEmptyBuffer(backend)
|
||||
close(stopCh) // stop channel has stopped
|
||||
}()
|
||||
backend.sendBatchEvents(backend.collectEvents(stopCh, timer))
|
||||
require.Equal(t, expected, <-got, "get queued events after timer expires")
|
||||
}
|
||||
|
||||
func TestBatchWebhookProcessEventsAfterStop(t *testing.T) {
|
||||
events := make([]*auditinternal.Event, 1) // less than max size.
|
||||
for i := range events {
|
||||
events[i] = &auditinternal.Event{}
|
||||
}
|
||||
|
||||
got := make(chan struct{})
|
||||
s := httptest.NewServer(newWebhookHandler(t, &auditv1beta1.EventList{}, func(events runtime.Object) {
|
||||
close(got)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
backend := newTestBatchWebhook(t, s.URL, auditv1beta1.SchemeGroupVersion)
|
||||
stopCh := make(chan struct{})
|
||||
|
||||
backend.Run(stopCh)
|
||||
close(stopCh)
|
||||
<-backend.shutdownCh
|
||||
backend.ProcessEvents(events...)
|
||||
assert.Equal(t, 0, len(backend.buffer), "processed events after the backed has been stopped")
|
||||
}
|
||||
|
||||
func TestBatchWebhookShutdown(t *testing.T) {
|
||||
events := make([]*auditinternal.Event, 1)
|
||||
for i := range events {
|
||||
events[i] = &auditinternal.Event{}
|
||||
}
|
||||
|
||||
got := make(chan struct{})
|
||||
contReqCh := make(chan struct{})
|
||||
shutdownCh := make(chan struct{})
|
||||
s := httptest.NewServer(newWebhookHandler(t, &auditv1beta1.EventList{}, func(events runtime.Object) {
|
||||
close(got)
|
||||
<-contReqCh
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
backend := newTestBatchWebhook(t, s.URL, auditv1beta1.SchemeGroupVersion)
|
||||
backend.ProcessEvents(events...)
|
||||
|
||||
go func() {
|
||||
// Assume stopCh was closed.
|
||||
close(backend.buffer)
|
||||
backend.sendBatchEvents(backend.collectLastEvents())
|
||||
}()
|
||||
|
||||
<-got
|
||||
|
||||
go func() {
|
||||
close(backend.shutdownCh)
|
||||
backend.Shutdown()
|
||||
close(shutdownCh)
|
||||
}()
|
||||
|
||||
// Wait for some time in case there's a bug that allows for the Shutdown
|
||||
// method to exit before all requests has been completed.
|
||||
time.Sleep(1 * time.Second)
|
||||
select {
|
||||
case <-shutdownCh:
|
||||
t.Fatal("Backend shut down before all requests finished")
|
||||
default:
|
||||
// Continue.
|
||||
}
|
||||
|
||||
close(contReqCh)
|
||||
<-shutdownCh
|
||||
}
|
||||
|
||||
func TestBatchWebhookEmptyBuffer(t *testing.T) {
|
||||
events := make([]*auditinternal.Event, 1) // less than max size.
|
||||
for i := range events {
|
||||
events[i] = &auditinternal.Event{}
|
||||
}
|
||||
|
||||
expected := len(events)
|
||||
got := make(chan int, 2)
|
||||
s := httptest.NewServer(newWebhookHandler(t, &auditv1beta1.EventList{}, func(events runtime.Object) {
|
||||
got <- len(events.(*auditv1beta1.EventList).Items)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
backend := newTestBatchWebhook(t, s.URL, auditv1beta1.SchemeGroupVersion)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
timer := make(chan time.Time, 1)
|
||||
|
||||
timer <- time.Now() // Timer is done.
|
||||
|
||||
// Buffer is empty, no events have been queued. This should exit but send no events.
|
||||
backend.sendBatchEvents(backend.collectEvents(stopCh, timer))
|
||||
|
||||
// Send additional events after the sendBatchEvents has been called.
|
||||
backend.ProcessEvents(events...)
|
||||
go func() {
|
||||
waitForEmptyBuffer(backend)
|
||||
timer <- time.Now()
|
||||
}()
|
||||
|
||||
backend.sendBatchEvents(backend.collectEvents(stopCh, timer))
|
||||
|
||||
// Make sure we didn't get a POST with zero events.
|
||||
require.Equal(t, expected, <-got, "expected one event")
|
||||
}
|
||||
|
||||
func TestBatchBufferFull(t *testing.T) {
|
||||
events := make([]*auditinternal.Event, defaultBatchBufferSize+1) // More than buffered size
|
||||
for i := range events {
|
||||
events[i] = &auditinternal.Event{}
|
||||
}
|
||||
s := httptest.NewServer(newWebhookHandler(t, &auditv1beta1.EventList{}, func(events runtime.Object) {
|
||||
// Do nothing.
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
backend := newTestBatchWebhook(t, s.URL, auditv1beta1.SchemeGroupVersion)
|
||||
|
||||
// Make sure this doesn't block.
|
||||
backend.ProcessEvents(events...)
|
||||
}
|
||||
|
||||
func TestBatchRun(t *testing.T) {
|
||||
|
||||
// Divisable by max batch size so we don't have to wait for a minute for
|
||||
// the test to finish.
|
||||
events := make([]*auditinternal.Event, defaultBatchMaxSize*3)
|
||||
for i := range events {
|
||||
events[i] = &auditinternal.Event{}
|
||||
}
|
||||
|
||||
got := new(int64)
|
||||
want := len(events)
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(want)
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
// When the expected number of events have been received, close the channel.
|
||||
close(done)
|
||||
}()
|
||||
|
||||
s := httptest.NewServer(newWebhookHandler(t, &auditv1beta1.EventList{}, func(obj runtime.Object) {
|
||||
events := obj.(*auditv1beta1.EventList)
|
||||
atomic.AddInt64(got, int64(len(events.Items)))
|
||||
wg.Add(-len(events.Items))
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
|
||||
backend := newTestBatchWebhook(t, s.URL, auditv1beta1.SchemeGroupVersion)
|
||||
|
||||
// Test the Run codepath. E.g. that the spawned goroutines behave correctly.
|
||||
backend.Run(stopCh)
|
||||
|
||||
backend.ProcessEvents(events...)
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
// Received all the events.
|
||||
case <-time.After(2 * time.Minute):
|
||||
t.Errorf("expected %d events got %d", want, atomic.LoadInt64(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchConcurrentRequests(t *testing.T) {
|
||||
events := make([]*auditinternal.Event, defaultBatchBufferSize) // Don't drop events
|
||||
for i := range events {
|
||||
events[i] = &auditinternal.Event{}
|
||||
}
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(len(events))
|
||||
|
||||
s := httptest.NewServer(newWebhookHandler(t, &auditv1beta1.EventList{}, func(events runtime.Object) {
|
||||
wg.Add(-len(events.(*auditv1beta1.EventList).Items))
|
||||
|
||||
// Since the webhook makes concurrent requests, blocking on the webhook response
|
||||
// shouldn't block the webhook from sending more events.
|
||||
//
|
||||
// Wait for all responses to be received before sending the response.
|
||||
wg.Wait()
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
|
||||
backend := newTestBatchWebhook(t, s.URL, auditv1beta1.SchemeGroupVersion)
|
||||
backend.Run(stopCh)
|
||||
|
||||
backend.ProcessEvents(events...)
|
||||
// Wait for the webhook to receive all events.
|
||||
wg.Wait()
|
||||
}
|
||||
289
vendor/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook_v1alpha1_test.go
generated
vendored
Normal file
289
vendor/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook_v1alpha1_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
/*
|
||||
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 webhook
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
auditv1alpha1 "k8s.io/apiserver/pkg/apis/audit/v1alpha1"
|
||||
)
|
||||
|
||||
func TestBatchWebhookMaxEventsV1Alpha1(t *testing.T) {
|
||||
nRest := 10
|
||||
events := make([]*auditinternal.Event, defaultBatchMaxSize+nRest) // greater than max size.
|
||||
for i := range events {
|
||||
events[i] = &auditinternal.Event{}
|
||||
}
|
||||
|
||||
got := make(chan int, 2)
|
||||
s := httptest.NewServer(newWebhookHandler(t, &auditv1alpha1.EventList{}, func(events runtime.Object) {
|
||||
got <- len(events.(*auditv1alpha1.EventList).Items)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
backend := newTestBatchWebhook(t, s.URL, auditv1alpha1.SchemeGroupVersion)
|
||||
|
||||
backend.ProcessEvents(events...)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
timer := make(chan time.Time, 1)
|
||||
|
||||
backend.sendBatchEvents(backend.collectEvents(stopCh, timer))
|
||||
require.Equal(t, defaultBatchMaxSize, <-got, "did not get batch max size")
|
||||
|
||||
go func() {
|
||||
waitForEmptyBuffer(backend) // wait for the buffer to empty
|
||||
timer <- time.Now() // Trigger the wait timeout
|
||||
}()
|
||||
|
||||
backend.sendBatchEvents(backend.collectEvents(stopCh, timer))
|
||||
require.Equal(t, nRest, <-got, "failed to get the rest of the events")
|
||||
}
|
||||
|
||||
func TestBatchWebhookStopChV1Alpha1(t *testing.T) {
|
||||
events := make([]*auditinternal.Event, 1) // less than max size.
|
||||
for i := range events {
|
||||
events[i] = &auditinternal.Event{}
|
||||
}
|
||||
|
||||
expected := len(events)
|
||||
got := make(chan int, 2)
|
||||
s := httptest.NewServer(newWebhookHandler(t, &auditv1alpha1.EventList{}, func(events runtime.Object) {
|
||||
got <- len(events.(*auditv1alpha1.EventList).Items)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
backend := newTestBatchWebhook(t, s.URL, auditv1alpha1.SchemeGroupVersion)
|
||||
backend.ProcessEvents(events...)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
timer := make(chan time.Time)
|
||||
|
||||
go func() {
|
||||
waitForEmptyBuffer(backend)
|
||||
close(stopCh) // stop channel has stopped
|
||||
}()
|
||||
backend.sendBatchEvents(backend.collectEvents(stopCh, timer))
|
||||
require.Equal(t, expected, <-got, "get queued events after timer expires")
|
||||
}
|
||||
|
||||
func TestBatchWebhookProcessEventsAfterStopV1Alpha1(t *testing.T) {
|
||||
events := make([]*auditinternal.Event, 1) // less than max size.
|
||||
for i := range events {
|
||||
events[i] = &auditinternal.Event{}
|
||||
}
|
||||
|
||||
got := make(chan struct{})
|
||||
s := httptest.NewServer(newWebhookHandler(t, &auditv1alpha1.EventList{}, func(events runtime.Object) {
|
||||
close(got)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
backend := newTestBatchWebhook(t, s.URL, auditv1alpha1.SchemeGroupVersion)
|
||||
stopCh := make(chan struct{})
|
||||
|
||||
backend.Run(stopCh)
|
||||
close(stopCh)
|
||||
<-backend.shutdownCh
|
||||
backend.ProcessEvents(events...)
|
||||
assert.Equal(t, 0, len(backend.buffer), "processed events after the backed has been stopped")
|
||||
}
|
||||
|
||||
func TestBatchWebhookShutdownV1Alpha1(t *testing.T) {
|
||||
events := make([]*auditinternal.Event, 1)
|
||||
for i := range events {
|
||||
events[i] = &auditinternal.Event{}
|
||||
}
|
||||
|
||||
got := make(chan struct{})
|
||||
contReqCh := make(chan struct{})
|
||||
shutdownCh := make(chan struct{})
|
||||
s := httptest.NewServer(newWebhookHandler(t, &auditv1alpha1.EventList{}, func(events runtime.Object) {
|
||||
close(got)
|
||||
<-contReqCh
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
backend := newTestBatchWebhook(t, s.URL, auditv1alpha1.SchemeGroupVersion)
|
||||
backend.ProcessEvents(events...)
|
||||
|
||||
go func() {
|
||||
// Assume stopCh was closed.
|
||||
close(backend.buffer)
|
||||
backend.sendBatchEvents(backend.collectLastEvents())
|
||||
}()
|
||||
|
||||
<-got
|
||||
|
||||
go func() {
|
||||
close(backend.shutdownCh)
|
||||
backend.Shutdown()
|
||||
close(shutdownCh)
|
||||
}()
|
||||
|
||||
// Wait for some time in case there's a bug that allows for the Shutdown
|
||||
// method to exit before all requests has been completed.
|
||||
time.Sleep(1 * time.Second)
|
||||
select {
|
||||
case <-shutdownCh:
|
||||
t.Fatal("Backend shut down before all requests finished")
|
||||
default:
|
||||
// Continue.
|
||||
}
|
||||
|
||||
close(contReqCh)
|
||||
<-shutdownCh
|
||||
}
|
||||
|
||||
func TestBatchWebhookEmptyBufferV1Alpha1(t *testing.T) {
|
||||
events := make([]*auditinternal.Event, 1) // less than max size.
|
||||
for i := range events {
|
||||
events[i] = &auditinternal.Event{}
|
||||
}
|
||||
|
||||
expected := len(events)
|
||||
got := make(chan int, 2)
|
||||
s := httptest.NewServer(newWebhookHandler(t, &auditv1alpha1.EventList{}, func(events runtime.Object) {
|
||||
got <- len(events.(*auditv1alpha1.EventList).Items)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
backend := newTestBatchWebhook(t, s.URL, auditv1alpha1.SchemeGroupVersion)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
timer := make(chan time.Time, 1)
|
||||
|
||||
timer <- time.Now() // Timer is done.
|
||||
|
||||
// Buffer is empty, no events have been queued. This should exit but send no events.
|
||||
backend.sendBatchEvents(backend.collectEvents(stopCh, timer))
|
||||
|
||||
// Send additional events after the sendBatchEvents has been called.
|
||||
backend.ProcessEvents(events...)
|
||||
go func() {
|
||||
waitForEmptyBuffer(backend)
|
||||
timer <- time.Now()
|
||||
}()
|
||||
|
||||
backend.sendBatchEvents(backend.collectEvents(stopCh, timer))
|
||||
|
||||
// Make sure we didn't get a POST with zero events.
|
||||
require.Equal(t, expected, <-got, "expected one event")
|
||||
}
|
||||
|
||||
func TestBatchBufferFullV1Alpha1(t *testing.T) {
|
||||
events := make([]*auditinternal.Event, defaultBatchBufferSize+1) // More than buffered size
|
||||
for i := range events {
|
||||
events[i] = &auditinternal.Event{}
|
||||
}
|
||||
s := httptest.NewServer(newWebhookHandler(t, &auditv1alpha1.EventList{}, func(events runtime.Object) {
|
||||
// Do nothing.
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
backend := newTestBatchWebhook(t, s.URL, auditv1alpha1.SchemeGroupVersion)
|
||||
|
||||
// Make sure this doesn't block.
|
||||
backend.ProcessEvents(events...)
|
||||
}
|
||||
|
||||
func TestBatchRunV1Alpha1(t *testing.T) {
|
||||
|
||||
// Divisable by max batch size so we don't have to wait for a minute for
|
||||
// the test to finish.
|
||||
events := make([]*auditinternal.Event, defaultBatchMaxSize*3)
|
||||
for i := range events {
|
||||
events[i] = &auditinternal.Event{}
|
||||
}
|
||||
|
||||
got := new(int64)
|
||||
want := len(events)
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(want)
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
// When the expected number of events have been received, close the channel.
|
||||
close(done)
|
||||
}()
|
||||
|
||||
s := httptest.NewServer(newWebhookHandler(t, &auditv1alpha1.EventList{}, func(obj runtime.Object) {
|
||||
events := obj.(*auditv1alpha1.EventList)
|
||||
atomic.AddInt64(got, int64(len(events.Items)))
|
||||
wg.Add(-len(events.Items))
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
|
||||
backend := newTestBatchWebhook(t, s.URL, auditv1alpha1.SchemeGroupVersion)
|
||||
|
||||
// Test the Run codepath. E.g. that the spawned goroutines behave correctly.
|
||||
backend.Run(stopCh)
|
||||
|
||||
backend.ProcessEvents(events...)
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
// Received all the events.
|
||||
case <-time.After(2 * time.Minute):
|
||||
t.Errorf("expected %d events got %d", want, atomic.LoadInt64(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchConcurrentRequestsV1Alpha1(t *testing.T) {
|
||||
events := make([]*auditinternal.Event, defaultBatchBufferSize) // Don't drop events
|
||||
for i := range events {
|
||||
events[i] = &auditinternal.Event{}
|
||||
}
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(len(events))
|
||||
|
||||
s := httptest.NewServer(newWebhookHandler(t, &auditv1alpha1.EventList{}, func(events runtime.Object) {
|
||||
wg.Add(-len(events.(*auditv1alpha1.EventList).Items))
|
||||
|
||||
// Since the webhook makes concurrent requests, blocking on the webhook response
|
||||
// shouldn't block the webhook from sending more events.
|
||||
//
|
||||
// Wait for all responses to be received before sending the response.
|
||||
wg.Wait()
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
|
||||
backend := newTestBatchWebhook(t, s.URL, auditv1alpha1.SchemeGroupVersion)
|
||||
backend.Run(stopCh)
|
||||
|
||||
backend.ProcessEvents(events...)
|
||||
// Wait for the webhook to receive all events.
|
||||
wg.Wait()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue