github.com/cilium/cilium@v1.16.2/pkg/hubble/metrics/api/registry_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Hubble
     3  
     4  package api
     5  
     6  import (
     7  	"context"
     8  	"io"
     9  	"testing"
    10  
    11  	"github.com/prometheus/client_golang/prometheus"
    12  	"github.com/sirupsen/logrus"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  
    16  	pb "github.com/cilium/cilium/api/v1/flow"
    17  	slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1"
    18  	"github.com/cilium/cilium/pkg/k8s/types"
    19  	monitorAPI "github.com/cilium/cilium/pkg/monitor/api"
    20  )
    21  
    22  type testPlugin struct {
    23  	handler Handler
    24  }
    25  
    26  func (t *testPlugin) NewHandler() Handler {
    27  	return t.handler
    28  }
    29  
    30  func (t *testPlugin) HelpText() string {
    31  	return ""
    32  }
    33  
    34  type testHandler struct {
    35  	ContextOptions   *ContextOptions
    36  	counter          *prometheus.CounterVec
    37  	ProcessCalled    int
    38  	InitCalled       int
    39  	ListMetricCalled int
    40  }
    41  
    42  func (t *testHandler) Init(registry *prometheus.Registry, options Options) error {
    43  	t.InitCalled++
    44  	return nil
    45  }
    46  
    47  func (t *testHandler) Status() string {
    48  	return ""
    49  }
    50  
    51  func (t *testHandler) Context() *ContextOptions {
    52  	return t.ContextOptions
    53  }
    54  
    55  func (t *testHandler) ListMetricVec() []*prometheus.MetricVec {
    56  	t.ListMetricCalled++
    57  	return []*prometheus.MetricVec{t.counter.MetricVec}
    58  }
    59  
    60  func (t *testHandler) ProcessFlow(ctx context.Context, p *pb.Flow) error {
    61  	labels, _ := t.ContextOptions.GetLabelValues(p)
    62  	t.counter.WithLabelValues(labels...).Inc()
    63  	t.ProcessCalled++
    64  	return nil
    65  }
    66  
    67  func TestRegister(t *testing.T) {
    68  
    69  	flow1 := &pb.Flow{
    70  		EventType: &pb.CiliumEventType{Type: monitorAPI.MessageTypeAccessLog},
    71  		L7: &pb.Layer7{
    72  			Record: &pb.Layer7_Http{Http: &pb.HTTP{}},
    73  		},
    74  		Source:      &pb.Endpoint{Namespace: "foo", PodName: "foo-123", Workloads: []*pb.Workload{{Name: "worker"}}},
    75  		Destination: &pb.Endpoint{Namespace: "bar", PodName: "bar-123", Workloads: []*pb.Workload{{Name: "api"}}},
    76  		Verdict:     pb.Verdict_FORWARDED,
    77  	}
    78  
    79  	flow2 := &pb.Flow{
    80  		EventType: &pb.CiliumEventType{Type: monitorAPI.MessageTypeAccessLog},
    81  		L7: &pb.Layer7{
    82  			Record: &pb.Layer7_Http{Http: &pb.HTTP{}},
    83  		},
    84  		Source:      &pb.Endpoint{Namespace: "abc", PodName: "abc-456", Workloads: []*pb.Workload{{Name: "worker"}}},
    85  		Destination: &pb.Endpoint{Namespace: "bar", PodName: "bar-123", Workloads: []*pb.Workload{{Name: "api"}}},
    86  		Verdict:     pb.Verdict_FORWARDED,
    87  	}
    88  	log := logrus.New()
    89  	log.SetOutput(io.Discard)
    90  
    91  	t.Run("Should not register handler", func(t *testing.T) {
    92  
    93  		r := NewRegistry(log)
    94  
    95  		handler := &testHandler{}
    96  
    97  		r.Register("test", &testPlugin{handler: handler})
    98  
    99  		handlers, err := r.ConfigureHandlers(nil, Map{})
   100  		assert.EqualValues(t, err, nil)
   101  		assert.EqualValues(t, len(handlers.handlers), 0)
   102  	})
   103  
   104  	t.Run("Should register handler", func(t *testing.T) {
   105  
   106  		promRegistry := prometheus.NewRegistry()
   107  		opts, _ := ParseContextOptions(Options{"sourceContext": "pod", "destinationContext": "pod"})
   108  		initHandlers(t, opts, promRegistry, log)
   109  	})
   110  
   111  	t.Run("Should remove metrics series with ContextPod", func(t *testing.T) {
   112  
   113  		promRegistry := prometheus.NewRegistry()
   114  		opts, _ := ParseContextOptions(Options{"sourceContext": "pod", "destinationContext": "pod"})
   115  		handlers := initHandlers(t, opts, promRegistry, log)
   116  
   117  		handlers.ProcessFlow(context.TODO(), flow1)
   118  		handlers.ProcessFlow(context.TODO(), flow2)
   119  		assert.EqualValues(t, handlers.handlers[0].(*testHandler).ProcessCalled, 2)
   120  
   121  		verifyMetricSeriesExists(t, promRegistry, 2)
   122  
   123  		handlers.ProcessCiliumEndpointDeletion(&types.CiliumEndpoint{
   124  			ObjectMeta: slim_metav1.ObjectMeta{
   125  				Name:      "foo-123",
   126  				Namespace: "foo",
   127  			},
   128  		})
   129  		assert.EqualValues(t, handlers.handlers[0].(*testHandler).ListMetricCalled, 1)
   130  
   131  		verifyMetricSeriesExists(t, promRegistry, 1)
   132  
   133  		handlers.ProcessCiliumEndpointDeletion(&types.CiliumEndpoint{
   134  			ObjectMeta: slim_metav1.ObjectMeta{
   135  				Name:      "bar-123",
   136  				Namespace: "bar",
   137  			},
   138  		})
   139  		assert.EqualValues(t, handlers.handlers[0].(*testHandler).ListMetricCalled, 2)
   140  
   141  		verifyMetricSeriesNotExists(t, promRegistry)
   142  	})
   143  
   144  	t.Run("Should not remove metrics series with ContextWorkloadName", func(t *testing.T) {
   145  
   146  		promRegistry := prometheus.NewRegistry()
   147  		opts, _ := ParseContextOptions(Options{"sourceContext": "workload-name", "destinationContext": "workload-name"})
   148  		handlers := initHandlers(t, opts, promRegistry, log)
   149  
   150  		handlers.ProcessFlow(context.TODO(), flow1)
   151  		handlers.ProcessFlow(context.TODO(), flow2)
   152  		assert.EqualValues(t, handlers.handlers[0].(*testHandler).ProcessCalled, 2)
   153  
   154  		verifyMetricSeriesExists(t, promRegistry, 1)
   155  
   156  		handlers.ProcessCiliumEndpointDeletion(&types.CiliumEndpoint{
   157  			ObjectMeta: slim_metav1.ObjectMeta{
   158  				Name:      "foo-123",
   159  				Namespace: "foo",
   160  			},
   161  		})
   162  		assert.EqualValues(t, handlers.handlers[0].(*testHandler).ListMetricCalled, 1)
   163  
   164  		verifyMetricSeriesExists(t, promRegistry, 1)
   165  
   166  		handlers.ProcessCiliumEndpointDeletion(&types.CiliumEndpoint{
   167  			ObjectMeta: slim_metav1.ObjectMeta{
   168  				Name:      "bar-123",
   169  				Namespace: "bar",
   170  			},
   171  		})
   172  		assert.EqualValues(t, handlers.handlers[0].(*testHandler).ListMetricCalled, 2)
   173  
   174  		verifyMetricSeriesExists(t, promRegistry, 1)
   175  	})
   176  
   177  	t.Run("Should remove metrics series with LabelsContext", func(t *testing.T) {
   178  
   179  		promRegistry := prometheus.NewRegistry()
   180  		opts, _ := ParseContextOptions(Options{"labelsContext": "source_pod,source_namespace,destination_pod,destination_namespace"})
   181  		handlers := initHandlers(t, opts, promRegistry, log)
   182  
   183  		handlers.ProcessFlow(context.TODO(), flow1)
   184  		handlers.ProcessFlow(context.TODO(), flow2)
   185  		assert.EqualValues(t, handlers.handlers[0].(*testHandler).ProcessCalled, 2)
   186  
   187  		verifyMetricSeriesExists(t, promRegistry, 2)
   188  
   189  		handlers.ProcessCiliumEndpointDeletion(&types.CiliumEndpoint{
   190  			ObjectMeta: slim_metav1.ObjectMeta{
   191  				Name:      "foo-123",
   192  				Namespace: "foo",
   193  			},
   194  		})
   195  		assert.EqualValues(t, handlers.handlers[0].(*testHandler).ListMetricCalled, 1)
   196  
   197  		verifyMetricSeriesExists(t, promRegistry, 1)
   198  
   199  		handlers.ProcessCiliumEndpointDeletion(&types.CiliumEndpoint{
   200  			ObjectMeta: slim_metav1.ObjectMeta{
   201  				Name:      "bar-123",
   202  				Namespace: "bar",
   203  			},
   204  		})
   205  		assert.EqualValues(t, handlers.handlers[0].(*testHandler).ListMetricCalled, 2)
   206  
   207  		verifyMetricSeriesNotExists(t, promRegistry)
   208  	})
   209  
   210  	t.Run("Should not remove metrics series with LabelsContext without namespace", func(t *testing.T) {
   211  
   212  		promRegistry := prometheus.NewRegistry()
   213  		opts, _ := ParseContextOptions(Options{"labelsContext": "source_pod,destination_pod"})
   214  		handlers := initHandlers(t, opts, promRegistry, log)
   215  
   216  		handlers.ProcessFlow(context.TODO(), flow1)
   217  		handlers.ProcessFlow(context.TODO(), flow2)
   218  		assert.EqualValues(t, handlers.handlers[0].(*testHandler).ProcessCalled, 2)
   219  
   220  		verifyMetricSeriesExists(t, promRegistry, 2)
   221  
   222  		handlers.ProcessCiliumEndpointDeletion(&types.CiliumEndpoint{
   223  			ObjectMeta: slim_metav1.ObjectMeta{
   224  				Name:      "foo-123",
   225  				Namespace: "foo",
   226  			},
   227  		})
   228  		assert.EqualValues(t, handlers.handlers[0].(*testHandler).ListMetricCalled, 1)
   229  
   230  		verifyMetricSeriesExists(t, promRegistry, 2)
   231  
   232  		handlers.ProcessCiliumEndpointDeletion(&types.CiliumEndpoint{
   233  			ObjectMeta: slim_metav1.ObjectMeta{
   234  				Name:      "bar-123",
   235  				Namespace: "bar",
   236  			},
   237  		})
   238  		assert.EqualValues(t, handlers.handlers[0].(*testHandler).ListMetricCalled, 2)
   239  
   240  		verifyMetricSeriesExists(t, promRegistry, 2)
   241  	})
   242  
   243  }
   244  
   245  func initHandlers(t *testing.T, opts *ContextOptions, promRegistry *prometheus.Registry, log *logrus.Logger) *Handlers {
   246  	counter := prometheus.NewCounterVec(prometheus.CounterOpts{
   247  		Namespace: "test",
   248  		Name:      "events",
   249  	}, opts.GetLabelNames())
   250  	promRegistry.MustRegister(counter)
   251  
   252  	r := NewRegistry(log)
   253  
   254  	handler := &testHandler{}
   255  	handler.ContextOptions = opts
   256  	handler.counter = counter
   257  
   258  	r.Register("test", &testPlugin{handler: handler})
   259  
   260  	handlers, err := r.ConfigureHandlers(nil, Map{"test": Options{}})
   261  	assert.EqualValues(t, err, nil)
   262  	assert.EqualValues(t, len(handlers.handlers), 1)
   263  	assert.EqualValues(t, handlers.handlers[0].(*testHandler).InitCalled, 1)
   264  	return handlers
   265  }
   266  
   267  func verifyMetricSeriesExists(t *testing.T, promRegistry *prometheus.Registry, expectedCount int) {
   268  	metricFamilies, err := promRegistry.Gather()
   269  	require.NoError(t, err)
   270  	require.Len(t, metricFamilies, 1)
   271  	assert.Equal(t, "test_events", *metricFamilies[0].Name)
   272  	require.Len(t, metricFamilies[0].Metric, expectedCount)
   273  }
   274  
   275  func verifyMetricSeriesNotExists(t *testing.T, promRegistry *prometheus.Registry) {
   276  	metricFamilies, err := promRegistry.Gather()
   277  	require.NoError(t, err)
   278  	require.Len(t, metricFamilies, 0)
   279  }