k8s.io/client-go@v0.22.2/tools/events/eventseries_test.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package events
    18  
    19  import (
    20  	"strconv"
    21  	"testing"
    22  	"time"
    23  
    24  	"os"
    25  	"strings"
    26  
    27  	v1 "k8s.io/api/core/v1"
    28  	eventsv1 "k8s.io/api/events/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	k8sruntime "k8s.io/apimachinery/pkg/runtime"
    31  	"k8s.io/apimachinery/pkg/util/wait"
    32  	"k8s.io/client-go/kubernetes/scheme"
    33  	restclient "k8s.io/client-go/rest"
    34  	ref "k8s.io/client-go/tools/reference"
    35  )
    36  
    37  type testEventSeriesSink struct {
    38  	OnCreate func(e *eventsv1.Event) (*eventsv1.Event, error)
    39  	OnUpdate func(e *eventsv1.Event) (*eventsv1.Event, error)
    40  	OnPatch  func(e *eventsv1.Event, p []byte) (*eventsv1.Event, error)
    41  }
    42  
    43  // Create records the event for testing.
    44  func (t *testEventSeriesSink) Create(e *eventsv1.Event) (*eventsv1.Event, error) {
    45  	if t.OnCreate != nil {
    46  		return t.OnCreate(e)
    47  	}
    48  	return e, nil
    49  }
    50  
    51  // Update records the event for testing.
    52  func (t *testEventSeriesSink) Update(e *eventsv1.Event) (*eventsv1.Event, error) {
    53  	if t.OnUpdate != nil {
    54  		return t.OnUpdate(e)
    55  	}
    56  	return e, nil
    57  }
    58  
    59  // Patch records the event for testing.
    60  func (t *testEventSeriesSink) Patch(e *eventsv1.Event, p []byte) (*eventsv1.Event, error) {
    61  	if t.OnPatch != nil {
    62  		return t.OnPatch(e, p)
    63  	}
    64  	return e, nil
    65  }
    66  
    67  func TestEventSeriesf(t *testing.T) {
    68  	hostname, _ := os.Hostname()
    69  
    70  	testPod := &v1.Pod{
    71  		ObjectMeta: metav1.ObjectMeta{
    72  			Name:      "foo",
    73  			Namespace: "baz",
    74  			UID:       "bar",
    75  		},
    76  	}
    77  
    78  	regarding, err := ref.GetPartialReference(scheme.Scheme, testPod, ".spec.containers[1]")
    79  	if err != nil {
    80  		t.Fatal(err)
    81  	}
    82  
    83  	related, err := ref.GetPartialReference(scheme.Scheme, testPod, ".spec.containers[0]")
    84  	if err != nil {
    85  		t.Fatal(err)
    86  	}
    87  
    88  	expectedEvent := &eventsv1.Event{
    89  		ObjectMeta: metav1.ObjectMeta{
    90  			Name:      "foo",
    91  			Namespace: "baz",
    92  		},
    93  		EventTime:           metav1.MicroTime{time.Now()},
    94  		ReportingController: "eventTest",
    95  		ReportingInstance:   "eventTest-" + hostname,
    96  		Action:              "started",
    97  		Reason:              "test",
    98  		Regarding:           *regarding,
    99  		Related:             related,
   100  		Note:                "some verbose message: 1",
   101  		Type:                v1.EventTypeNormal,
   102  	}
   103  
   104  	isomorphicEvent := expectedEvent.DeepCopy()
   105  
   106  	nonIsomorphicEvent := expectedEvent.DeepCopy()
   107  	nonIsomorphicEvent.Action = "stopped"
   108  
   109  	expectedEvent.Series = &eventsv1.EventSeries{Count: 1}
   110  	table := []struct {
   111  		regarding    k8sruntime.Object
   112  		related      k8sruntime.Object
   113  		actual       *eventsv1.Event
   114  		elements     []interface{}
   115  		expect       *eventsv1.Event
   116  		expectUpdate bool
   117  	}{
   118  		{
   119  			regarding:    regarding,
   120  			related:      related,
   121  			actual:       isomorphicEvent,
   122  			elements:     []interface{}{1},
   123  			expect:       expectedEvent,
   124  			expectUpdate: true,
   125  		},
   126  		{
   127  			regarding:    regarding,
   128  			related:      related,
   129  			actual:       nonIsomorphicEvent,
   130  			elements:     []interface{}{1},
   131  			expect:       nonIsomorphicEvent,
   132  			expectUpdate: false,
   133  		},
   134  	}
   135  
   136  	stopCh := make(chan struct{})
   137  
   138  	createEvent := make(chan *eventsv1.Event)
   139  	updateEvent := make(chan *eventsv1.Event)
   140  	patchEvent := make(chan *eventsv1.Event)
   141  
   142  	testEvents := testEventSeriesSink{
   143  		OnCreate: func(event *eventsv1.Event) (*eventsv1.Event, error) {
   144  			createEvent <- event
   145  			return event, nil
   146  		},
   147  		OnUpdate: func(event *eventsv1.Event) (*eventsv1.Event, error) {
   148  			updateEvent <- event
   149  			return event, nil
   150  		},
   151  		OnPatch: func(event *eventsv1.Event, patch []byte) (*eventsv1.Event, error) {
   152  			// event we receive is already patched, usually the sink uses it only to retrieve the name and namespace, here
   153  			// we'll use it directly
   154  			patchEvent <- event
   155  			return event, nil
   156  		},
   157  	}
   158  	eventBroadcaster := newBroadcaster(&testEvents, 0, map[eventKey]*eventsv1.Event{})
   159  	recorder := eventBroadcaster.NewRecorder(scheme.Scheme, "eventTest")
   160  	broadcaster := eventBroadcaster.(*eventBroadcasterImpl)
   161  	// Don't call StartRecordingToSink, as we don't need neither refreshing event
   162  	// series nor finishing them in this tests and additional events updated would
   163  	// race with our expected ones.
   164  	broadcaster.startRecordingEvents(stopCh)
   165  	recorder.Eventf(regarding, related, isomorphicEvent.Type, isomorphicEvent.Reason, isomorphicEvent.Action, isomorphicEvent.Note, []interface{}{1})
   166  	// read from the chan as this was needed only to populate the cache
   167  	<-createEvent
   168  	for index, item := range table {
   169  		actual := item.actual
   170  		recorder.Eventf(item.regarding, item.related, actual.Type, actual.Reason, actual.Action, actual.Note, item.elements)
   171  		// validate event
   172  		if item.expectUpdate {
   173  			actualEvent := <-patchEvent
   174  			t.Logf("%v - validating event affected by patch request", index)
   175  			validateEvent(strconv.Itoa(index), true, actualEvent, item.expect, t)
   176  		} else {
   177  			actualEvent := <-createEvent
   178  			t.Logf("%v - validating event affected by a create request", index)
   179  			validateEvent(strconv.Itoa(index), false, actualEvent, item.expect, t)
   180  		}
   181  	}
   182  	close(stopCh)
   183  }
   184  
   185  func validateEvent(messagePrefix string, expectedUpdate bool, actualEvent *eventsv1.Event, expectedEvent *eventsv1.Event, t *testing.T) {
   186  	recvEvent := *actualEvent
   187  
   188  	// Just check that the timestamp was set.
   189  	if recvEvent.EventTime.IsZero() {
   190  		t.Errorf("%v - timestamp wasn't set: %#v", messagePrefix, recvEvent)
   191  	}
   192  
   193  	if expectedUpdate {
   194  		if recvEvent.Series == nil {
   195  			t.Errorf("%v - Series was nil but expected: %#v", messagePrefix, recvEvent.Series)
   196  
   197  		} else {
   198  			if recvEvent.Series.Count != expectedEvent.Series.Count {
   199  				t.Errorf("%v - Series mismatch actual was: %#v but expected: %#v", messagePrefix, recvEvent.Series, expectedEvent.Series)
   200  			}
   201  		}
   202  
   203  		// Check that name has the right prefix.
   204  		if n, en := recvEvent.Name, expectedEvent.Name; !strings.HasPrefix(n, en) {
   205  			t.Errorf("%v - Name '%v' does not contain prefix '%v'", messagePrefix, n, en)
   206  		}
   207  	} else {
   208  		if recvEvent.Series != nil {
   209  			t.Errorf("%v - series was expected to be nil but was: %#v", messagePrefix, recvEvent.Series)
   210  		}
   211  	}
   212  
   213  }
   214  
   215  func TestFinishSeries(t *testing.T) {
   216  	hostname, _ := os.Hostname()
   217  	testPod := &v1.Pod{
   218  		ObjectMeta: metav1.ObjectMeta{
   219  			SelfLink:  "/api/v1/namespaces/baz/pods/foo",
   220  			Name:      "foo",
   221  			Namespace: "baz",
   222  			UID:       "bar",
   223  		},
   224  	}
   225  	regarding, err := ref.GetPartialReference(scheme.Scheme, testPod, ".spec.containers[1]")
   226  	if err != nil {
   227  		t.Fatal(err)
   228  	}
   229  	related, err := ref.GetPartialReference(scheme.Scheme, testPod, ".spec.containers[0]")
   230  	if err != nil {
   231  		t.Fatal(err)
   232  	}
   233  	LastObservedTime := metav1.MicroTime{Time: time.Now().Add(-9 * time.Minute)}
   234  
   235  	createEvent := make(chan *eventsv1.Event, 10)
   236  	updateEvent := make(chan *eventsv1.Event, 10)
   237  	patchEvent := make(chan *eventsv1.Event, 10)
   238  	testEvents := testEventSeriesSink{
   239  		OnCreate: func(event *eventsv1.Event) (*eventsv1.Event, error) {
   240  			createEvent <- event
   241  			return event, nil
   242  		},
   243  		OnUpdate: func(event *eventsv1.Event) (*eventsv1.Event, error) {
   244  			updateEvent <- event
   245  			return event, nil
   246  		},
   247  		OnPatch: func(event *eventsv1.Event, patch []byte) (*eventsv1.Event, error) {
   248  			// event we receive is already patched, usually the sink uses it
   249  			// only to retrieve the name and namespace, here we'll use it directly
   250  			patchEvent <- event
   251  			return event, nil
   252  		},
   253  	}
   254  	cache := map[eventKey]*eventsv1.Event{}
   255  	eventBroadcaster := newBroadcaster(&testEvents, 0, cache).(*eventBroadcasterImpl)
   256  	recorder := eventBroadcaster.NewRecorder(scheme.Scheme, "k8s.io/kube-foo").(*recorderImpl)
   257  	cachedEvent := recorder.makeEvent(regarding, related, metav1.MicroTime{time.Now()}, v1.EventTypeNormal, "test", "some verbose message: 1", "eventTest", "eventTest-"+hostname, "started")
   258  	nonFinishedEvent := cachedEvent.DeepCopy()
   259  	nonFinishedEvent.ReportingController = "nonFinished-controller"
   260  	cachedEvent.Series = &eventsv1.EventSeries{
   261  		Count:            10,
   262  		LastObservedTime: LastObservedTime,
   263  	}
   264  	cache[getKey(cachedEvent)] = cachedEvent
   265  	cache[getKey(nonFinishedEvent)] = nonFinishedEvent
   266  	eventBroadcaster.finishSeries()
   267  	select {
   268  	case actualEvent := <-patchEvent:
   269  		t.Logf("validating event affected by patch request")
   270  		eventBroadcaster.mu.Lock()
   271  		defer eventBroadcaster.mu.Unlock()
   272  		if len(cache) != 1 {
   273  			t.Errorf("cache should be empty, but instead got a size of %v", len(cache))
   274  		}
   275  		if !actualEvent.Series.LastObservedTime.Equal(&cachedEvent.Series.LastObservedTime) {
   276  			t.Errorf("series was expected be seen with LastObservedTime %v, but instead got %v ", cachedEvent.Series.LastObservedTime, actualEvent.Series.LastObservedTime)
   277  		}
   278  		// check that we emitted only one event
   279  		if len(patchEvent) != 0 || len(createEvent) != 0 || len(updateEvent) != 0 {
   280  			t.Errorf("exactly one event should be emitted, but got %v", len(patchEvent))
   281  		}
   282  	case <-time.After(wait.ForeverTestTimeout):
   283  		t.Fatalf("timeout after %v", wait.ForeverTestTimeout)
   284  	}
   285  }
   286  
   287  func TestRefreshExistingEventSeries(t *testing.T) {
   288  	hostname, _ := os.Hostname()
   289  	testPod := &v1.Pod{
   290  		ObjectMeta: metav1.ObjectMeta{
   291  			SelfLink:  "/api/v1/namespaces/baz/pods/foo",
   292  			Name:      "foo",
   293  			Namespace: "baz",
   294  			UID:       "bar",
   295  		},
   296  	}
   297  	regarding, err := ref.GetPartialReference(scheme.Scheme, testPod, ".spec.containers[1]")
   298  	if err != nil {
   299  		t.Fatal(err)
   300  	}
   301  	related, err := ref.GetPartialReference(scheme.Scheme, testPod, ".spec.containers[0]")
   302  	if err != nil {
   303  		t.Fatal(err)
   304  	}
   305  	LastObservedTime := metav1.MicroTime{Time: time.Now().Add(-9 * time.Minute)}
   306  	createEvent := make(chan *eventsv1.Event, 10)
   307  	updateEvent := make(chan *eventsv1.Event, 10)
   308  	patchEvent := make(chan *eventsv1.Event, 10)
   309  
   310  	table := []struct {
   311  		patchFunc func(event *eventsv1.Event, patch []byte) (*eventsv1.Event, error)
   312  	}{
   313  		{
   314  			patchFunc: func(event *eventsv1.Event, patch []byte) (*eventsv1.Event, error) {
   315  				// event we receive is already patched, usually the sink uses it
   316  				//only to retrieve the name and namespace, here we'll use it directly.
   317  				patchEvent <- event
   318  				return event, nil
   319  			},
   320  		},
   321  		{
   322  			patchFunc: func(event *eventsv1.Event, patch []byte) (*eventsv1.Event, error) {
   323  				// we simulate an apiserver error here
   324  				patchEvent <- nil
   325  				return nil, &restclient.RequestConstructionError{}
   326  			},
   327  		},
   328  	}
   329  	for _, item := range table {
   330  		testEvents := testEventSeriesSink{
   331  			OnCreate: func(event *eventsv1.Event) (*eventsv1.Event, error) {
   332  				createEvent <- event
   333  				return event, nil
   334  			},
   335  			OnUpdate: func(event *eventsv1.Event) (*eventsv1.Event, error) {
   336  				updateEvent <- event
   337  				return event, nil
   338  			},
   339  			OnPatch: item.patchFunc,
   340  		}
   341  		cache := map[eventKey]*eventsv1.Event{}
   342  		eventBroadcaster := newBroadcaster(&testEvents, 0, cache).(*eventBroadcasterImpl)
   343  		recorder := eventBroadcaster.NewRecorder(scheme.Scheme, "k8s.io/kube-foo").(*recorderImpl)
   344  		cachedEvent := recorder.makeEvent(regarding, related, metav1.MicroTime{time.Now()}, v1.EventTypeNormal, "test", "some verbose message: 1", "eventTest", "eventTest-"+hostname, "started")
   345  		cachedEvent.Series = &eventsv1.EventSeries{
   346  			Count:            10,
   347  			LastObservedTime: LastObservedTime,
   348  		}
   349  		cacheKey := getKey(cachedEvent)
   350  		cache[cacheKey] = cachedEvent
   351  
   352  		eventBroadcaster.refreshExistingEventSeries()
   353  		select {
   354  		case <-patchEvent:
   355  			t.Logf("validating event affected by patch request")
   356  			eventBroadcaster.mu.Lock()
   357  			defer eventBroadcaster.mu.Unlock()
   358  			if len(cache) != 1 {
   359  				t.Errorf("cache should be with same size, but instead got a size of %v", len(cache))
   360  			}
   361  			// check that we emitted only one event
   362  			if len(patchEvent) != 0 || len(createEvent) != 0 || len(updateEvent) != 0 {
   363  				t.Errorf("exactly one event should be emitted, but got %v", len(patchEvent))
   364  			}
   365  			cacheEvent, exists := cache[cacheKey]
   366  
   367  			if cacheEvent == nil || !exists {
   368  				t.Errorf("expected event to exist and not being nil, but instead event: %v and exists: %v", cacheEvent, exists)
   369  			}
   370  		case <-time.After(wait.ForeverTestTimeout):
   371  			t.Fatalf("timeout after %v", wait.ForeverTestTimeout)
   372  		}
   373  	}
   374  }