github.com/cilium/cilium@v1.16.2/pkg/hubble/exporter/exporter_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package exporter
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"io"
    10  	"testing"
    11  
    12  	"github.com/cilium/fake"
    13  	"github.com/sirupsen/logrus"
    14  	"github.com/stretchr/testify/assert"
    15  	timestamp "google.golang.org/protobuf/types/known/timestamppb"
    16  
    17  	flowpb "github.com/cilium/cilium/api/v1/flow"
    18  	observerpb "github.com/cilium/cilium/api/v1/observer"
    19  	v1 "github.com/cilium/cilium/pkg/hubble/api/v1"
    20  	"github.com/cilium/cilium/pkg/hubble/exporter/exporteroption"
    21  	nodeTypes "github.com/cilium/cilium/pkg/node/types"
    22  )
    23  
    24  type bytesWriteCloser struct{ bytes.Buffer }
    25  
    26  func (bwc *bytesWriteCloser) Close() error { return nil }
    27  
    28  type ioWriteCloser struct{ io.Writer }
    29  
    30  func (wc *ioWriteCloser) Close() error { return nil }
    31  
    32  func TestExporter(t *testing.T) {
    33  	// override node name for unit test.
    34  	nodeName := nodeTypes.GetName()
    35  	newNodeName := "my-node"
    36  	nodeTypes.SetName(newNodeName)
    37  	defer func() {
    38  		nodeTypes.SetName(nodeName)
    39  	}()
    40  	events := []*v1.Event{
    41  		{
    42  			Event: &observerpb.Flow{
    43  				NodeName: newNodeName,
    44  				Time:     &timestamp.Timestamp{Seconds: 1},
    45  			},
    46  		},
    47  		{Timestamp: &timestamp.Timestamp{Seconds: 2}, Event: &observerpb.AgentEvent{}},
    48  		{Timestamp: &timestamp.Timestamp{Seconds: 3}, Event: &observerpb.DebugEvent{}},
    49  		{Timestamp: &timestamp.Timestamp{Seconds: 4}, Event: &observerpb.LostEvent{}},
    50  	}
    51  	buf := &bytesWriteCloser{bytes.Buffer{}}
    52  	log := logrus.New()
    53  	log.SetOutput(io.Discard)
    54  	exporter, err := newExporter(context.Background(), log, buf, exporteroption.Default)
    55  	assert.NoError(t, err)
    56  
    57  	ctx := context.Background()
    58  	for _, ev := range events {
    59  		stop, err := exporter.OnDecodedEvent(ctx, ev)
    60  		assert.False(t, stop)
    61  		assert.NoError(t, err)
    62  
    63  	}
    64  	assert.Equal(t, `{"flow":{"time":"1970-01-01T00:00:01Z","node_name":"my-node"},"node_name":"my-node","time":"1970-01-01T00:00:01Z"}
    65  {"agent_event":{},"node_name":"my-node","time":"1970-01-01T00:00:02Z"}
    66  {"debug_event":{},"node_name":"my-node","time":"1970-01-01T00:00:03Z"}
    67  {"lost_events":{},"node_name":"my-node","time":"1970-01-01T00:00:04Z"}
    68  `, buf.String())
    69  }
    70  
    71  func TestExporterWithFilters(t *testing.T) {
    72  	allowNodeName := "allow/node"
    73  	events := []*v1.Event{
    74  		// Non-flow events will not be processed when filters are set
    75  		{Timestamp: &timestamp.Timestamp{Seconds: 2}, Event: &observerpb.AgentEvent{}},
    76  		{Timestamp: &timestamp.Timestamp{Seconds: 3}, Event: &observerpb.DebugEvent{}},
    77  		{Timestamp: &timestamp.Timestamp{Seconds: 4}, Event: &observerpb.LostEvent{}},
    78  		{
    79  			Event: &observerpb.Flow{
    80  				NodeName: allowNodeName,
    81  				Time:     &timestamp.Timestamp{Seconds: 12},
    82  			},
    83  		},
    84  		{
    85  			Event: &observerpb.Flow{
    86  				SourceNames: []string{"deny-pod/a"},
    87  				NodeName:    allowNodeName,
    88  				Time:        &timestamp.Timestamp{Seconds: 13},
    89  			},
    90  		},
    91  		{
    92  			Event: &observerpb.Flow{
    93  				SourceNames: []string{"allow-pod/a"},
    94  				NodeName:    allowNodeName,
    95  				Time:        &timestamp.Timestamp{Seconds: 14},
    96  			},
    97  		},
    98  		{
    99  			Event: &observerpb.Flow{
   100  				SourceNames: []string{"allow-pod/a"},
   101  				NodeName:    "another-node",
   102  				Time:        &timestamp.Timestamp{Seconds: 15},
   103  			},
   104  		},
   105  		{
   106  			Event: &observerpb.Flow{
   107  				SourceNames: []string{"allow-pod/a"},
   108  				NodeName:    allowNodeName,
   109  				Time:        &timestamp.Timestamp{Seconds: 16},
   110  			},
   111  		},
   112  	}
   113  	buf := &bytesWriteCloser{bytes.Buffer{}}
   114  	log := logrus.New()
   115  	log.SetOutput(io.Discard)
   116  
   117  	allowFilter := &flowpb.FlowFilter{NodeName: []string{"allow/"}}
   118  	denyFilter := &flowpb.FlowFilter{SourcePod: []string{"deny-pod/"}}
   119  
   120  	opts := exporteroption.Default
   121  	for _, opt := range []exporteroption.Option{
   122  		exporteroption.WithAllowList(log, []*flowpb.FlowFilter{allowFilter}),
   123  		exporteroption.WithDenyList(log, []*flowpb.FlowFilter{denyFilter}),
   124  	} {
   125  		err := opt(&opts)
   126  		assert.NoError(t, err)
   127  	}
   128  
   129  	ctx, cancel := context.WithCancel(context.Background())
   130  	defer cancel()
   131  	exporter, err := newExporter(ctx, log, buf, opts)
   132  	assert.NoError(t, err)
   133  
   134  	for i, ev := range events {
   135  		// Check if processing stops (shouldn't write the last event)
   136  		if i == len(events)-1 {
   137  			cancel()
   138  		}
   139  		stop, err := exporter.OnDecodedEvent(ctx, ev)
   140  		assert.False(t, stop)
   141  		assert.NoError(t, err)
   142  
   143  	}
   144  	assert.Equal(t, `{"flow":{"time":"1970-01-01T00:00:12Z","node_name":"allow/node"},"node_name":"allow/node","time":"1970-01-01T00:00:12Z"}
   145  {"flow":{"time":"1970-01-01T00:00:13Z","node_name":"allow/node","source_names":["deny-pod/a"]},"node_name":"allow/node","time":"1970-01-01T00:00:13Z"}
   146  {"flow":{"time":"1970-01-01T00:00:14Z","node_name":"allow/node","source_names":["allow-pod/a"]},"node_name":"allow/node","time":"1970-01-01T00:00:14Z"}
   147  `, buf.String())
   148  }
   149  
   150  func TestEventToExportEvent(t *testing.T) {
   151  	// override node name for unit test.
   152  	nodeName := nodeTypes.GetName()
   153  	newNodeName := "my-node"
   154  	nodeTypes.SetName(newNodeName)
   155  	defer func() {
   156  		nodeTypes.SetName(nodeName)
   157  	}()
   158  
   159  	buf := &bytesWriteCloser{bytes.Buffer{}}
   160  	log := logrus.New()
   161  	log.SetOutput(io.Discard)
   162  	ctx, cancel := context.WithCancel(context.Background())
   163  	defer cancel()
   164  	exporter, err := newExporter(ctx, log, buf, exporteroption.Default)
   165  	assert.NoError(t, err)
   166  
   167  	// flow
   168  	ev := v1.Event{
   169  		Event: &observerpb.Flow{
   170  			NodeName: newNodeName,
   171  			Time:     &timestamp.Timestamp{Seconds: 1},
   172  		},
   173  	}
   174  	res := exporter.eventToExportEvent(&ev)
   175  	expected := &observerpb.ExportEvent{
   176  		ResponseTypes: &observerpb.ExportEvent_Flow{Flow: ev.Event.(*flowpb.Flow)},
   177  		NodeName:      newNodeName,
   178  		Time:          ev.GetFlow().Time,
   179  	}
   180  	assert.Equal(t, res, expected)
   181  
   182  	// lost event
   183  	ev = v1.Event{
   184  		Timestamp: &timestamp.Timestamp{Seconds: 1},
   185  		Event:     &observerpb.LostEvent{},
   186  	}
   187  	res = exporter.eventToExportEvent(&ev)
   188  	expected = &observerpb.ExportEvent{
   189  		ResponseTypes: &observerpb.ExportEvent_LostEvents{LostEvents: ev.Event.(*flowpb.LostEvent)},
   190  		NodeName:      newNodeName,
   191  		Time:          ev.Timestamp,
   192  	}
   193  	assert.Equal(t, res, expected)
   194  
   195  	// agent event
   196  	ev = v1.Event{
   197  		Timestamp: &timestamp.Timestamp{Seconds: 1},
   198  		Event:     &observerpb.AgentEvent{},
   199  	}
   200  	res = exporter.eventToExportEvent(&ev)
   201  	expected = &observerpb.ExportEvent{
   202  		ResponseTypes: &observerpb.ExportEvent_AgentEvent{AgentEvent: ev.Event.(*flowpb.AgentEvent)},
   203  		NodeName:      newNodeName,
   204  		Time:          ev.Timestamp,
   205  	}
   206  	assert.Equal(t, res, expected)
   207  
   208  	// debug event
   209  	ev = v1.Event{
   210  		Timestamp: &timestamp.Timestamp{Seconds: 1},
   211  		Event:     &observerpb.DebugEvent{},
   212  	}
   213  	res = exporter.eventToExportEvent(&ev)
   214  	expected = &observerpb.ExportEvent{
   215  		ResponseTypes: &observerpb.ExportEvent_DebugEvent{DebugEvent: ev.Event.(*flowpb.DebugEvent)},
   216  		NodeName:      newNodeName,
   217  		Time:          ev.Timestamp,
   218  	}
   219  	assert.Equal(t, res, expected)
   220  }
   221  
   222  func TestExporterWithFieldMask(t *testing.T) {
   223  	events := []*v1.Event{
   224  		{
   225  			Event: &observerpb.Flow{
   226  				NodeName: "nodeName",
   227  				Time:     &timestamp.Timestamp{Seconds: 12},
   228  				Source:   &flowpb.Endpoint{PodName: "podA", Namespace: "nsA"},
   229  			},
   230  		},
   231  		{
   232  			Event: &observerpb.Flow{
   233  				NodeName:    "nodeName",
   234  				Time:        &timestamp.Timestamp{Seconds: 13},
   235  				Destination: &flowpb.Endpoint{PodName: "podB", Namespace: "nsB"}},
   236  		},
   237  	}
   238  	buf := &bytesWriteCloser{bytes.Buffer{}}
   239  	log := logrus.New()
   240  	log.SetOutput(io.Discard)
   241  
   242  	opts := exporteroption.Default
   243  	for _, opt := range []exporteroption.Option{
   244  		exporteroption.WithFieldMask([]string{"source"}),
   245  	} {
   246  		err := opt(&opts)
   247  		assert.NoError(t, err)
   248  	}
   249  
   250  	ctx, cancel := context.WithCancel(context.Background())
   251  	defer cancel()
   252  	exporter, err := newExporter(ctx, log, buf, opts)
   253  	assert.NoError(t, err)
   254  
   255  	for _, ev := range events {
   256  		stop, err := exporter.OnDecodedEvent(ctx, ev)
   257  		assert.False(t, stop)
   258  		assert.NoError(t, err)
   259  	}
   260  
   261  	assert.Equal(t, `{"flow":{"source":{"namespace":"nsA","pod_name":"podA"}}}
   262  {"flow":{}}
   263  `, buf.String())
   264  }
   265  
   266  func BenchmarkExporter(b *testing.B) {
   267  	allowNS, denyNS := fake.K8sNamespace(), fake.K8sNamespace()
   268  	for allowNS == denyNS {
   269  		allowNS, denyNS = fake.K8sNamespace(), fake.K8sNamespace()
   270  	}
   271  	allowEvent := v1.Event{
   272  		Event: &observerpb.Flow{
   273  			Time:     &timestamp.Timestamp{Seconds: 1},
   274  			NodeName: fake.K8sNodeName(),
   275  			Source: &flowpb.Endpoint{
   276  				Namespace: allowNS,
   277  				PodName:   fake.K8sPodName(),
   278  				Labels:    fake.K8sLabels(),
   279  			},
   280  			Destination: &flowpb.Endpoint{
   281  				Namespace: allowNS,
   282  				PodName:   fake.K8sPodName(),
   283  				Labels:    fake.K8sLabels(),
   284  			},
   285  			SourceNames:      fake.Names(2),
   286  			DestinationNames: fake.Names(2),
   287  			Verdict:          flowpb.Verdict_AUDIT,
   288  			Summary:          fake.AlphaNum(20),
   289  		},
   290  	}
   291  	noAllowEvent := v1.Event{
   292  		Event: &observerpb.Flow{
   293  			Time:     &timestamp.Timestamp{Seconds: 1},
   294  			NodeName: fake.K8sNodeName(),
   295  			Source: &flowpb.Endpoint{
   296  				Namespace: denyNS,
   297  				PodName:   fake.K8sPodName(),
   298  				Labels:    fake.K8sLabels(),
   299  			},
   300  			Destination: &flowpb.Endpoint{
   301  				Namespace: allowNS,
   302  				PodName:   fake.K8sPodName(),
   303  				Labels:    fake.K8sLabels(),
   304  			},
   305  			SourceNames:      fake.Names(2),
   306  			DestinationNames: fake.Names(2),
   307  			Verdict:          flowpb.Verdict_AUDIT,
   308  			Summary:          fake.AlphaNum(20),
   309  		},
   310  	}
   311  	denyEvent := v1.Event{
   312  		Event: &observerpb.Flow{
   313  			Time:     &timestamp.Timestamp{Seconds: 1},
   314  			NodeName: fake.K8sNodeName(),
   315  			Source: &flowpb.Endpoint{
   316  				Namespace: allowNS,
   317  				PodName:   fake.K8sPodName(),
   318  				Labels:    fake.K8sLabels(),
   319  			},
   320  			Destination: &flowpb.Endpoint{
   321  				Namespace: denyNS,
   322  				PodName:   fake.K8sPodName(),
   323  				Labels:    fake.K8sLabels(),
   324  			},
   325  			SourceNames:      fake.Names(2),
   326  			DestinationNames: fake.Names(2),
   327  			Verdict:          flowpb.Verdict_AUDIT,
   328  			Summary:          fake.AlphaNum(20),
   329  		},
   330  	}
   331  
   332  	buf := &ioWriteCloser{io.Discard}
   333  	log := logrus.New()
   334  	log.SetOutput(io.Discard)
   335  
   336  	opts := exporteroption.Default
   337  	for _, opt := range []exporteroption.Option{
   338  		exporteroption.WithFieldMask([]string{"time", "node_name", "source"}),
   339  		exporteroption.WithAllowList(log, []*flowpb.FlowFilter{
   340  			{SourcePod: []string{"no-matches-for-this-one"}},
   341  			{SourcePod: []string{allowNS + "/"}},
   342  		}),
   343  		exporteroption.WithDenyList(log, []*flowpb.FlowFilter{
   344  			{DestinationPod: []string{"no-matches-for-this-one"}},
   345  			{DestinationPod: []string{denyNS + "/"}},
   346  		}),
   347  	} {
   348  		err := opt(&opts)
   349  		assert.NoError(b, err)
   350  	}
   351  
   352  	ctx, cancel := context.WithCancel(context.Background())
   353  	defer cancel()
   354  	exporter, err := newExporter(ctx, log, buf, opts)
   355  	assert.NoError(b, err)
   356  
   357  	b.StartTimer()
   358  	for i := 0; i < b.N; i++ {
   359  		event := &allowEvent
   360  		if i%10 == 0 { // 10% doesn't match allow filter
   361  			event = &noAllowEvent
   362  		}
   363  		if i%10 == 1 { // 10% matches deny filter
   364  			event = &denyEvent
   365  		}
   366  		stop, err := exporter.OnDecodedEvent(ctx, event)
   367  		assert.False(b, stop)
   368  		assert.NoError(b, err)
   369  	}
   370  }