Skip to content

Commit ad09396

Browse files
improvements
1 parent 0af1882 commit ad09396

File tree

5 files changed

+472
-10
lines changed

5 files changed

+472
-10
lines changed

pkg/api/transform_network.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ type NetworkTransformKubeConfig struct {
5252
ConfigPath string `yaml:"configPath,omitempty" json:"configPath,omitempty" doc:"path to kubeconfig file (optional)"`
5353
SecondaryNetworks []SecondaryNetwork `yaml:"secondaryNetworks,omitempty" json:"secondaryNetworks,omitempty" doc:"configuration for secondary networks"`
5454
ManagedCNI []string `yaml:"managedCNI,omitempty" json:"managedCNI,omitempty" doc:"a list of CNI (network plugins) to manage, for detecting additional interfaces. Currently supported: ovn"`
55+
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."`
5556
}
5657

5758
type TransformNetworkOperationEnum string

pkg/pipeline/transform/kubernetes/informers/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ type Config struct {
1919
secondaryNetworks []api.SecondaryNetwork
2020
hasMultus bool
2121
hasUDN bool
22+
trackedKinds []string
2223
}
2324

2425
func NewConfig(cfg api.NetworkTransformKubeConfig) Config {
2526
c := Config{
2627
managedCNI: cfg.ManagedCNI,
2728
secondaryNetworks: cfg.SecondaryNetworks,
29+
trackedKinds: cfg.TrackedKinds,
2830
}
2931
if c.managedCNI == nil {
3032
c.managedCNI = []string{api.OVN}

pkg/pipeline/transform/kubernetes/informers/informers-mock.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,52 @@ func (m *IndexerMock) MockReplicaSet(name, namespace, ownerName, ownerKind strin
135135
}, true, nil)
136136
}
137137

138+
func (m *IndexerMock) MockDeployment(name, namespace, ownerName, ownerKind string) {
139+
m.On("GetByKey", namespace+"/"+name).Return(&metav1.ObjectMeta{
140+
Name: name,
141+
OwnerReferences: []metav1.OwnerReference{{
142+
Kind: ownerKind,
143+
Name: ownerName,
144+
}},
145+
}, true, nil)
146+
}
147+
148+
func (m *IndexerMock) MockGateway(name, namespace, ownerName, ownerKind string) {
149+
if ownerName == "" {
150+
// No owner
151+
m.On("GetByKey", namespace+"/"+name).Return(&metav1.ObjectMeta{
152+
Name: name,
153+
OwnerReferences: []metav1.OwnerReference{},
154+
}, true, nil)
155+
} else {
156+
m.On("GetByKey", namespace+"/"+name).Return(&metav1.ObjectMeta{
157+
Name: name,
158+
OwnerReferences: []metav1.OwnerReference{{
159+
Kind: ownerKind,
160+
Name: ownerName,
161+
}},
162+
}, true, nil)
163+
}
164+
}
165+
166+
func (m *IndexerMock) MockVirtualMachineInstance(name, namespace, ownerName, ownerKind string) {
167+
m.On("GetByKey", namespace+"/"+name).Return(&metav1.ObjectMeta{
168+
Name: name,
169+
OwnerReferences: []metav1.OwnerReference{{
170+
Kind: ownerKind,
171+
Name: ownerName,
172+
}},
173+
}, true, nil)
174+
}
175+
176+
func (m *IndexerMock) MockVirtualMachine(name, namespace string) {
177+
// VirtualMachine typically has no owner
178+
m.On("GetByKey", namespace+"/"+name).Return(&metav1.ObjectMeta{
179+
Name: name,
180+
OwnerReferences: []metav1.OwnerReference{},
181+
}, true, nil)
182+
}
183+
138184
func (m *IndexerMock) FallbackNotFound() {
139185
m.On("ByIndex", IndexIP, mock.Anything).Return([]interface{}{}, nil)
140186
}
@@ -163,6 +209,38 @@ func SetupIndexerMocks(kd *Informers) (pods, nodes, svc, rs *IndexerMock) {
163209
return
164210
}
165211

