github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/events/exchange/exchange_test.go (about)

     1  /*
     2     Copyright The containerd 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 exchange
    18  
    19  import (
    20  	"context"
    21  	"reflect"
    22  	"testing"
    23  	"time"
    24  
    25  	eventstypes "github.com/containerd/containerd/api/events"
    26  	"github.com/containerd/containerd/errdefs"
    27  	"github.com/containerd/containerd/events"
    28  	"github.com/containerd/containerd/namespaces"
    29  	"github.com/containerd/typeurl"
    30  	"github.com/pkg/errors"
    31  )
    32  
    33  func TestExchangeBasic(t *testing.T) {
    34  	ctx := namespaces.WithNamespace(context.Background(), t.Name())
    35  	testevents := []events.Event{
    36  		&eventstypes.ContainerCreate{ID: "asdf"},
    37  		&eventstypes.ContainerCreate{ID: "qwer"},
    38  		&eventstypes.ContainerCreate{ID: "zxcv"},
    39  	}
    40  	exchange := NewExchange()
    41  
    42  	t.Log("subscribe")
    43  	var cancel1, cancel2 func()
    44  
    45  	// Create two subscribers for same set of events and make sure they
    46  	// traverse the exchange.
    47  	ctx1, cancel1 := context.WithCancel(ctx)
    48  	eventq1, errq1 := exchange.Subscribe(ctx1)
    49  
    50  	ctx2, cancel2 := context.WithCancel(ctx)
    51  	eventq2, errq2 := exchange.Subscribe(ctx2)
    52  
    53  	t.Log("publish")
    54  	errChan := make(chan error)
    55  	go func() {
    56  		defer close(errChan)
    57  		for _, event := range testevents {
    58  			if err := exchange.Publish(ctx, "/test", event); err != nil {
    59  				errChan <- err
    60  				return
    61  			}
    62  		}
    63  
    64  		t.Log("finished publishing")
    65  	}()
    66  
    67  	t.Log("waiting")
    68  	if err := <-errChan; err != nil {
    69  		t.Fatal(err)
    70  	}
    71  
    72  	for _, subscriber := range []struct {
    73  		eventq <-chan *events.Envelope
    74  		errq   <-chan error
    75  		cancel func()
    76  	}{
    77  		{
    78  			eventq: eventq1,
    79  			errq:   errq1,
    80  			cancel: cancel1,
    81  		},
    82  		{
    83  			eventq: eventq2,
    84  			errq:   errq2,
    85  			cancel: cancel2,
    86  		},
    87  	} {
    88  		var received []events.Event
    89  	subscribercheck:
    90  		for {
    91  			select {
    92  			case env := <-subscriber.eventq:
    93  				ev, err := typeurl.UnmarshalAny(env.Event)
    94  				if err != nil {
    95  					t.Fatal(err)
    96  				}
    97  				received = append(received, ev.(*eventstypes.ContainerCreate))
    98  			case err := <-subscriber.errq:
    99  				if err != nil {
   100  					t.Fatal(err)
   101  				}
   102  				break subscribercheck
   103  			}
   104  
   105  			if reflect.DeepEqual(received, testevents) {
   106  				// when we do this, we expect the errs channel to be closed and
   107  				// this will return.
   108  				subscriber.cancel()
   109  			}
   110  		}
   111  	}
   112  }
   113  
   114  func TestExchangeFilters(t *testing.T) {
   115  	var (
   116  		ctx      = namespaces.WithNamespace(context.Background(), t.Name())
   117  		exchange = NewExchange()
   118  
   119  		// config events, All events will be published
   120  		containerCreateEvents = []events.Event{
   121  			&eventstypes.ContainerCreate{ID: "asdf"},
   122  			&eventstypes.ContainerCreate{ID: "qwer"},
   123  			&eventstypes.ContainerCreate{ID: "zxcv"},
   124  		}
   125  		taskExitEvents = []events.Event{
   126  			&eventstypes.TaskExit{ContainerID: "abcdef"},
   127  		}
   128  		testEventSets = []struct {
   129  			topic  string
   130  			events []events.Event
   131  		}{
   132  			{
   133  				topic:  "/containers/create",
   134  				events: containerCreateEvents,
   135  			},
   136  			{
   137  				topic:  "/tasks/exit",
   138  				events: taskExitEvents,
   139  			},
   140  		}
   141  		allTestEvents = func(eventSets []struct {
   142  			topic  string
   143  			events []events.Event
   144  		}) (events []events.Event) {
   145  			for _, v := range eventSets {
   146  				events = append(events, v.events...)
   147  			}
   148  			return
   149  		}(testEventSets)
   150  
   151  		// config test cases
   152  		testCases = []struct {
   153  			testName string
   154  			filters  []string
   155  
   156  			// The fields as below are for store data. Don't config them.
   157  			expectEvents []events.Event
   158  			eventq       <-chan *events.Envelope
   159  			errq         <-chan error
   160  			cancel       func()
   161  		}{
   162  			{
   163  				testName:     "No Filter",
   164  				expectEvents: allTestEvents,
   165  			},
   166  			{
   167  				testName: "Filter events by one topic",
   168  				filters: []string{
   169  					`topic=="/containers/create"`,
   170  				},
   171  				expectEvents: containerCreateEvents,
   172  			},
   173  			{
   174  				testName: "Filter events by field",
   175  				filters: []string{
   176  					"event.id",
   177  				},
   178  				expectEvents: containerCreateEvents,
   179  			},
   180  			{
   181  				testName: "Filter events by field OR topic",
   182  				filters: []string{
   183  					`topic=="/containers/create"`,
   184  					"event.id",
   185  				},
   186  				expectEvents: containerCreateEvents,
   187  			},
   188  			{
   189  				testName: "Filter events by regex ",
   190  				filters: []string{
   191  					`topic~="/containers/*"`,
   192  				},
   193  				expectEvents: containerCreateEvents,
   194  			},
   195  			{
   196  				testName: "Filter events for by anyone of two topics",
   197  				filters: []string{
   198  					`topic=="/tasks/exit"`,
   199  					`topic=="/containers/create"`,
   200  				},
   201  				expectEvents: append(containerCreateEvents, taskExitEvents...),
   202  			},
   203  			{
   204  				testName: "Filter events for by one topic AND id",
   205  				filters: []string{
   206  					`topic=="/containers/create",event.id=="qwer"`,
   207  				},
   208  				expectEvents: []events.Event{
   209  					&eventstypes.ContainerCreate{ID: "qwer"},
   210  				},
   211  			},
   212  		}
   213  	)
   214  
   215  	t.Log("subscribe")
   216  	for i := range testCases {
   217  		var ctx1 context.Context
   218  		ctx1, testCases[i].cancel = context.WithCancel(ctx)
   219  		testCases[i].eventq, testCases[i].errq = exchange.Subscribe(ctx1, testCases[i].filters...)
   220  	}
   221  
   222  	t.Log("publish")
   223  	errChan := make(chan error)
   224  	go func() {
   225  		defer close(errChan)
   226  		for _, es := range testEventSets {
   227  			for _, e := range es.events {
   228  				if err := exchange.Publish(ctx, es.topic, e); err != nil {
   229  					errChan <- err
   230  					return
   231  				}
   232  			}
   233  		}
   234  
   235  		t.Log("finished publishing")
   236  	}()
   237  
   238  	t.Log("waiting")
   239  	if err := <-errChan; err != nil {
   240  		t.Fatal(err)
   241  	}
   242  
   243  	t.Log("receive event")
   244  	for _, subscriber := range testCases {
   245  		t.Logf("test case: %q", subscriber.testName)
   246  		var received []events.Event
   247  	subscribercheck:
   248  		for {
   249  			select {
   250  			case env := <-subscriber.eventq:
   251  				ev, err := typeurl.UnmarshalAny(env.Event)
   252  				if err != nil {
   253  					t.Fatal(err)
   254  				}
   255  				received = append(received, ev)
   256  			case err := <-subscriber.errq:
   257  				if err != nil {
   258  					t.Fatal(err)
   259  				}
   260  				break subscribercheck
   261  			}
   262  
   263  			if reflect.DeepEqual(received, subscriber.expectEvents) {
   264  				// when we do this, we expect the errs channel to be closed and
   265  				// this will return.
   266  				subscriber.cancel()
   267  			}
   268  		}
   269  	}
   270  }
   271  
   272  func TestExchangeValidateTopic(t *testing.T) {
   273  	namespace := t.Name()
   274  	ctx := namespaces.WithNamespace(context.Background(), namespace)
   275  	exchange := NewExchange()
   276  
   277  	for _, testcase := range []struct {
   278  		input string
   279  		err   error
   280  	}{
   281  		{
   282  			input: "/test",
   283  		},
   284  		{
   285  			input: "/test/test",
   286  		},
   287  		{
   288  			input: "test",
   289  			err:   errdefs.ErrInvalidArgument,
   290  		},
   291  	} {
   292  		t.Run(testcase.input, func(t *testing.T) {
   293  			event := &eventstypes.ContainerCreate{ID: t.Name()}
   294  			if err := exchange.Publish(ctx, testcase.input, event); !errors.Is(err, testcase.err) {
   295  				if err == nil {
   296  					t.Fatalf("expected error %v, received nil", testcase.err)
   297  				} else {
   298  					t.Fatalf("expected error %v, received %v", testcase.err, err)
   299  				}
   300  			}
   301  
   302  			evany, err := typeurl.MarshalAny(event)
   303  			if err != nil {
   304  				t.Fatal(err)
   305  			}
   306  
   307  			envelope := events.Envelope{
   308  				Timestamp: time.Now().UTC(),
   309  				Namespace: namespace,
   310  				Topic:     testcase.input,
   311  				Event:     evany,
   312  			}
   313  
   314  			// make sure we get same errors with forward.
   315  			if err := exchange.Forward(ctx, &envelope); !errors.Is(err, testcase.err) {
   316  				if err == nil {
   317  					t.Fatalf("expected error %v, received nil", testcase.err)
   318  				} else {
   319  					t.Fatalf("expected error %v, received %v", testcase.err, err)
   320  				}
   321  			}
   322  
   323  		})
   324  	}
   325  }