github.com/castai/kvisor@v1.7.1-0.20240516114728-b3572a2607b5/cmd/agent/daemon/state/controller_test.go (about)

     1  package state
     2  
     3  import (
     4  	"context"
     5  	"net/netip"
     6  	"testing"
     7  	"time"
     8  
     9  	kubepb "github.com/castai/kvisor/api/v1/kube"
    10  	castaipb "github.com/castai/kvisor/api/v1/runtime"
    11  	"github.com/castai/kvisor/cmd/agent/daemon/enrichment"
    12  	"github.com/castai/kvisor/cmd/agent/daemon/netstats"
    13  	"github.com/castai/kvisor/pkg/cgroup"
    14  	"github.com/castai/kvisor/pkg/containers"
    15  	"github.com/castai/kvisor/pkg/ebpftracer"
    16  	"github.com/castai/kvisor/pkg/ebpftracer/types"
    17  	"github.com/castai/kvisor/pkg/logging"
    18  	"github.com/samber/lo"
    19  	"github.com/stretchr/testify/require"
    20  	"google.golang.org/grpc"
    21  )
    22  
    23  func TestController(t *testing.T) {
    24  	ctx, cancel := context.WithCancel(context.Background())
    25  	defer cancel()
    26  
    27  	t.Run("events pipeline", func(t *testing.T) {
    28  		r := require.New(t)
    29  		ctrl := newTestController()
    30  		exporter := &mockEventsExporter{events: make(chan *castaipb.Event, 10)}
    31  		ctrl.exporters.Events = append(ctrl.exporters.Events, exporter)
    32  		ctrl.tracer.(*mockEbpfTracer).eventsChan <- &types.Event{
    33  			Context: &types.EventContext{Ts: 1},
    34  			Container: &containers.Container{
    35  				PodName: "p1",
    36  			},
    37  		}
    38  		ctrlerr := make(chan error, 1)
    39  		go func() {
    40  			ctrlerr <- ctrl.Run(ctx)
    41  		}()
    42  
    43  		select {
    44  		case e := <-exporter.events:
    45  			r.Equal("p1", e.PodName)
    46  		case err := <-ctrlerr:
    47  			t.Fatal(err)
    48  		case <-time.After(time.Second):
    49  			t.Fatal("timed out waiting for data")
    50  		}
    51  	})
    52  
    53  	t.Run("container stats pipeline", func(t *testing.T) {
    54  		r := require.New(t)
    55  		ctrl := newTestController()
    56  		exporter := &mockContainerStatsExporter{events: make(chan *castaipb.ContainerStatsBatch, 10)}
    57  		ctrl.exporters.ContainerStats = append(ctrl.exporters.ContainerStats, exporter)
    58  		ctrl.tracer.(*mockEbpfTracer).syscallStats = map[ebpftracer.SyscallStatsKeyCgroupID][]ebpftracer.SyscallStats{
    59  			1: {
    60  				{ebpftracer.SyscallID(2), 3},
    61  			},
    62  		}
    63  		ctrl.containersClient.(*mockContainersClient).list = []*containers.Container{
    64  			{
    65  				ID:           "c1",
    66  				Name:         "cont",
    67  				CgroupID:     1,
    68  				PodNamespace: "ns1",
    69  				PodUID:       "p1",
    70  				PodName:      "p1",
    71  				Cgroup:       nil,
    72  				PIDs:         []uint32{1},
    73  			},
    74  		}
    75  
    76  		ctrlerr := make(chan error, 1)
    77  		go func() {
    78  			ctrlerr <- ctrl.Run(ctx)
    79  		}()
    80  
    81  		select {
    82  		case e := <-exporter.events:
    83  			r.Len(e.Items, 1)
    84  			r.Len(e.Items[0].Stats, 1)
    85  			r.Equal(1, int(e.Items[0].Stats[0].Group))
    86  			r.Equal(2, int(e.Items[0].Stats[0].Subgroup))
    87  			r.GreaterOrEqual(1, int(e.Items[0].Stats[0].Value))
    88  		case err := <-ctrlerr:
    89  			t.Fatal(err)
    90  		case <-time.After(time.Second):
    91  			t.Fatal("timed out waiting for data")
    92  		}
    93  	})
    94  
    95  	t.Run("netflow pipeline", func(t *testing.T) {
    96  		r := require.New(t)
    97  		ctrl := newTestController()
    98  		exporter := &mockNetflowExporter{events: make(chan *castaipb.Netflow, 10)}
    99  		ctrl.exporters.Netflow = append(ctrl.exporters.Netflow, exporter)
   100  
   101  		ctrl.tracer.(*mockEbpfTracer).netflowEventsChan <- &types.Event{
   102  			Context: &types.EventContext{Ts: 1},
   103  			Container: &containers.Container{
   104  				PodName: "p1",
   105  			},
   106  			Args: types.NetFlowBaseArgs{
   107  				Proto: 6,
   108  				Tuple: types.AddrTuple{
   109  					Src: netip.MustParseAddrPort("10.10.0.10:34561"),
   110  					Dst: netip.MustParseAddrPort("10.10.0.15:80"),
   111  				},
   112  				TxBytes:   10,
   113  				TxPackets: 5,
   114  			},
   115  		}
   116  
   117  		ctrlerr := make(chan error, 1)
   118  		go func() {
   119  			ctrlerr <- ctrl.Run(ctx)
   120  		}()
   121  
   122  		select {
   123  		case e := <-exporter.events:
   124  			r.Equal(castaipb.NetflowProtocol_NETFLOW_PROTOCOL_TCP, e.Protocol)
   125  			r.Equal(netip.MustParseAddr("10.10.0.10").AsSlice(), e.Addr)
   126  			r.Equal(34561, int(e.Port))
   127  			r.Len(e.Destinations, 1)
   128  			dest := e.Destinations[0]
   129  			r.Equal(netip.MustParseAddr("10.10.0.15").AsSlice(), dest.Addr)
   130  			r.Equal(80, int(dest.Port))
   131  			r.Equal(10, int(dest.TxBytes))
   132  			r.Equal(5, int(dest.TxPackets))
   133  		case err := <-ctrlerr:
   134  			t.Fatal(err)
   135  		case <-time.After(time.Second):
   136  			t.Fatal("timed out waiting for data")
   137  		}
   138  	})
   139  
   140  	t.Run("netflow cleanup", func(t *testing.T) {
   141  		r := require.New(t)
   142  		ctrl := newTestController()
   143  
   144  		ctrl.netflows = map[uint64]*netflowVal{
   145  			1: {
   146  				updatedAt: time.Now(),
   147  			},
   148  			2: {
   149  				updatedAt: time.Now().Add(-time.Hour),
   150  			},
   151  		}
   152  
   153  		ctrl.cfg.NetflowExportInterval = 1 * time.Minute
   154  		ctrl.cleanupNetflow()
   155  		// Should keep recent flows.
   156  		r.Equal([]uint64{1}, lo.Keys(ctrl.netflows))
   157  	})
   158  }
   159  
   160  func newTestController() *Controller {
   161  	log := logging.NewTestLog()
   162  	cfg := Config{
   163  		ContainerStatsScrapeInterval: time.Millisecond,
   164  		NetflowExportInterval:        time.Millisecond,
   165  		NetflowCleanupInterval:       5 * time.Millisecond,
   166  	}
   167  	exporters := NewExporters(log)
   168  	contClient := &mockContainersClient{}
   169  	netReader := &mockNetStatsReader{}
   170  	ctClient := &mockConntrackClient{}
   171  	tracer := &mockEbpfTracer{eventsChan: make(chan *types.Event, 100), netflowEventsChan: make(chan *types.Event, 100)}
   172  	sigEngine := &mockSignatureEngine{eventsChan: make(chan *castaipb.Event, 100)}
   173  	enrichService := &mockEnrichmentService{eventsChan: make(chan *castaipb.Event, 100)}
   174  	kubeClient := &mockKubeClient{}
   175  	return NewController(
   176  		log,
   177  		cfg,
   178  		exporters,
   179  		contClient,
   180  		netReader,
   181  		ctClient,
   182  		tracer,
   183  		sigEngine,
   184  		enrichService,
   185  		kubeClient,
   186  	)
   187  }
   188  
   189  type mockEventsExporter struct {
   190  	events chan *castaipb.Event
   191  }
   192  
   193  func (m *mockEventsExporter) Run(ctx context.Context) error {
   194  	return nil
   195  }
   196  
   197  func (m *mockEventsExporter) Enqueue(e *castaipb.Event) {
   198  	m.events <- e
   199  }
   200  
   201  type mockContainerStatsExporter struct {
   202  	events chan *castaipb.ContainerStatsBatch
   203  }
   204  
   205  func (m *mockContainerStatsExporter) Run(ctx context.Context) error {
   206  	return nil
   207  }
   208  
   209  func (m *mockContainerStatsExporter) Enqueue(e *castaipb.ContainerStatsBatch) {
   210  	m.events <- e
   211  }
   212  
   213  type mockNetflowExporter struct {
   214  	events chan *castaipb.Netflow
   215  }
   216  
   217  func (m *mockNetflowExporter) Run(ctx context.Context) error {
   218  	return nil
   219  }
   220  
   221  func (m *mockNetflowExporter) Enqueue(e *castaipb.Netflow) {
   222  	m.events <- e
   223  }
   224  
   225  type mockContainersClient struct {
   226  	list []*containers.Container
   227  }
   228  
   229  func (m *mockContainersClient) GetCgroupCpuStats(c *containers.Container) (*cgroup.CPUStat, error) {
   230  	return &cgroup.CPUStat{}, nil
   231  }
   232  
   233  func (m *mockContainersClient) GetCgroupMemoryStats(c *containers.Container) (*cgroup.MemoryStat, error) {
   234  	return &cgroup.MemoryStat{}, nil
   235  }
   236  
   237  func (m *mockContainersClient) ListContainers() []*containers.Container {
   238  	return m.list
   239  }
   240  
   241  func (m *mockContainersClient) GetContainerForCgroup(ctx context.Context, cgroup uint64) (*containers.Container, error) {
   242  	for _, v := range m.list {
   243  		if v.CgroupID == cgroup {
   244  			return v, nil
   245  		}
   246  	}
   247  	return nil, containers.ErrContainerNotFound
   248  }
   249  
   250  func (m *mockContainersClient) LookupContainerForCgroupInCache(cgroup uint64) (*containers.Container, bool, error) {
   251  	for _, v := range m.list {
   252  		if v.CgroupID == cgroup {
   253  			return v, true, nil
   254  		}
   255  	}
   256  	return nil, false, containers.ErrContainerNotFound
   257  }
   258  
   259  func (m *mockContainersClient) CleanupCgroup(cgroup cgroup.ID) {
   260  	return
   261  }
   262  
   263  func (m *mockContainersClient) GetCgroupsInNamespace(namespace string) []uint64 {
   264  	return []uint64{}
   265  }
   266  
   267  func (m *mockContainersClient) RegisterContainerCreatedListener(l containers.ContainerCreatedListener) {
   268  	return
   269  }
   270  
   271  func (m *mockContainersClient) RegisterContainerDeletedListener(l containers.ContainerDeletedListener) {
   272  	return
   273  }
   274  
   275  type mockNetStatsReader struct {
   276  }
   277  
   278  func (m *mockNetStatsReader) Read(pid uint32) ([]netstats.InterfaceStats, error) {
   279  	return nil, nil
   280  }
   281  
   282  type mockConntrackClient struct {
   283  }
   284  
   285  func (m *mockConntrackClient) GetDestination(src, dst netip.AddrPort) (netip.AddrPort, bool) {
   286  	return netip.AddrPort{}, false
   287  }
   288  
   289  type mockEbpfTracer struct {
   290  	eventsChan        chan *types.Event
   291  	netflowEventsChan chan *types.Event
   292  	syscallStats      map[ebpftracer.SyscallStatsKeyCgroupID][]ebpftracer.SyscallStats
   293  }
   294  
   295  func (m *mockEbpfTracer) ReadSyscallStats() (map[ebpftracer.SyscallStatsKeyCgroupID][]ebpftracer.SyscallStats, error) {
   296  	// Inc stats each time scrape is called since we export only stats which changed.
   297  	for _, s := range m.syscallStats {
   298  		for i := range s {
   299  			s[i].Count++
   300  		}
   301  	}
   302  	return m.syscallStats, nil
   303  }
   304  
   305  func (m *mockEbpfTracer) Events() <-chan *types.Event {
   306  	return m.eventsChan
   307  }
   308  
   309  func (m *mockEbpfTracer) NetflowEvents() <-chan *types.Event {
   310  	return m.netflowEventsChan
   311  }
   312  
   313  func (m *mockEbpfTracer) MuteEventsFromCgroup(cgroup uint64) error {
   314  	return nil
   315  }
   316  
   317  func (m *mockEbpfTracer) MuteEventsFromCgroups(cgroups []uint64) error {
   318  	return nil
   319  }
   320  
   321  func (m *mockEbpfTracer) UnmuteEventsFromCgroup(cgroup uint64) error {
   322  	return nil
   323  }
   324  
   325  func (m *mockEbpfTracer) UnmuteEventsFromCgroups(cgroups []uint64) error {
   326  	return nil
   327  }
   328  
   329  func (m *mockEbpfTracer) IsCgroupMuted(cgroup uint64) bool {
   330  	return true
   331  }
   332  
   333  type mockSignatureEngine struct {
   334  	eventsChan chan *castaipb.Event
   335  }
   336  
   337  func (m *mockSignatureEngine) Events() <-chan *castaipb.Event {
   338  	return m.eventsChan
   339  }
   340  
   341  type mockEnrichmentService struct {
   342  	eventsChan chan *castaipb.Event
   343  }
   344  
   345  func (m *mockEnrichmentService) Events() <-chan *castaipb.Event {
   346  	return m.eventsChan
   347  }
   348  
   349  func (m *mockEnrichmentService) Enqueue(e *enrichment.EnrichRequest) bool {
   350  	return false
   351  }
   352  
   353  type mockKubeClient struct {
   354  }
   355  
   356  func (m *mockKubeClient) GetClusterInfo(ctx context.Context, in *kubepb.GetClusterInfoRequest, opts ...grpc.CallOption) (*kubepb.GetClusterInfoResponse, error) {
   357  	return &kubepb.GetClusterInfoResponse{
   358  		PodsCidr:    "10.0.0.0/16",
   359  		ServiceCidr: "172.168.0.0/16",
   360  	}, nil
   361  }
   362  
   363  func (m *mockKubeClient) GetIPInfo(ctx context.Context, in *kubepb.GetIPInfoRequest, opts ...grpc.CallOption) (*kubepb.GetIPInfoResponse, error) {
   364  	return &kubepb.GetIPInfoResponse{
   365  		Info: &kubepb.IPInfo{},
   366  	}, nil
   367  }
   368  
   369  func (m *mockKubeClient) GetPod(ctx context.Context, in *kubepb.GetPodRequest, opts ...grpc.CallOption) (*kubepb.GetPodResponse, error) {
   370  	return &kubepb.GetPodResponse{
   371  		Pod: &kubepb.Pod{},
   372  	}, nil
   373  }