From ad09396e7582b166152434e4036de5ff5038c6fe Mon Sep 17 00:00:00 2001 From: Leandro Beretta Date: Fri, 7 Nov 2025 12:44:29 -0300 Subject: [PATCH 1/9] improvements --- pkg/api/transform_network.go | 1 + .../transform/kubernetes/informers/config.go | 2 + .../kubernetes/informers/informers-mock.go | 78 ++++++ .../kubernetes/informers/informers.go | 253 +++++++++++++++++- .../kubernetes/informers/informers_test.go | 148 ++++++++++ 5 files changed, 472 insertions(+), 10 deletions(-) diff --git a/pkg/api/transform_network.go b/pkg/api/transform_network.go index 68a2e99c0..848784fdf 100644 --- a/pkg/api/transform_network.go +++ b/pkg/api/transform_network.go @@ -52,6 +52,7 @@ type NetworkTransformKubeConfig struct { ConfigPath string `yaml:"configPath,omitempty" json:"configPath,omitempty" doc:"path to kubeconfig file (optional)"` SecondaryNetworks []SecondaryNetwork `yaml:"secondaryNetworks,omitempty" json:"secondaryNetworks,omitempty" doc:"configuration for secondary networks"` ManagedCNI []string `yaml:"managedCNI,omitempty" json:"managedCNI,omitempty" doc:"a list of CNI (network plugins) to manage, for detecting additional interfaces. Currently supported: ovn"` + TrackedKinds []string `yaml:"trackedKinds,omitempty" json:"trackedKinds,omitempty" doc:"list of Kubernetes resource kinds to track for ownership chain (e.g., Deployment, Gateway, VirtualMachine). If a resource's owner is in this list, FLP will continue tracking up the ownership chain."` } type TransformNetworkOperationEnum string diff --git a/pkg/pipeline/transform/kubernetes/informers/config.go b/pkg/pipeline/transform/kubernetes/informers/config.go index 80bc35be3..e537ad14e 100644 --- a/pkg/pipeline/transform/kubernetes/informers/config.go +++ b/pkg/pipeline/transform/kubernetes/informers/config.go @@ -19,12 +19,14 @@ type Config struct { secondaryNetworks []api.SecondaryNetwork hasMultus bool hasUDN bool + trackedKinds []string } func NewConfig(cfg api.NetworkTransformKubeConfig) Config { c := Config{ managedCNI: cfg.ManagedCNI, secondaryNetworks: cfg.SecondaryNetworks, + trackedKinds: cfg.TrackedKinds, } if c.managedCNI == nil { c.managedCNI = []string{api.OVN} diff --git a/pkg/pipeline/transform/kubernetes/informers/informers-mock.go b/pkg/pipeline/transform/kubernetes/informers/informers-mock.go index cad81c48f..b95b50c22 100644 --- a/pkg/pipeline/transform/kubernetes/informers/informers-mock.go +++ b/pkg/pipeline/transform/kubernetes/informers/informers-mock.go @@ -135,6 +135,52 @@ func (m *IndexerMock) MockReplicaSet(name, namespace, ownerName, ownerKind strin }, true, nil) } +func (m *IndexerMock) MockDeployment(name, namespace, ownerName, ownerKind string) { + m.On("GetByKey", namespace+"/"+name).Return(&metav1.ObjectMeta{ + Name: name, + OwnerReferences: []metav1.OwnerReference{{ + Kind: ownerKind, + Name: ownerName, + }}, + }, true, nil) +} + +func (m *IndexerMock) MockGateway(name, namespace, ownerName, ownerKind string) { + if ownerName == "" { + // No owner + m.On("GetByKey", namespace+"/"+name).Return(&metav1.ObjectMeta{ + Name: name, + OwnerReferences: []metav1.OwnerReference{}, + }, true, nil) + } else { + m.On("GetByKey", namespace+"/"+name).Return(&metav1.ObjectMeta{ + Name: name, + OwnerReferences: []metav1.OwnerReference{{ + Kind: ownerKind, + Name: ownerName, + }}, + }, true, nil) + } +} + +func (m *IndexerMock) MockVirtualMachineInstance(name, namespace, ownerName, ownerKind string) { + m.On("GetByKey", namespace+"/"+name).Return(&metav1.ObjectMeta{ + Name: name, + OwnerReferences: []metav1.OwnerReference{{ + Kind: ownerKind, + Name: ownerName, + }}, + }, true, nil) +} + +func (m *IndexerMock) MockVirtualMachine(name, namespace string) { + // VirtualMachine typically has no owner + m.On("GetByKey", namespace+"/"+name).Return(&metav1.ObjectMeta{ + Name: name, + OwnerReferences: []metav1.OwnerReference{}, + }, true, nil) +} + func (m *IndexerMock) FallbackNotFound() { m.On("ByIndex", IndexIP, mock.Anything).Return([]interface{}{}, nil) } @@ -163,6 +209,38 @@ func SetupIndexerMocks(kd *Informers) (pods, nodes, svc, rs *IndexerMock) { return } +func SetupIndexerMocksWithTrackedKinds(kd *Informers, trackedKinds []string) (pods, nodes, svc, rs, deploy, gw, vmi, vm *IndexerMock) { + // Setup base informers + pods, nodes, svc, rs = SetupIndexerMocks(kd) + + // Setup additional informers based on trackedKinds + for _, kind := range trackedKinds { + switch kind { + case "Deployment": + deploy = &IndexerMock{} + dim := InformerMock{} + dim.On("GetIndexer").Return(deploy) + kd.deployments = &dim + case "Gateway": + gw = &IndexerMock{} + gim := InformerMock{} + gim.On("GetIndexer").Return(gw) + kd.gateways = &gim + case "VirtualMachineInstance": + vmi = &IndexerMock{} + vim := InformerMock{} + vim.On("GetIndexer").Return(vmi) + kd.virtualMachineInstances = &vim + case "VirtualMachine": + vm = &IndexerMock{} + vmim := InformerMock{} + vmim.On("GetIndexer").Return(vm) + kd.virtualMachines = &vmim + } + } + return +} + type FakeInformers struct { Interface ipInfo map[string]*model.ResourceMetaData diff --git a/pkg/pipeline/transform/kubernetes/informers/informers.go b/pkg/pipeline/transform/kubernetes/informers/informers.go index 546b65b8a..06c1b360d 100644 --- a/pkg/pipeline/transform/kubernetes/informers/informers.go +++ b/pkg/pipeline/transform/kubernetes/informers/informers.go @@ -66,6 +66,13 @@ type Informers struct { services cache.SharedIndexInformer // replicaSets caches the ReplicaSets as partially-filled *ObjectMeta pointers replicaSets cache.SharedIndexInformer + // New informers for ownership tracking + deployments cache.SharedIndexInformer + virtualMachineInstances cache.SharedIndexInformer + virtualMachines cache.SharedIndexInformer + gateways cache.SharedIndexInformer + // Config and channels + config Config stopChan chan struct{} mdStopChan chan struct{} indexerHitMetric *prometheus.CounterVec @@ -178,18 +185,125 @@ func (k *Informers) GetNodeByName(name string) (*model.ResourceMetaData, error) } func (k *Informers) checkParent(info *model.ResourceMetaData) { - if info.OwnerKind == "ReplicaSet" { - item, ok, err := k.replicaSets.GetIndexer().GetByKey(info.Namespace + "/" + info.OwnerName) - if err != nil { - log.WithError(err).WithField("key", info.Namespace+"/"+info.OwnerName). - Debug("can't get ReplicaSet info from informer. Ignoring") - } else if ok { - rsInfo := item.(*metav1.ObjectMeta) - if len(rsInfo.OwnerReferences) > 0 { - info.OwnerKind = rsInfo.OwnerReferences[0].Kind - info.OwnerName = rsInfo.OwnerReferences[0].Name + // Maximum 3 ownership hops: Pod → ReplicaSet → Deployment → Gateway + // This allows tracking up to 3 levels beyond the initial resource + const maxHops = 3 + + // If trackedKinds is empty, use legacy behavior (stop after ReplicaSet resolution) + if len(k.config.trackedKinds) == 0 { + // Legacy behavior: only resolve ReplicaSet + if info.OwnerKind == "ReplicaSet" { + item, ok, err := k.replicaSets.GetIndexer().GetByKey(info.Namespace + "/" + info.OwnerName) + if err != nil { + log.WithError(err).WithField("key", info.Namespace+"/"+info.OwnerName). + Debug("can't get ReplicaSet info from informer. Ignoring") + return + } + if ok { + rsInfo := item.(*metav1.ObjectMeta) + if len(rsInfo.OwnerReferences) > 0 { + info.OwnerKind = rsInfo.OwnerReferences[0].Kind + info.OwnerName = rsInfo.OwnerReferences[0].Name + } } } + return + } + + // New behavior with trackedKinds: traverse ownership chain until we find a tracked kind or hit max depth + for i := 0; i < maxHops; i++ { + // Check if current owner is in trackedKinds + if k.isTracked(info.OwnerKind) { + // This owner IS tracked. Try to get its parent to see if we can go higher. + parent := k.getOwnerFromInformer(info.OwnerKind, info.Namespace, info.OwnerName) + if parent == nil { + // No parent exists → STOP at current tracked kind + break + } + // Parent exists - check if parent is ALSO tracked + if k.isTracked(parent.Kind) { + // Parent is also tracked → update and continue (prefer higher level) + info.OwnerKind = parent.Kind + info.OwnerName = parent.Name + continue + } + // Parent exists but is NOT tracked → STOP at current tracked kind + break + } + + // Current owner is NOT tracked → try to find a tracked parent + parent := k.getOwnerFromInformer(info.OwnerKind, info.Namespace, info.OwnerName) + if parent == nil { + // No parent found → STOP at current (untracked) owner + break + } + + // Update to parent and continue + info.OwnerKind = parent.Kind + info.OwnerName = parent.Name + } +} + +// isTracked returns true if the given kind is in the trackedKinds configuration +func (k *Informers) isTracked(kind string) bool { + for _, tracked := range k.config.trackedKinds { + if tracked == kind { + return true + } + } + return false +} + +// OwnerInfo contains basic ownership information +type OwnerInfo struct { + Kind string + Name string +} + +// getOwnerFromInformer retrieves the owner of a resource from the appropriate informer +func (k *Informers) getOwnerFromInformer(kind, namespace, name string) *OwnerInfo { + var informer cache.SharedIndexInformer + + switch kind { + case "ReplicaSet": + informer = k.replicaSets + case "Deployment": + informer = k.deployments + case "Gateway": + informer = k.gateways + case "VirtualMachineInstance": + informer = k.virtualMachineInstances + case "VirtualMachine": + informer = k.virtualMachines + default: + return nil + } + + if informer == nil { + log.WithField("kind", kind).Debug("informer not initialized for this kind") + return nil + } + + item, ok, err := informer.GetIndexer().GetByKey(namespace + "/" + name) + if err != nil { + log.WithError(err). + WithField("kind", kind). + WithField("key", namespace+"/"+name). + Debug("can't get resource info from informer") + return nil + } + if !ok { + return nil + } + + meta := item.(*metav1.ObjectMeta) + if len(meta.OwnerReferences) == 0 { + return nil + } + + return &OwnerInfo{ + Kind: meta.OwnerReferences[0].Kind, + Name: meta.OwnerReferences[0].Name, } } @@ -376,8 +490,101 @@ func (k *Informers) initReplicaSetInformer(informerFactory metadatainformer.Shar return nil } +func (k *Informers) initDeploymentInformer(informerFactory metadatainformer.SharedInformerFactory) error { + k.deployments = informerFactory.ForResource( + schema.GroupVersionResource{ + Group: "apps", + Version: "v1", + Resource: "deployments", + }).Informer() + if err := k.deployments.SetTransform(func(i interface{}) (interface{}, error) { + deploy, ok := i.(*metav1.PartialObjectMetadata) + if !ok { + return nil, fmt.Errorf("was expecting a Deployment. Got: %T", i) + } + return &metav1.ObjectMeta{ + Name: deploy.Name, + Namespace: deploy.Namespace, + OwnerReferences: deploy.OwnerReferences, + }, nil + }); err != nil { + return fmt.Errorf("can't set Deployments transform: %w", err) + } + return nil +} + +func (k *Informers) initGatewayInformer(informerFactory metadatainformer.SharedInformerFactory) error { + k.gateways = informerFactory.ForResource( + schema.GroupVersionResource{ + Group: "gateway.networking.k8s.io", + Version: "v1", + Resource: "gateways", + }).Informer() + if err := k.gateways.SetTransform(func(i interface{}) (interface{}, error) { + gw, ok := i.(*metav1.PartialObjectMetadata) + if !ok { + return nil, fmt.Errorf("was expecting a Gateway. Got: %T", i) + } + return &metav1.ObjectMeta{ + Name: gw.Name, + Namespace: gw.Namespace, + OwnerReferences: gw.OwnerReferences, + }, nil + }); err != nil { + return fmt.Errorf("can't set Gateways transform: %w", err) + } + return nil +} + +func (k *Informers) initVirtualMachineInstanceInformer(informerFactory metadatainformer.SharedInformerFactory) error { + k.virtualMachineInstances = informerFactory.ForResource( + schema.GroupVersionResource{ + Group: "kubevirt.io", + Version: "v1", + Resource: "virtualmachineinstances", + }).Informer() + if err := k.virtualMachineInstances.SetTransform(func(i interface{}) (interface{}, error) { + vmi, ok := i.(*metav1.PartialObjectMetadata) + if !ok { + return nil, fmt.Errorf("was expecting a VirtualMachineInstance. Got: %T", i) + } + return &metav1.ObjectMeta{ + Name: vmi.Name, + Namespace: vmi.Namespace, + OwnerReferences: vmi.OwnerReferences, + }, nil + }); err != nil { + return fmt.Errorf("can't set VirtualMachineInstances transform: %w", err) + } + return nil +} + +func (k *Informers) initVirtualMachineInformer(informerFactory metadatainformer.SharedInformerFactory) error { + k.virtualMachines = informerFactory.ForResource( + schema.GroupVersionResource{ + Group: "kubevirt.io", + Version: "v1", + Resource: "virtualmachines", + }).Informer() + if err := k.virtualMachines.SetTransform(func(i interface{}) (interface{}, error) { + vm, ok := i.(*metav1.PartialObjectMetadata) + if !ok { + return nil, fmt.Errorf("was expecting a VirtualMachine. Got: %T", i) + } + return &metav1.ObjectMeta{ + Name: vm.Name, + Namespace: vm.Namespace, + OwnerReferences: vm.OwnerReferences, + }, nil + }); err != nil { + return fmt.Errorf("can't set VirtualMachines transform: %w", err) + } + return nil +} + func (k *Informers) InitFromConfig(kubeconfig string, infConfig Config, opMetrics *operational.Metrics) error { // Initialization variables + k.config = infConfig k.stopChan = make(chan struct{}) k.mdStopChan = make(chan struct{}) @@ -430,6 +637,32 @@ func (k *Informers) initInformers(client kubernetes.Interface, metaClient metada return err } + // Initialize additional informers based on trackedKinds configuration + for _, kind := range cfg.trackedKinds { + switch kind { + case "Deployment": + log.Debugf("initializing Deployment informer (trackedKinds)") + if err := k.initDeploymentInformer(metadataInformerFactory); err != nil { + return err + } + case "Gateway": + log.Debugf("initializing Gateway informer (trackedKinds)") + if err := k.initGatewayInformer(metadataInformerFactory); err != nil { + return err + } + case "VirtualMachineInstance": + log.Debugf("initializing VirtualMachineInstance informer (trackedKinds)") + if err := k.initVirtualMachineInstanceInformer(metadataInformerFactory); err != nil { + return err + } + case "VirtualMachine": + log.Debugf("initializing VirtualMachine informer (trackedKinds)") + if err := k.initVirtualMachineInformer(metadataInformerFactory); err != nil { + return err + } + } + } + // Informers expose an indexer log.Debugf("adding indexers") byIP := cache.Indexers{IndexIP: ipIndexer} diff --git a/pkg/pipeline/transform/kubernetes/informers/informers_test.go b/pkg/pipeline/transform/kubernetes/informers/informers_test.go index d28f20305..b27d303cb 100644 --- a/pkg/pipeline/transform/kubernetes/informers/informers_test.go +++ b/pkg/pipeline/transform/kubernetes/informers/informers_test.go @@ -121,3 +121,151 @@ func TestGetInfo(t *testing.T) { info = kubeData.IndexLookup(nil, "1.2.3.200") require.Nil(t, info) } + +// TestOwnershipTracking_GatewayAPI tests the ownership chain: Pod → ReplicaSet → Deployment → Gateway +func TestOwnershipTracking_GatewayAPI(t *testing.T) { + metrics := operational.NewMetrics(&config.MetricsSettings{}) + kubeData := Informers{ + indexerHitMetric: metrics.CreateIndexerHitCounter(), + config: Config{ + trackedKinds: []string{"ReplicaSet", "Deployment", "Gateway"}, + }, + } + + pidx, hidx, sidx, ridx, didx, gwidx, _, _ := SetupIndexerMocksWithTrackedKinds(&kubeData, []string{"Deployment", "Gateway"}) + + // Setup mocks for the ownership chain + ridx.MockReplicaSet("rs1", "test-ns", "deploy1", "Deployment") + ridx.FallbackNotFound() + didx.MockDeployment("deploy1", "test-ns", "gateway1", "Gateway") + gwidx.MockGateway("gateway1", "test-ns", "", "") // Gateway has no owner + + pidx.MockPod("1.2.3.4", "", "", "pod1", "test-ns", "10.0.0.1", "rs1", "ReplicaSet") + pidx.FallbackNotFound() + hidx.MockNode("10.0.0.1", "node1") + hidx.FallbackNotFound() + sidx.FallbackNotFound() + + // Test: Pod should resolve to Gateway as final owner + info := kubeData.IndexLookup(nil, "1.2.3.4") + require.NotNil(t, info) + require.Equal(t, "Gateway", info.OwnerKind) + require.Equal(t, "gateway1", info.OwnerName) +} + +// TestOwnershipTracking_OnlyDeployment tests when only Deployment is tracked (not Gateway) +func TestOwnershipTracking_OnlyDeployment(t *testing.T) { + metrics := operational.NewMetrics(&config.MetricsSettings{}) + kubeData := Informers{ + indexerHitMetric: metrics.CreateIndexerHitCounter(), + config: Config{ + trackedKinds: []string{"ReplicaSet", "Deployment"}, // Gateway NOT tracked + }, + } + + pidx, hidx, sidx, ridx, didx, _, _, _ := SetupIndexerMocksWithTrackedKinds(&kubeData, []string{"Deployment"}) + + ridx.MockReplicaSet("rs1", "test-ns", "deploy1", "Deployment") + ridx.FallbackNotFound() + didx.MockDeployment("deploy1", "test-ns", "gateway1", "Gateway") + + pidx.MockPod("1.2.3.4", "", "", "pod1", "test-ns", "10.0.0.1", "rs1", "ReplicaSet") + pidx.FallbackNotFound() + hidx.MockNode("10.0.0.1", "node1") + hidx.FallbackNotFound() + sidx.FallbackNotFound() + + // Test: Pod should resolve to Deployment (stops there because Gateway is not tracked) + info := kubeData.IndexLookup(nil, "1.2.3.4") + require.NotNil(t, info) + require.Equal(t, "Deployment", info.OwnerKind) + require.Equal(t, "deploy1", info.OwnerName) +} + +// TestOwnershipTracking_KubeVirt tests the ownership chain: Pod → VirtualMachineInstance → VirtualMachine +func TestOwnershipTracking_KubeVirt(t *testing.T) { + metrics := operational.NewMetrics(&config.MetricsSettings{}) + kubeData := Informers{ + indexerHitMetric: metrics.CreateIndexerHitCounter(), + config: Config{ + trackedKinds: []string{"VirtualMachineInstance", "VirtualMachine"}, + }, + } + + pidx, hidx, sidx, ridx, _, _, vmiidx, vmidx := SetupIndexerMocksWithTrackedKinds(&kubeData, []string{"VirtualMachineInstance", "VirtualMachine"}) + + ridx.FallbackNotFound() + vmiidx.MockVirtualMachineInstance("vmi1", "test-ns", "vm1", "VirtualMachine") + vmidx.MockVirtualMachine("vm1", "test-ns") // VM has no owner + + pidx.MockPod("1.2.3.4", "", "", "virt-launcher-pod", "test-ns", "10.0.0.1", "vmi1", "VirtualMachineInstance") + pidx.FallbackNotFound() + hidx.MockNode("10.0.0.1", "node1") + hidx.FallbackNotFound() + sidx.FallbackNotFound() + + // Test: Pod should resolve to VirtualMachine as final owner + info := kubeData.IndexLookup(nil, "1.2.3.4") + require.NotNil(t, info) + require.Equal(t, "VirtualMachine", info.OwnerKind) + require.Equal(t, "vm1", info.OwnerName) +} + +// TestOwnershipTracking_NoTrackedKinds tests backward compatibility (no trackedKinds configured) +func TestOwnershipTracking_NoTrackedKinds(t *testing.T) { + metrics := operational.NewMetrics(&config.MetricsSettings{}) + kubeData := Informers{ + indexerHitMetric: metrics.CreateIndexerHitCounter(), + config: Config{ + trackedKinds: []string{}, // Empty list + }, + } + + pidx, hidx, sidx, ridx := SetupIndexerMocks(&kubeData) + + ridx.MockReplicaSet("rs1", "test-ns", "deploy1", "Deployment") + ridx.FallbackNotFound() + + pidx.MockPod("1.2.3.4", "", "", "pod1", "test-ns", "10.0.0.1", "rs1", "ReplicaSet") + pidx.FallbackNotFound() + hidx.MockNode("10.0.0.1", "node1") + hidx.FallbackNotFound() + sidx.FallbackNotFound() + + // Test: Pod should resolve to Deployment (ReplicaSet is always processed) + info := kubeData.IndexLookup(nil, "1.2.3.4") + require.NotNil(t, info) + require.Equal(t, "Deployment", info.OwnerKind) + require.Equal(t, "deploy1", info.OwnerName) +} + +// TestOwnershipTracking_MaxDepth tests that ownership tracking stops at 3 levels +func TestOwnershipTracking_MaxDepth(t *testing.T) { + metrics := operational.NewMetrics(&config.MetricsSettings{}) + kubeData := Informers{ + indexerHitMetric: metrics.CreateIndexerHitCounter(), + config: Config{ + trackedKinds: []string{"ReplicaSet", "Deployment", "Gateway"}, + }, + } + + pidx, hidx, sidx, ridx, didx, gwidx, _, _ := SetupIndexerMocksWithTrackedKinds(&kubeData, []string{"Deployment", "Gateway"}) + + ridx.MockReplicaSet("rs1", "test-ns", "deploy1", "Deployment") + ridx.FallbackNotFound() + didx.MockDeployment("deploy1", "test-ns", "gateway1", "Gateway") + // Gateway has another owner (hypothetical 4th level) - should be ignored + gwidx.MockGateway("gateway1", "test-ns", "someother", "SomeKind") + + pidx.MockPod("1.2.3.4", "", "", "pod1", "test-ns", "10.0.0.1", "rs1", "ReplicaSet") + pidx.FallbackNotFound() + hidx.MockNode("10.0.0.1", "node1") + hidx.FallbackNotFound() + sidx.FallbackNotFound() + + // Test: Should stop at Gateway (3rd level), not continue to 4th level + info := kubeData.IndexLookup(nil, "1.2.3.4") + require.NotNil(t, info) + require.Equal(t, "Gateway", info.OwnerKind) + require.Equal(t, "gateway1", info.OwnerName) +} From 613ac8eaa2c919b7fbfb05786d6df935f99be000 Mon Sep 17 00:00:00 2001 From: Leandro Beretta Date: Mon, 10 Nov 2025 09:47:24 -0300 Subject: [PATCH 2/9] fix lint issue --- pkg/pipeline/transform/kubernetes/datasource/datasource.go | 4 ++-- pkg/pipeline/transform/kubernetes/enrich.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/pipeline/transform/kubernetes/datasource/datasource.go b/pkg/pipeline/transform/kubernetes/datasource/datasource.go index bf52fd5eb..8446be696 100644 --- a/pkg/pipeline/transform/kubernetes/datasource/datasource.go +++ b/pkg/pipeline/transform/kubernetes/datasource/datasource.go @@ -11,9 +11,9 @@ type Datasource struct { Informers informers.Interface } -func NewInformerDatasource(kubeconfig string, infConfig informers.Config, opMetrics *operational.Metrics) (*Datasource, error) { +func NewInformerDatasource(kubeconfig string, infConfig *informers.Config, opMetrics *operational.Metrics) (*Datasource, error) { inf := &informers.Informers{} - if err := inf.InitFromConfig(kubeconfig, infConfig, opMetrics); err != nil { + if err := inf.InitFromConfig(kubeconfig, *infConfig, opMetrics); err != nil { return nil, err } return &Datasource{Informers: inf}, nil diff --git a/pkg/pipeline/transform/kubernetes/enrich.go b/pkg/pipeline/transform/kubernetes/enrich.go index 34b9570a0..9da50fa01 100644 --- a/pkg/pipeline/transform/kubernetes/enrich.go +++ b/pkg/pipeline/transform/kubernetes/enrich.go @@ -25,7 +25,7 @@ func InitInformerDatasource(config api.NetworkTransformKubeConfig, opMetrics *op var err error infConfig = informers.NewConfig(config) if ds == nil { - ds, err = datasource.NewInformerDatasource(config.ConfigPath, infConfig, opMetrics) + ds, err = datasource.NewInformerDatasource(config.ConfigPath, &infConfig, opMetrics) } return err } From 5107dd29e56333ebab7de4aa66c545ee0e9b822f Mon Sep 17 00:00:00 2001 From: Leandro Beretta Date: Mon, 10 Nov 2025 10:10:19 -0300 Subject: [PATCH 3/9] fix lint issue --- pkg/pipeline/transform/kubernetes/enrich.go | 4 ++-- pkg/pipeline/transform/transform_network.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/pipeline/transform/kubernetes/enrich.go b/pkg/pipeline/transform/kubernetes/enrich.go index 9da50fa01..f2f12253f 100644 --- a/pkg/pipeline/transform/kubernetes/enrich.go +++ b/pkg/pipeline/transform/kubernetes/enrich.go @@ -21,9 +21,9 @@ func MockInformers() { ds = &datasource.Datasource{Informers: informers.NewInformersMock()} } -func InitInformerDatasource(config api.NetworkTransformKubeConfig, opMetrics *operational.Metrics) error { +func InitInformerDatasource(config *api.NetworkTransformKubeConfig, opMetrics *operational.Metrics) error { var err error - infConfig = informers.NewConfig(config) + infConfig = informers.NewConfig(*config) if ds == nil { ds, err = datasource.NewInformerDatasource(config.ConfigPath, &infConfig, opMetrics) } diff --git a/pkg/pipeline/transform/transform_network.go b/pkg/pipeline/transform/transform_network.go index 45daba68e..9e806f780 100644 --- a/pkg/pipeline/transform/transform_network.go +++ b/pkg/pipeline/transform/transform_network.go @@ -219,7 +219,7 @@ func NewTransformNetwork(params config.StageParam, opMetrics *operational.Metric } if needToInitKubeData { - err := kubernetes.InitInformerDatasource(jsonNetworkTransform.KubeConfig, opMetrics) + err := kubernetes.InitInformerDatasource(&jsonNetworkTransform.KubeConfig, opMetrics) if err != nil { return nil, err } From 4e9fcb3b822b9a1541251e53489cc5b2cb7ebc32 Mon Sep 17 00:00:00 2001 From: Leandro Beretta Date: Mon, 10 Nov 2025 10:37:00 -0300 Subject: [PATCH 4/9] pass object by pointers --- .../transform/kubernetes/datasource/datasource.go | 2 +- pkg/pipeline/transform/kubernetes/enrich.go | 4 ++-- pkg/pipeline/transform/kubernetes/informers/config.go | 2 +- .../transform/kubernetes/informers/informers-mock.go | 6 +++--- .../transform/kubernetes/informers/informers.go | 10 +++++----- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pkg/pipeline/transform/kubernetes/datasource/datasource.go b/pkg/pipeline/transform/kubernetes/datasource/datasource.go index 8446be696..4b3803474 100644 --- a/pkg/pipeline/transform/kubernetes/datasource/datasource.go +++ b/pkg/pipeline/transform/kubernetes/datasource/datasource.go @@ -13,7 +13,7 @@ type Datasource struct { func NewInformerDatasource(kubeconfig string, infConfig *informers.Config, opMetrics *operational.Metrics) (*Datasource, error) { inf := &informers.Informers{} - if err := inf.InitFromConfig(kubeconfig, *infConfig, opMetrics); err != nil { + if err := inf.InitFromConfig(kubeconfig, infConfig, opMetrics); err != nil { return nil, err } return &Datasource{Informers: inf}, nil diff --git a/pkg/pipeline/transform/kubernetes/enrich.go b/pkg/pipeline/transform/kubernetes/enrich.go index f2f12253f..c71c2cda6 100644 --- a/pkg/pipeline/transform/kubernetes/enrich.go +++ b/pkg/pipeline/transform/kubernetes/enrich.go @@ -17,13 +17,13 @@ var infConfig informers.Config // For testing func MockInformers() { - infConfig = informers.NewConfig(api.NetworkTransformKubeConfig{}) + infConfig = informers.NewConfig(&api.NetworkTransformKubeConfig{}) ds = &datasource.Datasource{Informers: informers.NewInformersMock()} } func InitInformerDatasource(config *api.NetworkTransformKubeConfig, opMetrics *operational.Metrics) error { var err error - infConfig = informers.NewConfig(*config) + infConfig = informers.NewConfig(config) if ds == nil { ds, err = datasource.NewInformerDatasource(config.ConfigPath, &infConfig, opMetrics) } diff --git a/pkg/pipeline/transform/kubernetes/informers/config.go b/pkg/pipeline/transform/kubernetes/informers/config.go index e537ad14e..c773f9e26 100644 --- a/pkg/pipeline/transform/kubernetes/informers/config.go +++ b/pkg/pipeline/transform/kubernetes/informers/config.go @@ -22,7 +22,7 @@ type Config struct { trackedKinds []string } -func NewConfig(cfg api.NetworkTransformKubeConfig) Config { +func NewConfig(cfg *api.NetworkTransformKubeConfig) Config { c := Config{ managedCNI: cfg.ManagedCNI, secondaryNetworks: cfg.SecondaryNetworks, diff --git a/pkg/pipeline/transform/kubernetes/informers/informers-mock.go b/pkg/pipeline/transform/kubernetes/informers/informers-mock.go index b95b50c22..40690344c 100644 --- a/pkg/pipeline/transform/kubernetes/informers/informers-mock.go +++ b/pkg/pipeline/transform/kubernetes/informers/informers-mock.go @@ -36,7 +36,7 @@ func NewInformersMock() *Mock { return inf } -func (o *Mock) InitFromConfig(kubeconfig string, infConfig Config, opMetrics *operational.Metrics) error { +func (o *Mock) InitFromConfig(kubeconfig string, infConfig *Config, opMetrics *operational.Metrics) error { args := o.Called(kubeconfig, infConfig, opMetrics) return args.Error(0) } @@ -249,7 +249,7 @@ type FakeInformers struct { } func SetupStubs(ipInfo, customKeysInfo, nodes map[string]*model.ResourceMetaData) (Config, *FakeInformers) { - cfg := NewConfig(api.NetworkTransformKubeConfig{SecondaryNetworks: secondaryNetConfig}) + cfg := NewConfig(&api.NetworkTransformKubeConfig{SecondaryNetworks: secondaryNetConfig}) return cfg, &FakeInformers{ ipInfo: ipInfo, customKeysInfo: customKeysInfo, @@ -257,7 +257,7 @@ func SetupStubs(ipInfo, customKeysInfo, nodes map[string]*model.ResourceMetaData } } -func (f *FakeInformers) InitFromConfig(_ string, _ Config, _ *operational.Metrics) error { +func (f *FakeInformers) InitFromConfig(_ string, _ *Config, _ *operational.Metrics) error { return nil } diff --git a/pkg/pipeline/transform/kubernetes/informers/informers.go b/pkg/pipeline/transform/kubernetes/informers/informers.go index 06c1b360d..035fd044a 100644 --- a/pkg/pipeline/transform/kubernetes/informers/informers.go +++ b/pkg/pipeline/transform/kubernetes/informers/informers.go @@ -316,7 +316,7 @@ func (k *Informers) getHostName(hostIP string) string { return "" } -func (k *Informers) initNodeInformer(informerFactory inf.SharedInformerFactory, cfg Config) error { +func (k *Informers) initNodeInformer(informerFactory inf.SharedInformerFactory, cfg *Config) error { nodes := informerFactory.Core().V1().Nodes().Informer() // Transform any *v1.Node instance into a *Info instance to save space // in the informer's cache @@ -368,7 +368,7 @@ func (k *Informers) initNodeInformer(informerFactory inf.SharedInformerFactory, return nil } -func (k *Informers) initPodInformer(informerFactory inf.SharedInformerFactory, cfg Config, dynClient *dynamic.DynamicClient) error { +func (k *Informers) initPodInformer(informerFactory inf.SharedInformerFactory, cfg *Config, dynClient *dynamic.DynamicClient) error { pods := informerFactory.Core().V1().Pods().Informer() // Transform any *v1.Pod instance into a *Info instance to save space // in the informer's cache @@ -582,9 +582,9 @@ func (k *Informers) initVirtualMachineInformer(informerFactory metadatainformer. return nil } -func (k *Informers) InitFromConfig(kubeconfig string, infConfig Config, opMetrics *operational.Metrics) error { +func (k *Informers) InitFromConfig(kubeconfig string, infConfig *Config, opMetrics *operational.Metrics) error { // Initialization variables - k.config = infConfig + k.config = *infConfig k.stopChan = make(chan struct{}) k.mdStopChan = make(chan struct{}) @@ -617,7 +617,7 @@ func (k *Informers) InitFromConfig(kubeconfig string, infConfig Config, opMetric return nil } -func (k *Informers) initInformers(client kubernetes.Interface, metaClient metadata.Interface, dynClient *dynamic.DynamicClient, cfg Config) error { +func (k *Informers) initInformers(client kubernetes.Interface, metaClient metadata.Interface, dynClient *dynamic.DynamicClient, cfg *Config) error { informerFactory := inf.NewSharedInformerFactory(client, syncTime) metadataInformerFactory := metadatainformer.NewSharedInformerFactory(metaClient, syncTime) err := k.initNodeInformer(informerFactory, cfg) From 32c61a00953b5f386d7201abf2b88e38763ef3bd Mon Sep 17 00:00:00 2001 From: Leandro Beretta Date: Mon, 10 Nov 2025 10:47:23 -0300 Subject: [PATCH 5/9] fix linter --- pkg/pipeline/transform/kubernetes/informers/informers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/pipeline/transform/kubernetes/informers/informers.go b/pkg/pipeline/transform/kubernetes/informers/informers.go index 035fd044a..f092f06f8 100644 --- a/pkg/pipeline/transform/kubernetes/informers/informers.go +++ b/pkg/pipeline/transform/kubernetes/informers/informers.go @@ -55,7 +55,7 @@ var ( type Interface interface { IndexLookup([]cni.SecondaryNetKey, string) *model.ResourceMetaData GetNodeByName(string) (*model.ResourceMetaData, error) - InitFromConfig(string, Config, *operational.Metrics) error + InitFromConfig(string, *Config, *operational.Metrics) error } type Informers struct { @@ -65,7 +65,7 @@ type Informers struct { nodes cache.SharedIndexInformer services cache.SharedIndexInformer // replicaSets caches the ReplicaSets as partially-filled *ObjectMeta pointers - replicaSets cache.SharedIndexInformer + replicaSets cache.SharedIndexInformer // New informers for ownership tracking deployments cache.SharedIndexInformer virtualMachineInstances cache.SharedIndexInformer From 6a362817e300f8ebbb2b8890592366164f09589b Mon Sep 17 00:00:00 2001 From: Leandro Beretta Date: Mon, 17 Nov 2025 10:39:01 -0300 Subject: [PATCH 6/9] clean vmi and vm changes --- .../kubernetes/informers/informers-mock.go | 30 +-------- .../kubernetes/informers/informers.go | 66 +------------------ .../kubernetes/informers/informers_test.go | 37 ++--------- 3 files changed, 7 insertions(+), 126 deletions(-) diff --git a/pkg/pipeline/transform/kubernetes/informers/informers-mock.go b/pkg/pipeline/transform/kubernetes/informers/informers-mock.go index 40690344c..4dd14494d 100644 --- a/pkg/pipeline/transform/kubernetes/informers/informers-mock.go +++ b/pkg/pipeline/transform/kubernetes/informers/informers-mock.go @@ -163,24 +163,6 @@ func (m *IndexerMock) MockGateway(name, namespace, ownerName, ownerKind string) } } -func (m *IndexerMock) MockVirtualMachineInstance(name, namespace, ownerName, ownerKind string) { - m.On("GetByKey", namespace+"/"+name).Return(&metav1.ObjectMeta{ - Name: name, - OwnerReferences: []metav1.OwnerReference{{ - Kind: ownerKind, - Name: ownerName, - }}, - }, true, nil) -} - -func (m *IndexerMock) MockVirtualMachine(name, namespace string) { - // VirtualMachine typically has no owner - m.On("GetByKey", namespace+"/"+name).Return(&metav1.ObjectMeta{ - Name: name, - OwnerReferences: []metav1.OwnerReference{}, - }, true, nil) -} - func (m *IndexerMock) FallbackNotFound() { m.On("ByIndex", IndexIP, mock.Anything).Return([]interface{}{}, nil) } @@ -209,7 +191,7 @@ func SetupIndexerMocks(kd *Informers) (pods, nodes, svc, rs *IndexerMock) { return } -func SetupIndexerMocksWithTrackedKinds(kd *Informers, trackedKinds []string) (pods, nodes, svc, rs, deploy, gw, vmi, vm *IndexerMock) { +func SetupIndexerMocksWithTrackedKinds(kd *Informers, trackedKinds []string) (pods, nodes, svc, rs, deploy, gw *IndexerMock) { // Setup base informers pods, nodes, svc, rs = SetupIndexerMocks(kd) @@ -226,16 +208,6 @@ func SetupIndexerMocksWithTrackedKinds(kd *Informers, trackedKinds []string) (po gim := InformerMock{} gim.On("GetIndexer").Return(gw) kd.gateways = &gim - case "VirtualMachineInstance": - vmi = &IndexerMock{} - vim := InformerMock{} - vim.On("GetIndexer").Return(vmi) - kd.virtualMachineInstances = &vim - case "VirtualMachine": - vm = &IndexerMock{} - vmim := InformerMock{} - vmim.On("GetIndexer").Return(vm) - kd.virtualMachines = &vmim } } return diff --git a/pkg/pipeline/transform/kubernetes/informers/informers.go b/pkg/pipeline/transform/kubernetes/informers/informers.go index f092f06f8..1af658f28 100644 --- a/pkg/pipeline/transform/kubernetes/informers/informers.go +++ b/pkg/pipeline/transform/kubernetes/informers/informers.go @@ -67,10 +67,8 @@ type Informers struct { // replicaSets caches the ReplicaSets as partially-filled *ObjectMeta pointers replicaSets cache.SharedIndexInformer // New informers for ownership tracking - deployments cache.SharedIndexInformer - virtualMachineInstances cache.SharedIndexInformer - virtualMachines cache.SharedIndexInformer - gateways cache.SharedIndexInformer + deployments cache.SharedIndexInformer + gateways cache.SharedIndexInformer // Config and channels config Config stopChan chan struct{} @@ -271,10 +269,6 @@ func (k *Informers) getOwnerFromInformer(kind, namespace, name string) *OwnerInf informer = k.deployments case "Gateway": informer = k.gateways - case "VirtualMachineInstance": - informer = k.virtualMachineInstances - case "VirtualMachine": - informer = k.virtualMachines default: return nil } @@ -536,52 +530,6 @@ func (k *Informers) initGatewayInformer(informerFactory metadatainformer.SharedI return nil } -func (k *Informers) initVirtualMachineInstanceInformer(informerFactory metadatainformer.SharedInformerFactory) error { - k.virtualMachineInstances = informerFactory.ForResource( - schema.GroupVersionResource{ - Group: "kubevirt.io", - Version: "v1", - Resource: "virtualmachineinstances", - }).Informer() - if err := k.virtualMachineInstances.SetTransform(func(i interface{}) (interface{}, error) { - vmi, ok := i.(*metav1.PartialObjectMetadata) - if !ok { - return nil, fmt.Errorf("was expecting a VirtualMachineInstance. Got: %T", i) - } - return &metav1.ObjectMeta{ - Name: vmi.Name, - Namespace: vmi.Namespace, - OwnerReferences: vmi.OwnerReferences, - }, nil - }); err != nil { - return fmt.Errorf("can't set VirtualMachineInstances transform: %w", err) - } - return nil -} - -func (k *Informers) initVirtualMachineInformer(informerFactory metadatainformer.SharedInformerFactory) error { - k.virtualMachines = informerFactory.ForResource( - schema.GroupVersionResource{ - Group: "kubevirt.io", - Version: "v1", - Resource: "virtualmachines", - }).Informer() - if err := k.virtualMachines.SetTransform(func(i interface{}) (interface{}, error) { - vm, ok := i.(*metav1.PartialObjectMetadata) - if !ok { - return nil, fmt.Errorf("was expecting a VirtualMachine. Got: %T", i) - } - return &metav1.ObjectMeta{ - Name: vm.Name, - Namespace: vm.Namespace, - OwnerReferences: vm.OwnerReferences, - }, nil - }); err != nil { - return fmt.Errorf("can't set VirtualMachines transform: %w", err) - } - return nil -} - func (k *Informers) InitFromConfig(kubeconfig string, infConfig *Config, opMetrics *operational.Metrics) error { // Initialization variables k.config = *infConfig @@ -650,16 +598,6 @@ func (k *Informers) initInformers(client kubernetes.Interface, metaClient metada if err := k.initGatewayInformer(metadataInformerFactory); err != nil { return err } - case "VirtualMachineInstance": - log.Debugf("initializing VirtualMachineInstance informer (trackedKinds)") - if err := k.initVirtualMachineInstanceInformer(metadataInformerFactory); err != nil { - return err - } - case "VirtualMachine": - log.Debugf("initializing VirtualMachine informer (trackedKinds)") - if err := k.initVirtualMachineInformer(metadataInformerFactory); err != nil { - return err - } } } diff --git a/pkg/pipeline/transform/kubernetes/informers/informers_test.go b/pkg/pipeline/transform/kubernetes/informers/informers_test.go index b27d303cb..63318479f 100644 --- a/pkg/pipeline/transform/kubernetes/informers/informers_test.go +++ b/pkg/pipeline/transform/kubernetes/informers/informers_test.go @@ -128,11 +128,11 @@ func TestOwnershipTracking_GatewayAPI(t *testing.T) { kubeData := Informers{ indexerHitMetric: metrics.CreateIndexerHitCounter(), config: Config{ - trackedKinds: []string{"ReplicaSet", "Deployment", "Gateway"}, + trackedKinds: []string{"Deployment", "Gateway"}, }, } - pidx, hidx, sidx, ridx, didx, gwidx, _, _ := SetupIndexerMocksWithTrackedKinds(&kubeData, []string{"Deployment", "Gateway"}) + pidx, hidx, sidx, ridx, didx, gwidx := SetupIndexerMocksWithTrackedKinds(&kubeData, []string{"Deployment", "Gateway"}) // Setup mocks for the ownership chain ridx.MockReplicaSet("rs1", "test-ns", "deploy1", "Deployment") @@ -163,7 +163,7 @@ func TestOwnershipTracking_OnlyDeployment(t *testing.T) { }, } - pidx, hidx, sidx, ridx, didx, _, _, _ := SetupIndexerMocksWithTrackedKinds(&kubeData, []string{"Deployment"}) + pidx, hidx, sidx, ridx, didx, _ := SetupIndexerMocksWithTrackedKinds(&kubeData, []string{"Deployment"}) ridx.MockReplicaSet("rs1", "test-ns", "deploy1", "Deployment") ridx.FallbackNotFound() @@ -182,35 +182,6 @@ func TestOwnershipTracking_OnlyDeployment(t *testing.T) { require.Equal(t, "deploy1", info.OwnerName) } -// TestOwnershipTracking_KubeVirt tests the ownership chain: Pod → VirtualMachineInstance → VirtualMachine -func TestOwnershipTracking_KubeVirt(t *testing.T) { - metrics := operational.NewMetrics(&config.MetricsSettings{}) - kubeData := Informers{ - indexerHitMetric: metrics.CreateIndexerHitCounter(), - config: Config{ - trackedKinds: []string{"VirtualMachineInstance", "VirtualMachine"}, - }, - } - - pidx, hidx, sidx, ridx, _, _, vmiidx, vmidx := SetupIndexerMocksWithTrackedKinds(&kubeData, []string{"VirtualMachineInstance", "VirtualMachine"}) - - ridx.FallbackNotFound() - vmiidx.MockVirtualMachineInstance("vmi1", "test-ns", "vm1", "VirtualMachine") - vmidx.MockVirtualMachine("vm1", "test-ns") // VM has no owner - - pidx.MockPod("1.2.3.4", "", "", "virt-launcher-pod", "test-ns", "10.0.0.1", "vmi1", "VirtualMachineInstance") - pidx.FallbackNotFound() - hidx.MockNode("10.0.0.1", "node1") - hidx.FallbackNotFound() - sidx.FallbackNotFound() - - // Test: Pod should resolve to VirtualMachine as final owner - info := kubeData.IndexLookup(nil, "1.2.3.4") - require.NotNil(t, info) - require.Equal(t, "VirtualMachine", info.OwnerKind) - require.Equal(t, "vm1", info.OwnerName) -} - // TestOwnershipTracking_NoTrackedKinds tests backward compatibility (no trackedKinds configured) func TestOwnershipTracking_NoTrackedKinds(t *testing.T) { metrics := operational.NewMetrics(&config.MetricsSettings{}) @@ -249,7 +220,7 @@ func TestOwnershipTracking_MaxDepth(t *testing.T) { }, } - pidx, hidx, sidx, ridx, didx, gwidx, _, _ := SetupIndexerMocksWithTrackedKinds(&kubeData, []string{"Deployment", "Gateway"}) + pidx, hidx, sidx, ridx, didx, gwidx := SetupIndexerMocksWithTrackedKinds(&kubeData, []string{"Deployment", "Gateway"}) ridx.MockReplicaSet("rs1", "test-ns", "deploy1", "Deployment") ridx.FallbackNotFound() From 7cea2b332a10dcf0ac1a5d43f97a7eb4d38cae54 Mon Sep 17 00:00:00 2001 From: Leandro Beretta Date: Mon, 17 Nov 2025 11:01:39 -0300 Subject: [PATCH 7/9] remove extra informers that are not needed --- .../kubernetes/informers/informers-mock.go | 30 +++++----------- .../kubernetes/informers/informers.go | 34 ++----------------- .../kubernetes/informers/informers_test.go | 14 ++++---- 3 files changed, 17 insertions(+), 61 deletions(-) diff --git a/pkg/pipeline/transform/kubernetes/informers/informers-mock.go b/pkg/pipeline/transform/kubernetes/informers/informers-mock.go index 4dd14494d..0ef89e7f7 100644 --- a/pkg/pipeline/transform/kubernetes/informers/informers-mock.go +++ b/pkg/pipeline/transform/kubernetes/informers/informers-mock.go @@ -136,16 +136,6 @@ func (m *IndexerMock) MockReplicaSet(name, namespace, ownerName, ownerKind strin } func (m *IndexerMock) MockDeployment(name, namespace, ownerName, ownerKind string) { - m.On("GetByKey", namespace+"/"+name).Return(&metav1.ObjectMeta{ - Name: name, - OwnerReferences: []metav1.OwnerReference{{ - Kind: ownerKind, - Name: ownerName, - }}, - }, true, nil) -} - -func (m *IndexerMock) MockGateway(name, namespace, ownerName, ownerKind string) { if ownerName == "" { // No owner m.On("GetByKey", namespace+"/"+name).Return(&metav1.ObjectMeta{ @@ -191,23 +181,21 @@ func SetupIndexerMocks(kd *Informers) (pods, nodes, svc, rs *IndexerMock) { return } -func SetupIndexerMocksWithTrackedKinds(kd *Informers, trackedKinds []string) (pods, nodes, svc, rs, deploy, gw *IndexerMock) { +func SetupIndexerMocksWithTrackedKinds(kd *Informers, trackedKinds []string) (pods, nodes, svc, rs, deploy *IndexerMock) { // Setup base informers pods, nodes, svc, rs = SetupIndexerMocks(kd) // Setup additional informers based on trackedKinds for _, kind := range trackedKinds { switch kind { - case "Deployment": - deploy = &IndexerMock{} - dim := InformerMock{} - dim.On("GetIndexer").Return(deploy) - kd.deployments = &dim - case "Gateway": - gw = &IndexerMock{} - gim := InformerMock{} - gim.On("GetIndexer").Return(gw) - kd.gateways = &gim + case "Deployment", "Gateway": + // Gateway requires Deployment informer, so we initialize it for both + if deploy == nil { + deploy = &IndexerMock{} + dim := InformerMock{} + dim.On("GetIndexer").Return(deploy) + kd.deployments = &dim + } } } return diff --git a/pkg/pipeline/transform/kubernetes/informers/informers.go b/pkg/pipeline/transform/kubernetes/informers/informers.go index 1af658f28..1c3a52b1d 100644 --- a/pkg/pipeline/transform/kubernetes/informers/informers.go +++ b/pkg/pipeline/transform/kubernetes/informers/informers.go @@ -68,7 +68,6 @@ type Informers struct { replicaSets cache.SharedIndexInformer // New informers for ownership tracking deployments cache.SharedIndexInformer - gateways cache.SharedIndexInformer // Config and channels config Config stopChan chan struct{} @@ -267,8 +266,6 @@ func (k *Informers) getOwnerFromInformer(kind, namespace, name string) *OwnerInf informer = k.replicaSets case "Deployment": informer = k.deployments - case "Gateway": - informer = k.gateways default: return nil } @@ -507,29 +504,6 @@ func (k *Informers) initDeploymentInformer(informerFactory metadatainformer.Shar return nil } -func (k *Informers) initGatewayInformer(informerFactory metadatainformer.SharedInformerFactory) error { - k.gateways = informerFactory.ForResource( - schema.GroupVersionResource{ - Group: "gateway.networking.k8s.io", - Version: "v1", - Resource: "gateways", - }).Informer() - if err := k.gateways.SetTransform(func(i interface{}) (interface{}, error) { - gw, ok := i.(*metav1.PartialObjectMetadata) - if !ok { - return nil, fmt.Errorf("was expecting a Gateway. Got: %T", i) - } - return &metav1.ObjectMeta{ - Name: gw.Name, - Namespace: gw.Namespace, - OwnerReferences: gw.OwnerReferences, - }, nil - }); err != nil { - return fmt.Errorf("can't set Gateways transform: %w", err) - } - return nil -} - func (k *Informers) InitFromConfig(kubeconfig string, infConfig *Config, opMetrics *operational.Metrics) error { // Initialization variables k.config = *infConfig @@ -588,16 +562,12 @@ func (k *Informers) initInformers(client kubernetes.Interface, metaClient metada // Initialize additional informers based on trackedKinds configuration for _, kind := range cfg.trackedKinds { switch kind { - case "Deployment": + case "Deployment", "Gateway": + // Gateway requires Deployment informer to navigate ownership chain log.Debugf("initializing Deployment informer (trackedKinds)") if err := k.initDeploymentInformer(metadataInformerFactory); err != nil { return err } - case "Gateway": - log.Debugf("initializing Gateway informer (trackedKinds)") - if err := k.initGatewayInformer(metadataInformerFactory); err != nil { - return err - } } } diff --git a/pkg/pipeline/transform/kubernetes/informers/informers_test.go b/pkg/pipeline/transform/kubernetes/informers/informers_test.go index 63318479f..dd7c18ae2 100644 --- a/pkg/pipeline/transform/kubernetes/informers/informers_test.go +++ b/pkg/pipeline/transform/kubernetes/informers/informers_test.go @@ -132,13 +132,12 @@ func TestOwnershipTracking_GatewayAPI(t *testing.T) { }, } - pidx, hidx, sidx, ridx, didx, gwidx := SetupIndexerMocksWithTrackedKinds(&kubeData, []string{"Deployment", "Gateway"}) + pidx, hidx, sidx, ridx, didx := SetupIndexerMocksWithTrackedKinds(&kubeData, []string{"Deployment", "Gateway"}) // Setup mocks for the ownership chain ridx.MockReplicaSet("rs1", "test-ns", "deploy1", "Deployment") ridx.FallbackNotFound() didx.MockDeployment("deploy1", "test-ns", "gateway1", "Gateway") - gwidx.MockGateway("gateway1", "test-ns", "", "") // Gateway has no owner pidx.MockPod("1.2.3.4", "", "", "pod1", "test-ns", "10.0.0.1", "rs1", "ReplicaSet") pidx.FallbackNotFound() @@ -159,11 +158,11 @@ func TestOwnershipTracking_OnlyDeployment(t *testing.T) { kubeData := Informers{ indexerHitMetric: metrics.CreateIndexerHitCounter(), config: Config{ - trackedKinds: []string{"ReplicaSet", "Deployment"}, // Gateway NOT tracked + trackedKinds: []string{"Deployment"}, // Gateway NOT tracked }, } - pidx, hidx, sidx, ridx, didx, _ := SetupIndexerMocksWithTrackedKinds(&kubeData, []string{"Deployment"}) + pidx, hidx, sidx, ridx, didx := SetupIndexerMocksWithTrackedKinds(&kubeData, []string{"Deployment"}) ridx.MockReplicaSet("rs1", "test-ns", "deploy1", "Deployment") ridx.FallbackNotFound() @@ -216,17 +215,16 @@ func TestOwnershipTracking_MaxDepth(t *testing.T) { kubeData := Informers{ indexerHitMetric: metrics.CreateIndexerHitCounter(), config: Config{ - trackedKinds: []string{"ReplicaSet", "Deployment", "Gateway"}, + trackedKinds: []string{"Deployment", "Gateway"}, }, } - pidx, hidx, sidx, ridx, didx, gwidx := SetupIndexerMocksWithTrackedKinds(&kubeData, []string{"Deployment", "Gateway"}) + pidx, hidx, sidx, ridx, didx := SetupIndexerMocksWithTrackedKinds(&kubeData, []string{"Deployment", "Gateway"}) ridx.MockReplicaSet("rs1", "test-ns", "deploy1", "Deployment") ridx.FallbackNotFound() + // Deployment owned by Gateway (which we can't traverse further without Gateway informer) didx.MockDeployment("deploy1", "test-ns", "gateway1", "Gateway") - // Gateway has another owner (hypothetical 4th level) - should be ignored - gwidx.MockGateway("gateway1", "test-ns", "someother", "SomeKind") pidx.MockPod("1.2.3.4", "", "", "pod1", "test-ns", "10.0.0.1", "rs1", "ReplicaSet") pidx.FallbackNotFound() From 86b2148ee0d97c476c3a73f524b706f71597f8f9 Mon Sep 17 00:00:00 2001 From: Leandro Beretta Date: Thu, 20 Nov 2025 13:27:42 -0300 Subject: [PATCH 8/9] address feedback --- pkg/pipeline/transform/kubernetes/informers/informers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/pipeline/transform/kubernetes/informers/informers.go b/pkg/pipeline/transform/kubernetes/informers/informers.go index 1c3a52b1d..a96ea4bff 100644 --- a/pkg/pipeline/transform/kubernetes/informers/informers.go +++ b/pkg/pipeline/transform/kubernetes/informers/informers.go @@ -562,7 +562,7 @@ func (k *Informers) initInformers(client kubernetes.Interface, metaClient metada // Initialize additional informers based on trackedKinds configuration for _, kind := range cfg.trackedKinds { switch kind { - case "Deployment", "Gateway": + case "Deployment": // Gateway requires Deployment informer to navigate ownership chain log.Debugf("initializing Deployment informer (trackedKinds)") if err := k.initDeploymentInformer(metadataInformerFactory); err != nil { From 34132839cf78a645b08b6d8445e5d85f6364f51d Mon Sep 17 00:00:00 2001 From: Leandro Beretta Date: Thu, 20 Nov 2025 14:11:02 -0300 Subject: [PATCH 9/9] fix lint --- pkg/pipeline/transform/kubernetes/informers/informers.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/pipeline/transform/kubernetes/informers/informers.go b/pkg/pipeline/transform/kubernetes/informers/informers.go index a96ea4bff..db5ea1b61 100644 --- a/pkg/pipeline/transform/kubernetes/informers/informers.go +++ b/pkg/pipeline/transform/kubernetes/informers/informers.go @@ -561,8 +561,7 @@ func (k *Informers) initInformers(client kubernetes.Interface, metaClient metada // Initialize additional informers based on trackedKinds configuration for _, kind := range cfg.trackedKinds { - switch kind { - case "Deployment": + if kind == "Deployment" { // Gateway requires Deployment informer to navigate ownership chain log.Debugf("initializing Deployment informer (trackedKinds)") if err := k.initDeploymentInformer(metadataInformerFactory); err != nil {