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: ×tamp.Timestamp{Seconds: 1}, 45 }, 46 }, 47 {Timestamp: ×tamp.Timestamp{Seconds: 2}, Event: &observerpb.AgentEvent{}}, 48 {Timestamp: ×tamp.Timestamp{Seconds: 3}, Event: &observerpb.DebugEvent{}}, 49 {Timestamp: ×tamp.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: ×tamp.Timestamp{Seconds: 2}, Event: &observerpb.AgentEvent{}}, 76 {Timestamp: ×tamp.Timestamp{Seconds: 3}, Event: &observerpb.DebugEvent{}}, 77 {Timestamp: ×tamp.Timestamp{Seconds: 4}, Event: &observerpb.LostEvent{}}, 78 { 79 Event: &observerpb.Flow{ 80 NodeName: allowNodeName, 81 Time: ×tamp.Timestamp{Seconds: 12}, 82 }, 83 }, 84 { 85 Event: &observerpb.Flow{ 86 SourceNames: []string{"deny-pod/a"}, 87 NodeName: allowNodeName, 88 Time: ×tamp.Timestamp{Seconds: 13}, 89 }, 90 }, 91 { 92 Event: &observerpb.Flow{ 93 SourceNames: []string{"allow-pod/a"}, 94 NodeName: allowNodeName, 95 Time: ×tamp.Timestamp{Seconds: 14}, 96 }, 97 }, 98 { 99 Event: &observerpb.Flow{ 100 SourceNames: []string{"allow-pod/a"}, 101 NodeName: "another-node", 102 Time: ×tamp.Timestamp{Seconds: 15}, 103 }, 104 }, 105 { 106 Event: &observerpb.Flow{ 107 SourceNames: []string{"allow-pod/a"}, 108 NodeName: allowNodeName, 109 Time: ×tamp.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: ×tamp.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: ×tamp.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: ×tamp.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: ×tamp.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: ×tamp.Timestamp{Seconds: 12}, 228 Source: &flowpb.Endpoint{PodName: "podA", Namespace: "nsA"}, 229 }, 230 }, 231 { 232 Event: &observerpb.Flow{ 233 NodeName: "nodeName", 234 Time: ×tamp.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: ×tamp.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: ×tamp.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: ×tamp.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 }