212+
func SetupIndexerMocksWithTrackedKinds(kd *Informers, trackedKinds []string) (pods, nodes, svc, rs, deploy, gw, vmi, vm *IndexerMock) {
213+
// Setup base informers
214+
pods, nodes, svc, rs = SetupIndexerMocks(kd)
215+
216+
// Setup additional informers based on trackedKinds
217+
for _, kind := range trackedKinds {
218+
switch kind {
219+
case "Deployment":
220+
deploy = &IndexerMock{}
221+
dim := InformerMock{}
222+
dim.On("GetIndexer").Return(deploy)
223+
kd.deployments = &dim
224+
case "Gateway":
225+
gw = &IndexerMock{}
226+
gim := InformerMock{}
227+
gim.On("GetIndexer").Return(gw)
228+
kd.gateways = &gim
229+
case "VirtualMachineInstance":
230+
vmi = &IndexerMock{}
231+
vim := InformerMock{}
232+
vim.On("GetIndexer").Return(vmi)
233+
kd.virtualMachineInstances = &vim
234+
case "VirtualMachine":
235+
vm = &IndexerMock{}
236+
vmim := InformerMock{}
237+
vmim.On("GetIndexer").Return(vm)
238+
kd.virtualMachines = &vmim
239+
}
240+
}
241+
return
242+
}
243+
166244
type FakeInformers struct {
167245
Interface
168246
ipInfo map[string]*model.ResourceMetaData

pkg/pipeline/transform/kubernetes/informers/informers.go

Lines changed: 243 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ type Informers struct {
6666
services cache.SharedIndexInformer
6767
// replicaSets caches the ReplicaSets as partially-filled *ObjectMeta pointers
6868
replicaSets cache.SharedIndexInformer
69+
// New informers for ownership tracking
70+
deployments cache.SharedIndexInformer
71+
virtualMachineInstances cache.SharedIndexInformer
72+
virtualMachines cache.SharedIndexInformer
73+
gateways cache.SharedIndexInformer
74+
// Config and channels
75+
config Config
6976
stopChan chan struct{}
7077
mdStopChan chan struct{}
7178
indexerHitMetric *prometheus.CounterVec
@@ -178,18 +185,125 @@ func (k *Informers) GetNodeByName(name string) (*model.ResourceMetaData, error)
178185
}
179186

180187
func (k *Informers) checkParent(info *model.ResourceMetaData) {
181-
if info.OwnerKind == "ReplicaSet" {
182-
item, ok, err := k.replicaSets.GetIndexer().GetByKey(info.Namespace + "/" + info.OwnerName)
183-
if err != nil {
184-
log.WithError(err).WithField("key", info.Namespace+"/"+info.OwnerName).
185-
Debug("can't get ReplicaSet info from informer. Ignoring")
186-
} else if ok {
187-
rsInfo := item.(*metav1.ObjectMeta)
188-
if len(rsInfo.OwnerReferences) > 0 {
189-
info.OwnerKind = rsInfo.OwnerReferences[0].Kind
190-
info.OwnerName = rsInfo.OwnerReferences[0].Name
188+
// Maximum 3 ownership hops: Pod → ReplicaSet → Deployment → Gateway
189+
// This allows tracking up to 3 levels beyond the initial resource
190+
const maxHops = 3
191+
192+
// If trackedKinds is empty, use legacy behavior (stop after ReplicaSet resolution)
193+
if len(k.config.trackedKinds) == 0 {
194+
// Legacy behavior: only resolve ReplicaSet
195+
if info.OwnerKind == "ReplicaSet" {
196+
item, ok, err := k.replicaSets.GetIndexer().GetByKey(info.Namespace + "/" + info.OwnerName)
197+
if err != nil {
198+
log.WithError(err).WithField("key", info.Namespace+"/"+info.OwnerName).
199+
Debug("can't get ReplicaSet info from informer. Ignoring")
200+
return
201+
}
202+
if ok {
203+
rsInfo := item.(*metav1.ObjectMeta)
204+
if len(rsInfo.OwnerReferences) > 0 {
205+
info.OwnerKind = rsInfo.OwnerReferences[0].Kind
206+
info.OwnerName = rsInfo.OwnerReferences[0].Name
207+
}
191208
}
192209
}
210+
return
211+
}
212+
213+
// New behavior with trackedKinds: traverse ownership chain until we find a tracked kind or hit max depth
214+
for i := 0; i < maxHops; i++ {
215+
// Check if current owner is in trackedKinds
216+
if k.isTracked(info.OwnerKind) {
217+
// This owner IS tracked. Try to get its parent to see if we can go higher.
218+
parent := k.getOwnerFromInformer(info.OwnerKind, info.Namespace, info.OwnerName)
219+
if parent == nil {
220+
// No parent exists → STOP at current tracked kind
221+
break
222+
}
223+
// Parent exists - check if parent is ALSO tracked
224+
if k.isTracked(parent.Kind) {
225+
// Parent is also tracked → update and continue (prefer higher level)
226+
info.OwnerKind = parent.Kind
227+
info.OwnerName = parent.Name
228+
continue
229+
}
230+
// Parent exists but is NOT tracked → STOP at current tracked kind
231+
break
232+
}
233+
234+
// Current owner is NOT tracked → try to find a tracked parent
235+
parent := k.getOwnerFromInformer(info.OwnerKind, info.Namespace, info.OwnerName)
236+
if parent == nil {
237+
// No parent found → STOP at current (untracked) owner
238+
break
239+
}
240+
241+
// Update to parent and continue
242+
info.OwnerKind = parent.Kind
243+
info.OwnerName = parent.Name
244+
}
245+
}
246+
247+
// isTracked returns true if the given kind is in the trackedKinds configuration
248+
func (k *Informers) isTracked(kind string) bool {
249+
for _, tracked := range k.config.trackedKinds {
250+
if tracked == kind {
251+
return true
252+
}
253+
}
254+
return false
255+
}
256+
257+
// OwnerInfo contains basic ownership information
258+
type OwnerInfo struct {
259+
Kind string
260+
Name string
261+
}
262+
263+
// getOwnerFromInformer retrieves the owner of a resource from the appropriate informer
264+
func (k *Informers) getOwnerFromInformer(kind, namespace, name string) *OwnerInfo {
265+
var informer cache.SharedIndexInformer
266+
267+
switch kind {
268+
case "ReplicaSet":
269+
informer = k.replicaSets
270+
case "Deployment":
271+
informer = k.deployments
272+
case "Gateway":
273+
informer = k.gateways
274+
case "VirtualMachineInstance":
275+
informer = k.virtualMachineInstances
276+
case "VirtualMachine":
277+
informer = k.virtualMachines
278+
default:
279+
return nil
280+
}
281+
282+
if informer == nil {
283+
log.WithField("kind", kind).Debug("informer not initialized for this kind")
284+
return nil
285+
}
286+
287+
item, ok, err := informer.GetIndexer().GetByKey(namespace + "/" + name)
288+
if err != nil {
289+
log.WithError(err).
290+
WithField("kind", kind).
291+
WithField("key", namespace+"/"+name).
292+
Debug("can't get resource info from informer")
293+
return nil
294+
}
295+
if !ok {
296+
return nil
297+
}
298+
299+
meta := item.(*metav1.ObjectMeta)
300+
if len(meta.OwnerReferences) == 0 {
301+
return nil
302+
}
303+
304+
return &OwnerInfo{
305+
Kind: meta.OwnerReferences[0].Kind,
306+
Name: meta.OwnerReferences[0].Name,
193307
}
194308
}
195309

@@ -376,8 +490,101 @@ func (k *Informers) initReplicaSetInformer(informerFactory metadatainformer.Shar
376490
return nil
377491
}
378492

493+
func (k *Informers) initDeploymentInformer(informerFactory metadatainformer.SharedInformerFactory) error {
494+
k.deployments = informerFactory.ForResource(
495+
schema.GroupVersionResource{
496+
Group: "apps",
497+
Version: "v1",
498+
Resource: "deployments",
499+
}).Informer()
500+
if err := k.deployments.SetTransform(func(i interface{}) (interface{}, error) {
501+
deploy, ok := i.(*metav1.PartialObjectMetadata)
502+
if !ok {
503+
return nil, fmt.Errorf("was expecting a Deployment. Got: %T", i)
504+
}
505+
return &metav1.ObjectMeta{
506+
Name: deploy.Name,
507+
Namespace: deploy.Namespace,
508+
OwnerReferences: deploy.OwnerReferences,
509+
}, nil
510+
}); err != nil {
511+
return fmt.Errorf("can't set Deployments transform: %w", err)
512+
}
513+
return nil
514+
}
515+
516+
func (k *Informers) initGatewayInformer(informerFactory metadatainformer.SharedInformerFactory) error {
517+
k.gateways = informerFactory.ForResource(
518+
schema.GroupVersionResource{
519+
Group: "gateway.networking.k8s.io",
520+
Version: "v1",
521+
Resource: "gateways",
522+
}).Informer()
523+
if err := k.gateways.SetTransform(func(i interface{}) (interface{}, error) {
524+
gw, ok := i.(*metav1.PartialObjectMetadata)
525+
if !ok {
526+
return nil, fmt.Errorf("was expecting a Gateway. Got: %T", i)
527+
}
528+
return &metav1.ObjectMeta{
529+
Name: gw.Name,
530+
Namespace: gw.Namespace,
531+
OwnerReferences: gw.OwnerReferences,
532+
}, nil
533+
}); err != nil {
534+
return fmt.Errorf("can't set Gateways transform: %w", err)
535+
}
536+
return nil
537+
}
538+
539+
func (k *Informers) initVirtualMachineInstanceInformer(informerFactory metadatainformer.SharedInformerFactory) error {
540+
k.virtualMachineInstances = informerFactory.ForResource(
541+
schema.GroupVersionResource{
542+
Group: "kubevirt.io",
543+
Version: "v1",
544+
Resource: "virtualmachineinstances",
545+
}).Informer()
546+
if err := k.virtualMachineInstances.SetTransform(func(i interface{}) (interface{}, error) {
547+
vmi, ok := i.(*metav1.PartialObjectMetadata)
548+
if !ok {
549+
return nil, fmt.Errorf("was expecting a VirtualMachineInstance. Got: %T", i)
550+
}
551+
return &metav1.ObjectMeta{
552+
Name: vmi.Name,
553+
Namespace: vmi.Namespace,
554+
OwnerReferences: vmi.OwnerReferences,
555+
}, nil
556+
}); err != nil {
557+
return fmt.Errorf("can't set VirtualMachineInstances transform: %w", err)
558+
}
559+
return nil
560+
}
561+
562+
func (k *Informers) initVirtualMachineInformer(informerFactory metadatainformer.SharedInformerFactory) error {
563+
k.virtualMachines = informerFactory.ForResource(
564+
schema.GroupVersionResource{
565+
Group: "kubevirt.io",
566+
Version: "v1",
567+
Resource: "virtualmachines",
568+
}).Informer()
569+
if err := k.virtualMachines.SetTransform(func(i interface{}) (interface{}, error) {
570+
vm, ok := i.(*metav1.PartialObjectMetadata)
571+
if !ok {
572+
return nil, fmt.Errorf("was expecting a VirtualMachine. Got: %T", i)
573+
}
574+
return &metav1.ObjectMeta{
575+
Name: vm.Name,
576+
Namespace: vm.Namespace,
577+
OwnerReferences: vm.OwnerReferences,
578+
}, nil
579+
}); err != nil {
580+
return fmt.Errorf("can't set VirtualMachines transform: %w", err)
581+
}
582+
return nil
583+
}
584+
379585
func (k *Informers) InitFromConfig(kubeconfig string, infConfig Config, opMetrics *operational.Metrics) error {
380586
// Initialization variables
587+
k.config = infConfig
381588
k.stopChan = make(chan struct{})
382589
k.mdStopChan = make(chan struct{})
383590

@@ -430,6 +637,32 @@ func (k *Informers) initInformers(client kubernetes.Interface, metaClient metada
430637
return err
431638
}
432639

640+
// Initialize additional informers based on trackedKinds configuration
641+
for _, kind := range cfg.trackedKinds {
642+
switch kind {
643+
case "Deployment":
644+
log.Debugf("initializing Deployment informer (trackedKinds)")
645+
if err := k.initDeploymentInformer(metadataInformerFactory); err != nil {
646+
return err
647+
}
648+
case "Gateway":
649+
log.Debugf("initializing Gateway informer (trackedKinds)")
650+
if err := k.initGatewayInformer(metadataInformerFactory); err != nil {
651+
return err
652+
}
653+
case "VirtualMachineInstance":
654+
log.Debugf("initializing VirtualMachineInstance informer (trackedKinds)")
655+
if err := k.initVirtualMachineInstanceInformer(metadataInformerFactory); err != nil {
656+
return err
657+
}
658+
case "VirtualMachine":
659+
log.Debugf("initializing VirtualMachine informer (trackedKinds)")
660+
if err := k.initVirtualMachineInformer(metadataInformerFactory); err != nil {
661+
return err
662+
}
663+
}
664+
}
665+
433666
// Informers expose an indexer
434667
log.Debugf("adding indexers")
435668
byIP := cache.Indexers{IndexIP: ipIndexer}

0 commit comments

Comments
 (0)