• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

noironetworks / aci-containers / 11373

17 Nov 2025 04:25AM UTC coverage: 63.619% (-0.2%) from 63.784%
11373

push

travis-pro

web-flow
Merge pull request #1636 from noironetworks/clean_sync_nads_and_apic

Fixed following things:

0 of 174 new or added lines in 2 files covered. (0.0%)

6 existing lines in 3 files now uncovered.

13369 of 21014 relevant lines covered (63.62%)

0.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/pkg/controller/aaepmonitor.go
1
// Copyright 2019 Cisco Systems, Inc.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRATIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
// Handlers for AaepMonitor CR updates.
16

17
package controller
18

19
import (
20
        "context"
21
        "crypto/sha256"
22
        "encoding/hex"
23
        "encoding/json"
24
        "fmt"
25
        "regexp"
26
        "strconv"
27
        "strings"
28

29
        amv1 "github.com/noironetworks/aci-containers/pkg/aaepmonitor/apis/aci.attachmentmonitor/v1"
30
        aaepmonitorclientset "github.com/noironetworks/aci-containers/pkg/aaepmonitor/clientset/versioned"
31
        "github.com/noironetworks/aci-containers/pkg/apicapi"
32

33
        nadapi "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
34
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
35
        "k8s.io/apimachinery/pkg/runtime"
36
        "k8s.io/apimachinery/pkg/watch"
37
        "k8s.io/client-go/tools/cache"
38
        "k8s.io/kubernetes/pkg/controller"
39
)
40

41
const (
42
        aaepMonitorCRDName = "aaepmonitors.aci.attachmentmonitor"
43
)
44

45
func (cont *AciController) queueAaepMonitorConfigByKey(key string) {
×
46
        cont.aaepMonitorConfigQueue.Add(key)
×
47
}
×
48

49
func aaepMonitorInit(cont *AciController, stopCh <-chan struct{}) {
×
50
        restconfig := cont.env.RESTConfig()
×
51
        aaepMonitorClient, err := aaepmonitorclientset.NewForConfig(restconfig)
×
52
        if err != nil {
×
53
                cont.log.Errorf("Failed to intialize aaepMonitorClient")
×
54
                return
×
55
        }
×
56

57
        cont.initAaepMonitorInformerFromClient(aaepMonitorClient)
×
58
        go cont.aaepMonitorInformer.Run(stopCh)
×
59
        go cont.processQueue(cont.aaepMonitorConfigQueue, cont.aaepMonitorInformer.GetIndexer(),
×
60
                func(obj interface{}) bool {
×
61
                        return cont.handleAaepMonitorConfigurationUpdate(obj)
×
62
                }, func(key string) bool {
×
63
                        return cont.handleAaepMonitorConfigurationDelete(key)
×
64
                }, nil, stopCh)
×
65
        cache.WaitForCacheSync(stopCh, cont.aaepMonitorInformer.HasSynced)
×
66
}
67

68
func (cont *AciController) initAaepMonitorInformerFromClient(
69
        aaepMonitorClient *aaepmonitorclientset.Clientset) {
×
70
        cont.initAaepMonitorInformerBase(
×
71
                &cache.ListWatch{
×
72
                        ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
×
73
                                return aaepMonitorClient.AciV1().AaepMonitors().List(context.TODO(), options)
×
74
                        },
×
75
                        WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
×
76
                                return aaepMonitorClient.AciV1().AaepMonitors().Watch(context.TODO(), options)
×
77
                        },
×
78
                })
79
}
80

81
func (cont *AciController) initAaepMonitorInformerBase(listWatch *cache.ListWatch) {
×
82
        cont.aaepMonitorInformer = cache.NewSharedIndexInformer(
×
83
                listWatch,
×
84
                &amv1.AaepMonitor{},
×
85
                controller.NoResyncPeriodFunc(),
×
86
                cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
×
87
        )
×
88
        cont.aaepMonitorInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
×
89
                AddFunc: func(obj interface{}) {
×
90
                        cont.aaepMonitorConfAdded(obj)
×
91
                },
×
92
                UpdateFunc: func(oldobj interface{}, newobj interface{}) {
×
93
                        cont.aaepMonitorConfUpdate(oldobj, newobj)
×
94
                },
×
95
                DeleteFunc: func(obj interface{}) {
×
96
                        cont.aaepMonitorConfDelete(obj)
×
97
                },
×
98
        })
99
}
100

101
func (cont *AciController) aaepMonitorConfAdded(obj interface{}) {
×
102
        aaepMonitorConfig, ok := obj.(*amv1.AaepMonitor)
×
103
        if !ok {
×
104
                cont.log.Error("aaepMonitorConfAdded: Bad object type")
×
105
                return
×
106
        }
×
107
        key, err := cache.MetaNamespaceKeyFunc(aaepMonitorConfig)
×
108
        if err != nil {
×
109
                return
×
110
        }
×
111
        cont.queueAaepMonitorConfigByKey(key)
×
112
}
113

114
func (cont *AciController) aaepMonitorConfUpdate(oldobj interface{}, newobj interface{}) {
×
115
        newAaepMonitorConfig := newobj.(*amv1.AaepMonitor)
×
116

×
117
        key, err := cache.MetaNamespaceKeyFunc(newAaepMonitorConfig)
×
118
        if err != nil {
×
119
                return
×
120
        }
×
121
        cont.queueAaepMonitorConfigByKey(key)
×
122
}
123

124
func (cont *AciController) aaepMonitorConfDelete(obj interface{}) {
×
125
        cont.indexMutex.Lock()
×
126
        defer cont.indexMutex.Unlock()
×
127
        aaepMonitorConfig, ok := obj.(*amv1.AaepMonitor)
×
128

×
129
        if !ok {
×
130
                deletedState, ok := obj.(cache.DeletedFinalStateUnknown)
×
131
                if !ok {
×
132
                        cont.log.Errorf("Received unexpected object: ")
×
133
                        return
×
134
                }
×
135
                aaepMonitorConfig, ok = deletedState.Obj.(*amv1.AaepMonitor)
×
136
                if !ok {
×
137
                        cont.log.Errorf("DeletedFinalStateUnknown contained non-aaepmonitorconfiguration object: %v", deletedState.Obj)
×
138
                        return
×
139
                }
×
140
        }
141

142
        key, err := cache.MetaNamespaceKeyFunc(aaepMonitorConfig)
×
143
        if err != nil {
×
144
                return
×
145
        }
×
146
        cont.queueAaepMonitorConfigByKey("DELETED_" + key)
×
147
}
148

149
func (cont *AciController) handleAaepMonitorConfigurationUpdate(obj interface{}) bool {
×
150
        aaepMonitorConfig, ok := obj.(*amv1.AaepMonitor)
×
151
        if !ok {
×
152
                cont.log.Error("handleAaepMonitorConfigurationUpdate: Bad object type")
×
153
                return false
×
154
        }
×
155

NEW
156
        cont.indexMutex.Lock()
×
NEW
157
        allEpgToSubscribe := make(map[string]bool)
×
158
        cont.log.Infof("Started processing of aaepmonitor CR creation/modification")
×
159
        addedAaeps, removedAaeps := cont.getAaepDiff(aaepMonitorConfig.Spec.Aaeps)
×
160
        for _, aaepName := range addedAaeps {
×
NEW
161
                epgsToSubscribe := cont.reconcileNadData(aaepName)
×
NEW
162
                for epgDn := range epgsToSubscribe {
×
NEW
163
                        allEpgToSubscribe[epgDn] = true
×
NEW
164
                }
×
165
        }
166

167
        for _, aaepName := range removedAaeps {
×
168
                cont.cleanAnnotationSubscriptions(aaepName)
×
169

×
170
                aaepEpgAttachDataMap := cont.sharedAaepMonitor[aaepName]
×
171
                delete(cont.sharedAaepMonitor, aaepName)
×
172

×
173
                for epgDn, aaepEpgAttachData := range aaepEpgAttachDataMap {
×
174
                        cont.deleteNetworkAttachmentDefinition(aaepName, epgDn, aaepEpgAttachData, "AaepRemovedFromCR")
×
175
                }
×
176
        }
NEW
177
        cont.indexMutex.Unlock()
×
NEW
178
        for epgDn := range allEpgToSubscribe {
×
NEW
179
                cont.subscribeSafely(epgDn)
×
180
        }
×
181
        cont.log.Infof("Completed processing of aaepmonitor CR creation/modification")
×
182
        return false
×
183
}
184

185
func (cont *AciController) handleAaepMonitorConfigurationDelete(key string) bool {
×
186
        cont.indexMutex.Lock()
×
187
        defer cont.indexMutex.Unlock()
×
188
        cont.log.Infof("Started processing of aaepmonitor CR deletion")
×
189
        for aaepName, aaepEpgAttachDataMap := range cont.sharedAaepMonitor {
×
190
                cont.cleanAnnotationSubscriptions(aaepName)
×
191

×
192
                for epgDn, aaepEpgAttachData := range aaepEpgAttachDataMap {
×
193
                        cont.deleteNetworkAttachmentDefinition(aaepName, epgDn, aaepEpgAttachData, "CRDeleted")
×
194
                }
×
195
        }
196

197
        cont.sharedAaepMonitor = make(map[string]map[string]*AaepEpgAttachData)
×
198
        cont.log.Infof("Completed processing of aaepmonitor CR deletion")
×
199
        return false
×
200
}
201

202
func (cont *AciController) handleAaepEpgAttach(infraRsObj apicapi.ApicObject) {
×
203
        infraRsObjDn := infraRsObj.GetDn()
×
204
        aaepName := cont.matchesAEPFilter(infraRsObjDn)
×
205
        cont.log.Infof("Started processing of EPG: %s attached with AAEP: %s", infraRsObj.GetAttrStr("tDn"), aaepName)
×
206
        if aaepName == "" {
×
207
                cont.log.Debugf("Unable to find AAEP from %s in monitoring list", infraRsObjDn)
×
208
                return
×
209
        }
×
210

211
        state := infraRsObj.GetAttrStr("state")
×
212
        if state != "formed" {
×
NEW
213
                cont.indexMutex.Lock()
×
214
                cont.log.Debugf("Skipping NAD creation: %s is with state: %s", infraRsObjDn, state)
×
NEW
215
                cont.cleanStaleNADLocked(aaepName, infraRsObj.GetAttrStr("tDn"))
×
NEW
216
                cont.indexMutex.Unlock()
×
217
                return
×
218
        }
×
219

220
        epgDn := infraRsObj.GetAttrStr("tDn")
×
221
        encap := infraRsObj.GetAttrStr("encap")
×
222
        vlanID := cont.getVlanId(encap)
×
223

×
224
        if cont.checkDuplicateAaepEpgAttachRequest(aaepName, epgDn, vlanID) {
×
225
                cont.log.Infof("AAEP %s EPG %s attachment data already exists", aaepName, epgDn)
×
226
                return
×
227
        }
×
228

229
        cont.indexMutex.Lock()
×
230
        oldAaepMonitorData := cont.getAaepEpgAttachDataLocked(aaepName, epgDn)
×
231
        cont.indexMutex.Unlock()
×
232

×
233
        if cont.checkVlanUsedInCluster(vlanID) {
×
234
                // This is needed when user updates vlan to the vlan already used in cluster
×
235
                cont.handleOldNadDeletion(aaepName, epgDn, oldAaepMonitorData, "AaepEpgAttachedWithVlanInUse")
×
236
                cont.log.Errorf("Skipping NAD creation: VLAN %d is already used in cluster", vlanID)
×
237
                return
×
238
        }
×
239

NEW
240
        defer cont.subscribeSafely(epgDn)
×
241
        epgVlanMap := &EpgVlanMap{
×
242
                epgDn:     epgDn,
×
243
                encapVlan: vlanID,
×
244
        }
×
245

×
246
        aaepEpgAttachData := cont.collectNadData(epgVlanMap)
×
247
        if aaepEpgAttachData == nil {
×
248
                return
×
249
        }
×
250

NEW
251
        if cont.checkIfEpgWithOverlappingVlan(aaepName, vlanID, epgDn, false) {
×
252
                // This is needed when user updates vlan from non-overlapping to overlapping
×
253
                cont.handleOldNadDeletion(aaepName, epgDn, oldAaepMonitorData, "AaepEpgAttachedWithOverlappingVlan")
×
254

×
255
                // Add new entry with nadCreated as false
×
256
                cont.indexMutex.Lock()
×
257
                if cont.sharedAaepMonitor[aaepName] == nil {
×
258
                        cont.sharedAaepMonitor[aaepName] = make(map[string]*AaepEpgAttachData)
×
259
                }
×
260
                aaepEpgAttachData.nadCreated = false
×
261
                cont.sharedAaepMonitor[aaepName][epgDn] = aaepEpgAttachData
×
262
                cont.indexMutex.Unlock()
×
263
                cont.log.Errorf("Skipping NAD creation: EPG %s with AAEP %s has overlapping VLAN %d", epgDn, aaepName, vlanID)
×
264
                return
×
265
        }
266
        cont.syncNADsWithAciState(aaepName, epgDn, oldAaepMonitorData, aaepEpgAttachData, "AaepEpgAttached")
×
267
        cont.log.Infof("Completed processing of EPG: %s attached with AAEP: %s", epgDn, aaepName)
×
268
}
269

270
func (cont *AciController) handleAaepEpgDetach(infraRsObjDn string) {
×
271
        aaepName := cont.matchesAEPFilter(infraRsObjDn)
×
272
        if aaepName == "" {
×
273
                cont.log.Debugf("Unable to find AAEP from %s in monitoring list", infraRsObjDn)
×
274
                return
×
275
        }
×
276

277
        epgDn := cont.getEpgDnFromInfraRsDn(infraRsObjDn)
×
278

×
279
        if epgDn == "" {
×
280
                cont.log.Errorf("Unable to find EPG from %s", infraRsObjDn)
×
281
                return
×
282
        }
×
283

284
        cont.log.Infof("Started processing of EPG: %s detached from AAEP: %s", infraRsObjDn, aaepName)
×
285
        // Need to check if EPG is not attached with any other AAEP
×
NEW
286
        if !cont.isEpgAttachedWithAaep("", epgDn, false) {
×
287
                cont.apicConn.UnsubscribeImmediateDnLocked(epgDn, []string{"tagAnnotation"})
×
288
        }
×
289

290
        cont.indexMutex.Lock()
×
291
        aaepEpgAttachData := cont.getAaepEpgAttachDataLocked(aaepName, epgDn)
×
292
        cont.indexMutex.Unlock()
×
293

×
294
        if aaepEpgAttachData == nil {
×
295
                cont.log.Debugf("Monitoring data not available for EPG %s with AAEP %s", epgDn, aaepName)
×
296
                return
×
297
        }
×
298
        if !cont.namespaceChecks(aaepEpgAttachData.namespaceName, epgDn) {
×
299
                cont.log.Debugf("Namespace %s not found", aaepEpgAttachData.namespaceName)
×
300
                return
×
301
        }
×
302

303
        cont.handleOldNadDeletion(aaepName, epgDn, aaepEpgAttachData, "AaepEpgDetached")
×
304
        cont.log.Infof("Completed processing of EPG: %s detached from AAEP: %s", epgDn, aaepName)
×
305
}
306

307
func (cont *AciController) handleAnnotationAdded(obj apicapi.ApicObject) bool {
×
308
        annotationDn := obj.GetDn()
×
309
        epgDn := annotationDn[:strings.Index(annotationDn, "/annotationKey-")]
×
310
        aaepMonitorDataMap := cont.getAaepMonitoringDataForEpg(epgDn)
×
311

×
312
        cont.log.Infof("Started processing of EPG: %s annotation %s addition/modification", epgDn, annotationDn)
×
313
        for aaepName, aaepMonitorData := range aaepMonitorDataMap {
×
314
                cont.indexMutex.Lock()
×
315
                oldAaepMonitorData := cont.getAaepEpgAttachDataLocked(aaepName, epgDn)
×
316
                cont.indexMutex.Unlock()
×
317

×
318
                if len(aaepMonitorData) == 0 {
×
319
                        // No new monitoring data available for this EPG with this AAEP but old data exists
×
320
                        // delete old NAD if exists and remove from monitoring map
×
321
                        // create NAD for next EPG with old VLAN
×
322
                        cont.handleOldNadDeletion(aaepName, epgDn, oldAaepMonitorData, "NamespaceAnnotationAdded")
×
323
                        cont.log.Debugf("Insufficient data for NAD creation: Monitoring data not available for EPG %s with AAEP %s", epgDn, aaepName)
×
324
                        continue
×
325
                }
326

327
                aaepEpgAttachData, exists := aaepMonitorData[epgDn]
×
328
                if !exists || aaepEpgAttachData == nil {
×
329
                        cont.handleOldNadDeletion(aaepName, epgDn, oldAaepMonitorData, "NamespaceAnnotationAdded")
×
330
                        cont.log.Debugf("Insufficient data for NAD creation: Monitoring data not available for EPG %s with AAEP %s", epgDn, aaepName)
×
331
                        continue
×
332
                }
333

334
                if !cont.checkMonitorDataModified(oldAaepMonitorData, aaepEpgAttachData) {
×
335
                        cont.log.Debugf("No changes in annotation for EPG %s with AAEP %s", epgDn, aaepName)
×
336
                        continue
×
337
                }
338

339
                if !cont.namespaceChecks(aaepEpgAttachData.namespaceName, epgDn) {
×
340
                        // Namespace not exist, need to delete old NAD if exists and remove from monitoring map
×
341
                        // create NAD for next EPG with old VLAN
×
342
                        cont.handleOldNadDeletion(aaepName, epgDn, oldAaepMonitorData, "NamespaceAnnotationAdded")
×
343
                        cont.log.Debugf("Insufficient data in annotation for NAD creation: Namespace not exist, in case of EPG %s with AAEP %s", epgDn, aaepName)
×
344
                        continue
×
345
                }
346

347
                // This is needed when user updates annotation and VLAN is already overlapping
NEW
348
                if cont.checkIfEpgWithOverlappingVlan(aaepName, aaepEpgAttachData.encapVlan, epgDn, false) {
×
349
                        // Add new entry with nadCreated as false
×
350
                        aaepEpgAttachData.nadCreated = false
×
351
                        cont.indexMutex.Lock()
×
352
                        if cont.sharedAaepMonitor[aaepName] == nil {
×
353
                                cont.sharedAaepMonitor[aaepName] = make(map[string]*AaepEpgAttachData)
×
354
                        }
×
355
                        cont.sharedAaepMonitor[aaepName][epgDn] = aaepEpgAttachData
×
356
                        cont.indexMutex.Unlock()
×
357
                        cont.log.Debugf("Skipping EPG annotation add handling: EPG %s with AAEP %s has overlapping VLAN %d", epgDn,
×
358
                                aaepName, aaepEpgAttachData.encapVlan)
×
359
                        continue
×
360
                }
361

362
                cont.syncNADsWithAciState(aaepName, epgDn, oldAaepMonitorData, aaepEpgAttachData, "NamespaceAnnotationAdded")
×
363
        }
364
        cont.log.Infof("Completed processing annotation addition/modification for EPG: %s, AnnotationDn: %s", epgDn, annotationDn)
×
365
        return true
×
366
}
367

368
func (cont *AciController) handleAnnotationDeleted(annotationDn string) {
×
369
        epgDn := annotationDn[:strings.Index(annotationDn, "/annotationKey-")]
×
370
        cont.log.Infof("Started processing of EPG: %s annotation %s deletion", epgDn, annotationDn)
×
371
        aaepMonitorDataMap := cont.getAaepMonitoringDataForEpg(epgDn)
×
372

×
373
        var reason string
×
374
        if !cont.checkIfEPGExists(epgDn) {
×
375
                reason = "EpgDeleted"
×
376
        } else {
×
377
                reason = "NamespaceAnnotationRemoved"
×
378
        }
×
379

380
        for aaepName, aaepMonitorData := range aaepMonitorDataMap {
×
381
                cont.indexMutex.Lock()
×
382
                oldAaepMonitorData := cont.getAaepEpgAttachDataLocked(aaepName, epgDn)
×
383
                cont.indexMutex.Unlock()
×
384

×
385
                if oldAaepMonitorData == nil {
×
386
                        cont.log.Debugf("Monitoring data not available for EPG %s with AAEP %s", epgDn, aaepName)
×
387
                        continue
×
388
                }
389

390
                if oldAaepMonitorData.nadCreated == false {
×
391
                        cont.indexMutex.Lock()
×
392
                        delete(cont.sharedAaepMonitor[aaepName], epgDn)
×
393
                        cont.indexMutex.Unlock()
×
394
                        continue
×
395
                }
396

397
                if aaepMonitorData == nil {
×
398
                        cont.indexMutex.Lock()
×
399
                        delete(cont.sharedAaepMonitor[aaepName], epgDn)
×
400
                        cont.deleteNetworkAttachmentDefinition(aaepName, epgDn, oldAaepMonitorData, reason)
×
401
                        cont.indexMutex.Unlock()
×
402
                        cont.createNadForNextEpg(aaepName, oldAaepMonitorData.encapVlan)
×
403
                        continue
×
404
                }
405

406
                aaepEpgAttachData, exists := aaepMonitorData[epgDn]
×
407
                if !exists || aaepEpgAttachData == nil {
×
408
                        cont.indexMutex.Lock()
×
409
                        delete(cont.sharedAaepMonitor[aaepName], epgDn)
×
410
                        cont.deleteNetworkAttachmentDefinition(aaepName, epgDn, oldAaepMonitorData, reason)
×
411
                        cont.indexMutex.Unlock()
×
412
                        cont.createNadForNextEpg(aaepName, oldAaepMonitorData.encapVlan)
×
413
                        continue
×
414
                }
415

416
                cont.syncNADsWithAciState(aaepName, epgDn, oldAaepMonitorData, aaepEpgAttachData, reason)
×
417
        }
418
        cont.log.Infof("Completed processing annotation deletion for EPG: %s, AnnotationDn: %s", epgDn, annotationDn)
×
419
}
420

421
func (cont *AciController) collectNadData(epgVlanMap *EpgVlanMap) *AaepEpgAttachData {
×
422
        epgDn := epgVlanMap.epgDn
×
423
        epgAnnotations := cont.getEpgAnnotations(epgDn)
×
424
        if epgAnnotations == nil {
×
425
                cont.log.Debugf("No annotations found for EPG %s", epgDn)
×
426
                return nil
×
427
        }
×
428
        namespaceName, nadName := cont.getSpecificEPGAnnotation(epgAnnotations)
×
429

×
430
        if !cont.namespaceChecks(namespaceName, epgDn) {
×
431
                cont.log.Debugf("Namespace not exist, in case of EPG %s", epgDn)
×
432
                return nil
×
433
        }
×
434

435
        aaepMonitoringData := &AaepEpgAttachData{
×
436
                encapVlan:     epgVlanMap.encapVlan,
×
437
                nadName:       nadName,
×
438
                namespaceName: namespaceName,
×
439
                nadCreated:    true,
×
440
        }
×
441

×
442
        return aaepMonitoringData
×
443
}
444

445
func (cont *AciController) getAaepEpgAttachDataLocked(aaepName, epgDn string) *AaepEpgAttachData {
×
446
        aaepEpgAttachDataMap, exists := cont.sharedAaepMonitor[aaepName]
×
447
        if !exists || aaepEpgAttachDataMap == nil {
×
448
                cont.log.Debugf("AAEP %s EPG %s attachment data not found", aaepName, epgDn)
×
449
                return nil
×
450
        }
×
451

452
        aaepEpgAttachData, exists := aaepEpgAttachDataMap[epgDn]
×
453
        if !exists || aaepEpgAttachData == nil {
×
454
                cont.log.Debugf("AAEP %s EPG %s attachment data not found", aaepName, epgDn)
×
455
                return nil
×
456
        }
×
457

458
        cont.log.Infof("Found attachment data: %v for EPG : %s AAEP: %s", *aaepEpgAttachData, epgDn, aaepName)
×
459
        return aaepEpgAttachData
×
460
}
461

462
func (cont *AciController) matchesAEPFilter(infraRsObjDn string) string {
×
463
        cont.indexMutex.Lock()
×
464
        defer cont.indexMutex.Unlock()
×
465
        var aaepName string
×
466
        for aaepName = range cont.sharedAaepMonitor {
×
467
                expectedPrefix := fmt.Sprintf("uni/infra/attentp-%s/", aaepName)
×
468
                if strings.HasPrefix(infraRsObjDn, expectedPrefix) {
×
469
                        return aaepName
×
470
                }
×
471
        }
472
        return ""
×
473
}
474

475
func (cont *AciController) getEpgDnFromInfraRsDn(infraRsObjDn string) string {
×
476
        re := regexp.MustCompile(`\[(.*?)\]`)
×
477
        match := re.FindStringSubmatch(infraRsObjDn)
×
478

×
479
        var epgDn string
×
480
        if len(match) > 1 {
×
481
                epgDn = match[1]
×
482
                return epgDn
×
483
        }
×
484

485
        return epgDn
×
486
}
487

488
func (cont *AciController) getAaepMonitoringDataForEpg(epgDn string) map[string]map[string]*AaepEpgAttachData {
×
489
        aaepEpgAttachData := make(map[string]map[string]*AaepEpgAttachData)
×
490

×
491
        cont.indexMutex.Lock()
×
492
        defer cont.indexMutex.Unlock()
×
493
        for aaepName := range cont.sharedAaepMonitor {
×
NEW
494
                encap := ""
×
NEW
495
                aaepEpgAttachObj := cont.getEncapFromAaepEpgAttachObj(aaepName, epgDn)
×
NEW
496
                if aaepEpgAttachObj == nil {
×
NEW
497
                        cont.log.Debugf("infraRsFuncToEpg not available for EPG %s attached with AAEP %s", epgDn, aaepName)
×
NEW
498
                        continue
×
499
                }
NEW
500
                if val, ok := aaepEpgAttachObj.Attributes["encap"]; ok {
×
NEW
501
                        encap = val.(string)
×
NEW
502
                } else {
×
NEW
503
                        cont.log.Debugf("Encap missing for infraRsFuncToEpg object of AAEP: %s and EPG: %s", aaepName, epgDn)
×
NEW
504
                }
×
505

506
                if encap != "" {
×
507
                        vlanID := cont.getVlanId(encap)
×
508
                        epgVlanMap := EpgVlanMap{
×
509
                                epgDn:     epgDn,
×
510
                                encapVlan: vlanID,
×
511
                        }
×
512
                        if aaepEpgAttachData[aaepName] == nil {
×
513
                                aaepEpgAttachData[aaepName] = make(map[string]*AaepEpgAttachData)
×
514
                        }
×
515
                        nadData := cont.collectNadData(&epgVlanMap)
×
516
                        if nadData != nil {
×
517
                                aaepEpgAttachData[aaepName][epgDn] = nadData
×
518
                        }
×
519
                }
520
        }
521

522
        return aaepEpgAttachData
×
523
}
524

525
func (cont *AciController) cleanAnnotationSubscriptions(aaepName string) {
×
526
        epgVlanMapList := cont.getAaepEpgAttObjDetails(aaepName)
×
527
        if epgVlanMapList == nil {
×
528
                return
×
529
        }
×
530

531
        for _, epgVlanMap := range epgVlanMapList {
×
532
                cont.apicConn.UnsubscribeImmediateDnLocked(epgVlanMap.epgDn, []string{"tagAnnotation"})
×
533
        }
×
534
}
535

536
func (cont *AciController) syncNADsWithAciState(aaepName string, epgDn string, oldAaepEpgAttachData,
537
        aaepEpgAttachData *AaepEpgAttachData, syncReason string) {
×
NEW
538
        if oldAaepEpgAttachData != nil {
×
539
                if cont.checkAnnotationModified(oldAaepEpgAttachData, aaepEpgAttachData) {
×
540
                        if oldAaepEpgAttachData.namespaceName != aaepEpgAttachData.namespaceName {
×
541
                                cont.indexMutex.Lock()
×
542
                                if oldAaepEpgAttachData.nadCreated == true {
×
543
                                        cont.deleteNetworkAttachmentDefinition(aaepName, epgDn, oldAaepEpgAttachData, syncReason)
×
544
                                }
×
545
                                delete(cont.sharedAaepMonitor[aaepName], epgDn)
×
546
                                cont.indexMutex.Unlock()
×
547
                        }
548
                } else if oldAaepEpgAttachData.encapVlan != aaepEpgAttachData.encapVlan {
×
549
                        cont.handleOldNadDeletion(aaepName, epgDn, oldAaepEpgAttachData, syncReason)
×
550
                } else {
×
551
                        NADAlreadyInCluster, _ := cont.isNADWithSameVlanAlreadyPresent(aaepName, epgDn, aaepEpgAttachData)
×
552
                        if NADAlreadyInCluster == nil {
×
NEW
553
                                cont.handleNewNadCreation(aaepName, epgDn, aaepEpgAttachData, syncReason, false)
×
554
                        }
×
555
                        return
×
556
                }
557
        }
NEW
558
        cont.handleNewNadCreation(aaepName, epgDn, aaepEpgAttachData, syncReason, false)
×
559
}
560

561
func (cont *AciController) addDeferredNADs(namespaceName string) {
×
562
        aaepEpgVlanMap := make(map[string][]EpgVlanMap)
×
563

×
564
        // Collect all AAEP EPG attachment details
×
565
        cont.indexMutex.Lock()
×
566
        for aaepName := range cont.sharedAaepMonitor {
×
567
                epgVlanMapList := cont.getAaepEpgAttObjDetails(aaepName)
×
568

×
569
                if epgVlanMapList == nil {
×
570
                        continue
×
571
                }
572

573
                aaepEpgVlanMap[aaepName] = epgVlanMapList
×
574
        }
575
        cont.indexMutex.Unlock()
×
576

×
577
        // Process each AAEP EPG attachment details
×
578
        for aaepName, epgVlanMapList := range aaepEpgVlanMap {
×
579
                for _, epgVlanMap := range epgVlanMapList {
×
580
                        aaepEpgAttachData := cont.collectNadData(&epgVlanMap)
×
581
                        if aaepEpgAttachData == nil || aaepEpgAttachData.namespaceName != namespaceName {
×
582
                                continue
×
583
                        }
584

NEW
585
                        if cont.checkIfEpgWithOverlappingVlan(aaepName, aaepEpgAttachData.encapVlan, epgVlanMap.epgDn, false) {
×
586
                                cont.indexMutex.Lock()
×
587
                                if cont.sharedAaepMonitor[aaepName] == nil {
×
588
                                        cont.sharedAaepMonitor[aaepName] = make(map[string]*AaepEpgAttachData)
×
589
                                }
×
590
                                aaepEpgAttachData.nadCreated = false
×
591
                                cont.sharedAaepMonitor[aaepName][epgVlanMap.epgDn] = aaepEpgAttachData
×
592
                                cont.indexMutex.Unlock()
×
593

×
594
                                cont.log.Errorf("Skipping NAD creation: EPG %s with AAEP %s has overlapping VLAN %d", epgVlanMap.epgDn,
×
595
                                        aaepName, aaepEpgAttachData.encapVlan)
×
596
                                continue
×
597
                        } else if cont.checkVlanUsedInCluster(aaepEpgAttachData.encapVlan) {
×
598
                                cont.log.Errorf("Skipping NAD creation: VLAN %d is already used in cluster", aaepEpgAttachData.encapVlan)
×
599
                                continue
×
600
                        } else {
×
601
                                epgDn := epgVlanMap.epgDn
×
NEW
602
                                cont.handleNewNadCreation(aaepName, epgDn, aaepEpgAttachData, "NamespaceCreated", false)
×
603
                        }
×
604
                }
605
        }
606
}
607

608
func (cont *AciController) getAaepEpgAttObjDetails(aaepName string) []EpgVlanMap {
×
609
        uri := fmt.Sprintf("/api/node/mo/uni/infra/attentp-%s.json?query-target=subtree&target-subtree-class=infraRsFuncToEpg", aaepName)
×
610

×
611
        resp, err := cont.apicConn.GetApicResponse(uri)
×
612
        if err != nil {
×
613
                cont.log.Errorf("Failed to get response from APIC: %v", err)
×
614
                return nil
×
615
        }
×
616

617
        if len(resp.Imdata) == 0 {
×
618
                cont.log.Debugf("Can't find EPGs attached with AAEP %s", aaepName)
×
619
                return nil
×
620
        }
×
621

622
        epgVlanMapList := make([]EpgVlanMap, 0)
×
623
        for _, respImdata := range resp.Imdata {
×
624
                aaepEpgAttachObj, ok := respImdata["infraRsFuncToEpg"]
×
625
                if !ok {
×
626
                        cont.log.Debugf("Empty AAEP EPG attachment object")
×
627
                        continue
×
628
                }
629

630
                if state, hasState := aaepEpgAttachObj.Attributes["state"].(string); hasState {
×
631
                        if state != "formed" {
×
632
                                aaepEpgAttchDn := aaepEpgAttachObj.Attributes["dn"].(string)
×
633
                                cont.log.Debugf("%s is with state: %s", aaepEpgAttchDn, state)
×
NEW
634
                                cont.cleanStaleNADLocked(aaepName, aaepEpgAttachObj.Attributes["tDn"].(string))
×
UNCOV
635
                                continue
×
636
                        }
637
                }
638
                vlanID := 0
×
639
                if encap, hasEncap := aaepEpgAttachObj.Attributes["encap"].(string); hasEncap {
×
640
                        vlanID = cont.getVlanId(encap)
×
641
                }
×
642

643
                epgVlanMap := EpgVlanMap{
×
644
                        epgDn:     aaepEpgAttachObj.Attributes["tDn"].(string),
×
645
                        encapVlan: vlanID,
×
646
                }
×
647
                epgVlanMapList = append(epgVlanMapList, epgVlanMap)
×
648
        }
649

650
        return epgVlanMapList
×
651
}
652

653
func (cont *AciController) getEpgAnnotations(epgDn string) map[string]string {
×
654
        uri := fmt.Sprintf("/api/node/mo/%s.json?query-target=subtree&target-subtree-class=tagAnnotation", epgDn)
×
655
        resp, err := cont.apicConn.GetApicResponse(uri)
×
656
        if err != nil {
×
657
                cont.log.Errorf("Failed to get response from APIC: %v", err)
×
658
                return nil
×
659
        }
×
660

661
        annotationsMap := make(map[string]string)
×
662
        for _, respImdata := range resp.Imdata {
×
663
                annotationObj, ok := respImdata["tagAnnotation"]
×
664
                if !ok {
×
665
                        cont.log.Debugf("Empty tag annotation of EPG %s", epgDn)
×
666
                        continue
×
667
                }
668

669
                key := annotationObj.Attributes["key"].(string)
×
670
                annotationsMap[key] = annotationObj.Attributes["value"].(string)
×
671
        }
672

673
        return annotationsMap
×
674
}
675

676
func (cont *AciController) checkIfEPGExists(epgDn string) bool {
×
677
        uri := fmt.Sprintf("/api/node/mo/%s.json", epgDn)
×
678
        resp, err := cont.apicConn.GetApicResponse(uri)
×
679
        if err != nil {
×
680
                cont.log.Errorf("Failed to get response from APIC: %v", err)
×
681
                return false
×
682
        }
×
683
        if len(resp.Imdata) == 0 {
×
684
                cont.log.Debugf("EPG %s does not exist", epgDn)
×
685
                return false
×
686
        }
×
687
        return true
×
688
}
689

690
func (cont *AciController) getSpecificEPGAnnotation(annotations map[string]string) (string, string) {
×
691
        namespaceNameAnnotationKey := cont.config.CnoIdentifier + "-namespace"
×
692
        namespaceName, exists := annotations[namespaceNameAnnotationKey]
×
693
        if !exists {
×
694
                cont.log.Debugf("Annotation with key '%s' not found", namespaceNameAnnotationKey)
×
695
        }
×
696

697
        nadNameAnnotationKey := cont.config.CnoIdentifier + "-nad"
×
698
        nadName, exists := annotations[nadNameAnnotationKey]
×
699
        if !exists {
×
700
                cont.log.Debugf("Annotation with key '%s' not found", nadNameAnnotationKey)
×
701
        }
×
702
        return namespaceName, nadName
×
703
}
704

705
func (cont *AciController) namespaceChecks(namespaceName string, epgDn string) bool {
×
706
        if namespaceName == "" {
×
707
                cont.log.Debugf("Defering NAD operation for EPG %s: Namespace name not provided in EPG annotation", epgDn)
×
708
                return false
×
709
        }
×
710

711
        kubeClient := cont.env.(*K8sEnvironment).kubeClient
×
712
        _, err := kubeClient.CoreV1().Namespaces().Get(context.TODO(), namespaceName, metav1.GetOptions{})
×
713
        namespaceExists := err == nil
×
714
        if !namespaceExists {
×
715
                cont.log.Debugf("Defering NAD operation for EPG %s: Namespace %s not exists", epgDn, namespaceName)
×
716
                return false
×
717
        }
×
718

719
        return true
×
720
}
721

NEW
722
func (cont *AciController) reconcileNadData(aaepName string) map[string]bool {
×
NEW
723
        epgsToSubscribe := make(map[string]bool)
×
724
        epgVlanMapList := cont.getAaepEpgAttObjDetails(aaepName)
×
725

×
726
        for _, epgVlanMap := range epgVlanMapList {
×
727
                if cont.checkVlanUsedInCluster(epgVlanMap.encapVlan) {
×
728
                        cont.log.Errorf("Skipping NAD creation: VLAN %d is already used in cluster", epgVlanMap.encapVlan)
×
729
                        continue
×
730
                }
731
                aaepEpgAttachData := cont.collectNadData(&epgVlanMap)
×
732
                if aaepEpgAttachData == nil {
×
NEW
733
                        epgsToSubscribe[epgVlanMap.epgDn] = true
×
734
                        continue
×
735
                }
736

737
                epgDn := epgVlanMap.epgDn
×
NEW
738
                if cont.checkIfEpgWithOverlappingVlan(aaepName, aaepEpgAttachData.encapVlan, epgDn, true) {
×
739
                        aaepEpgAttachData.nadCreated = false
×
740
                        if cont.sharedAaepMonitor[aaepName] == nil {
×
741
                                cont.sharedAaepMonitor[aaepName] = make(map[string]*AaepEpgAttachData)
×
742
                        }
×
743
                        cont.sharedAaepMonitor[aaepName][epgDn] = aaepEpgAttachData
×
NEW
744
                        epgsToSubscribe[epgDn] = true
×
745
                        cont.log.Errorf("Skipping NAD creation: EPG %s with AAEP %s has overlapping VLAN %d",
×
746
                                epgDn, aaepName, aaepEpgAttachData.encapVlan)
×
747
                        continue
×
748
                }
749

NEW
750
                OldepgDn := cont.handleNewNadCreation(aaepName, epgDn, aaepEpgAttachData, "AaepAddedInCR", true)
×
751
                if OldepgDn != "" {
×
NEW
752
                        epgsToSubscribe[OldepgDn] = true
×
753
                }
×
754

NEW
755
                epgsToSubscribe[epgDn] = true
×
756
        }
757

758
        if _, ok := cont.sharedAaepMonitor[aaepName]; !ok {
×
759
                cont.sharedAaepMonitor[aaepName] = make(map[string]*AaepEpgAttachData)
×
760
        }
×
761

NEW
762
        return epgsToSubscribe
×
763
}
764

765
// clean converts a string to lowercase, removes underscores and dots,
766
// and replaces any other invalid character with a hyphen.
767
func cleanApicResourceNames(apicResource string) string {
×
768
        apicResource = strings.ToLower(apicResource)
×
769
        var stringBuilder strings.Builder
×
770
        for _, character := range apicResource {
×
771
                switch {
×
772
                case character == '_' || character == '.':
×
773
                        continue
×
774
                case (character >= 'a' && character <= 'z') || (character >= '0' && character <= '9') || character == '-':
×
775
                        stringBuilder.WriteRune(character)
×
776
                default:
×
777
                        stringBuilder.WriteRune('-')
×
778
                }
779
        }
780
        return strings.Trim(stringBuilder.String(), "-")
×
781
}
782

783
func (cont *AciController) generateDefaultNadName(aaepName, epgDn string) string {
×
784
        parts := strings.Split(epgDn, "/")
×
785

×
786
        tenant := parts[1][3:]
×
787
        appProfile := parts[2][3:]
×
788
        epgName := parts[3][4:]
×
789

×
790
        apicResourceNames := tenant + appProfile + epgName + aaepName
×
791
        hashBytes := sha256.Sum256([]byte(apicResourceNames))
×
792
        hash := hex.EncodeToString(hashBytes[:])[:16]
×
793

×
794
        return fmt.Sprintf("%s-%s-%s-%s",
×
795
                cleanApicResourceNames(tenant), cleanApicResourceNames(appProfile), cleanApicResourceNames(epgName), hash)
×
796
}
×
797

798
func (cont *AciController) isNADUpdateRequired(aaepName string, epgDn string, nadData *AaepEpgAttachData,
799
        existingNAD *nadapi.NetworkAttachmentDefinition) bool {
×
800
        vlanID := nadData.encapVlan
×
801
        namespaceName := nadData.namespaceName
×
802
        customNadName := nadData.nadName
×
803
        defaultNadName := cont.generateDefaultNadName(aaepName, epgDn)
×
804
        existingAnnotaions := existingNAD.ObjectMeta.Annotations
×
805
        if existingAnnotaions != nil {
×
806
                if existingNAD.ObjectMeta.Annotations["aci-sync-status"] == "out-of-sync" || existingNAD.ObjectMeta.Annotations["cno-name"] != customNadName {
×
807
                        return true
×
808
                }
×
809
        } else {
×
810
                // NAD exists, check if VLAN needs to be updated
×
811
                existingConfig := existingNAD.Spec.Config
×
812
                if existingConfig != "" {
×
813
                        var existingCNVConfig map[string]interface{}
×
814
                        if json.Unmarshal([]byte(existingConfig), &existingCNVConfig) == nil {
×
815
                                if existingVLAN, ok := existingCNVConfig["vlan"].(float64); ok {
×
816
                                        if int(existingVLAN) == vlanID {
×
817
                                                // VLAN hasn't changed, no update needed
×
818
                                                cont.log.Infof("NetworkAttachmentDefinition %s already exists with correct VLAN %d in namespace %s",
×
819
                                                        defaultNadName, vlanID, namespaceName)
×
820
                                                return false
×
821
                                        }
×
822
                                } else if vlanID == 0 {
×
823
                                        // Both existing and new have no VLAN, no update needed
×
824
                                        cont.log.Infof("NetworkAttachmentDefinition %s already exists with no VLAN in namespace %s", defaultNadName, namespaceName)
×
825
                                        return false
×
826
                                }
×
827
                        }
828
                }
829
        }
830

831
        return true
×
832
}
833

834
func (cont *AciController) createNetworkAttachmentDefinition(aaepName string, epgDn string, nadData *AaepEpgAttachData, createReason string) bool {
×
835
        bridge := cont.config.BridgeName
×
836
        if bridge == "" {
×
837
                cont.log.Errorf("Linux bridge name must be specified when creating NetworkAttachmentDefinitions")
×
838
                return false
×
839
        }
×
840

841
        vlanID := nadData.encapVlan
×
842
        namespaceName := nadData.namespaceName
×
843
        customNadName := nadData.nadName
×
844
        defaultNadName := cont.generateDefaultNadName(aaepName, epgDn)
×
845
        nadClient := cont.env.(*K8sEnvironment).nadClient
×
846
        mtu := 1500
×
847

×
848
        // Check if NAD already exists
×
849
        existingNAD, err := nadClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespaceName).Get(context.TODO(), defaultNadName, metav1.GetOptions{})
×
850
        nadExists := err == nil
×
851

×
852
        if nadExists && !cont.isNADUpdateRequired(aaepName, epgDn, nadData, existingNAD) {
×
853
                return true
×
854
        }
×
855

856
        cnvBridgeConfig := map[string]any{
×
857
                "cniVersion": "0.3.1",
×
858
                "name":       defaultNadName,
×
859
                "type":       "bridge",
×
860
                "bridge":     bridge,
×
861
        }
×
862

×
863
        if !nadExists {
×
864
                cnvBridgeConfig["mtu"] = mtu
×
865
                cnvBridgeConfig["disableContainerInterface"] = true
×
866
                // Add optional parameters from controller config if they are set
×
867
                if cont.config.IsGateway != nil {
×
868
                        cnvBridgeConfig["isGateway"] = *cont.config.IsGateway
×
869
                }
×
870
                if cont.config.IsDefaultGateway != nil {
×
871
                        cnvBridgeConfig["isDefaultGateway"] = *cont.config.IsDefaultGateway
×
872
                }
×
873
                if cont.config.ForceAddress != nil {
×
874
                        cnvBridgeConfig["forceAddress"] = *cont.config.ForceAddress
×
875
                }
×
876
                if cont.config.IpMasq != nil {
×
877
                        cnvBridgeConfig["ipMasq"] = *cont.config.IpMasq
×
878
                }
×
879
                if cont.config.IpMasqBackend != "" {
×
880
                        cnvBridgeConfig["ipMasqBackend"] = cont.config.IpMasqBackend
×
881
                }
×
882
                if cont.config.Mtu != nil {
×
883
                        cnvBridgeConfig["mtu"] = *cont.config.Mtu
×
884
                }
×
885
                if cont.config.HairpinMode != nil {
×
886
                        cnvBridgeConfig["hairpinMode"] = *cont.config.HairpinMode
×
887
                }
×
888
                if cont.config.PromiscMode != nil {
×
889
                        cnvBridgeConfig["promiscMode"] = *cont.config.PromiscMode
×
890
                }
×
891
                if cont.config.Enabledad != nil {
×
892
                        cnvBridgeConfig["enabledad"] = *cont.config.Enabledad
×
893
                }
×
894
                if cont.config.Macspoofchk != nil {
×
895
                        cnvBridgeConfig["macspoofchk"] = *cont.config.Macspoofchk
×
896
                }
×
897
                if cont.config.DisableContainerInterface != nil {
×
898
                        cnvBridgeConfig["disableContainerInterface"] = *cont.config.DisableContainerInterface
×
899
                }
×
900
                if cont.config.PortIsolation != nil {
×
901
                        cnvBridgeConfig["portIsolation"] = *cont.config.PortIsolation
×
902
                }
×
903
                if len(cont.config.Ipam) > 0 {
×
904
                        cnvBridgeConfig["ipam"] = cont.config.Ipam
×
905
                }
×
906
        } else {
×
907
                // Preserve existing optional parameters from existing NAD
×
908
                existingConfig := existingNAD.Spec.Config
×
909
                if existingConfig != "" {
×
910
                        var existingCNVConfig map[string]interface{}
×
911
                        if json.Unmarshal([]byte(existingConfig), &existingCNVConfig) == nil {
×
912
                                optionalParams := []string{"isGateway", "isDefaultGateway", "forceAddress", "ipMasq",
×
913
                                        "ipMasqBackend", "mtu", "hairpinMode", "promiscMode", "enabledad", "macspoofchk",
×
914
                                        "disableContainerInterface", "portIsolation", "ipam"}
×
915
                                for _, param := range optionalParams {
×
916
                                        if value, ok := existingCNVConfig[param]; ok {
×
917
                                                cnvBridgeConfig[param] = value
×
918
                                        }
×
919
                                }
920
                        }
921
                }
922
        }
923
        if vlanID > 0 {
×
924
                cnvBridgeConfig["vlan"] = vlanID
×
925
        }
×
926

927
        configJSON, err := json.Marshal(cnvBridgeConfig)
×
928
        if err != nil {
×
929
                cont.log.Errorf("Failed to marshal CNV bridge config: %v", err)
×
930
                return false
×
931
        }
×
932

933
        nad := &nadapi.NetworkAttachmentDefinition{
×
934
                TypeMeta: metav1.TypeMeta{
×
935
                        APIVersion: "k8s.cni.cncf.io/v1",
×
936
                        Kind:       "NetworkAttachmentDefinition",
×
937
                },
×
938
                ObjectMeta: metav1.ObjectMeta{
×
939
                        Name:      defaultNadName,
×
940
                        Namespace: namespaceName,
×
941
                        Labels: map[string]string{
×
942
                                "managed-by": "cisco-network-operator",
×
943
                                "vlan":       strconv.Itoa(vlanID),
×
944
                        },
×
945
                        Annotations: map[string]string{
×
946
                                "managed-by":      "cisco-network-operator",
×
947
                                "vlan":            strconv.Itoa(vlanID),
×
948
                                "cno-name":        customNadName,
×
949
                                "aci-sync-status": "in-sync",
×
950
                                "aaep-name":       aaepName,
×
951
                                "epg-dn":          epgDn,
×
952
                        },
×
953
                },
×
954
                Spec: nadapi.NetworkAttachmentDefinitionSpec{
×
955
                        Config: string(configJSON),
×
956
                },
×
957
        }
×
958

×
959
        if nadExists {
×
960
                nad.ObjectMeta.ResourceVersion = existingNAD.ObjectMeta.ResourceVersion
×
961

×
962
                updatedNad, err := nadClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespaceName).Update(context.TODO(), nad, metav1.UpdateOptions{})
×
963

×
964
                if err != nil {
×
965
                        cont.log.Errorf("Failed to update NetworkAttachmentDefinition %s from namespace %s : %v", customNadName, namespaceName, err)
×
966
                        return false
×
967
                }
×
968

969
                cont.log.Debugf("Existing NAD Annotations: %v, %s", existingNAD.ObjectMeta.Annotations, createReason)
×
970
                if existingNAD.ObjectMeta.Annotations["aci-sync-status"] == "out-of-sync" {
×
971
                        cont.submitEvent(updatedNad, createReason, cont.getNADRevampMessage(createReason))
×
972
                }
×
973
                cont.log.Infof("Updated NetworkAttachmentDefinition %s from namespace %s", defaultNadName, namespaceName)
×
974
        } else {
×
975
                _, err = nadClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespaceName).Create(context.TODO(), nad, metav1.CreateOptions{})
×
976
                if err != nil {
×
977
                        cont.log.Errorf("Failed to create NetworkAttachmentDefinition %s in namespace %s : %v", customNadName, namespaceName, err)
×
978
                        return false
×
979
                }
×
980
                cont.log.Infof("Created NetworkAttachmentDefinition %s in namespace %s", defaultNadName, namespaceName)
×
981
        }
982

983
        return true
×
984
}
985

986
func (cont *AciController) deleteNetworkAttachmentDefinition(aaepName string, epgDn string, nadData *AaepEpgAttachData, deleteReason string) {
×
987
        namespaceName := nadData.namespaceName
×
988

×
989
        kubeClient := cont.env.(*K8sEnvironment).kubeClient
×
990
        _, err := kubeClient.CoreV1().Namespaces().Get(context.TODO(), namespaceName, metav1.GetOptions{})
×
991
        namespaceExists := err == nil
×
992
        if !namespaceExists {
×
993
                cont.log.Debugf("Defering NAD deletion for EPG %s: Namespace %s not exists", epgDn, namespaceName)
×
994
                return
×
995
        }
×
996

997
        nadName := cont.generateDefaultNadName(aaepName, epgDn)
×
998
        nadClient := cont.env.(*K8sEnvironment).nadClient
×
999

×
1000
        nadDetails, err := nadClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespaceName).Get(context.TODO(), nadName, metav1.GetOptions{})
×
1001
        nadExists := err == nil
×
1002

×
1003
        if nadExists {
×
1004
                if !cont.isVmmLiteNAD(nadDetails) {
×
1005
                        return
×
1006
                }
×
1007

1008
                if cont.isNADinUse(namespaceName, nadName) {
×
1009
                        nadDetails.ObjectMeta.Annotations["aci-sync-status"] = "out-of-sync"
×
1010
                        _, err = nadClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespaceName).Update(context.TODO(), nadDetails, metav1.UpdateOptions{})
×
1011
                        if err != nil {
×
1012
                                cont.log.Errorf("Failed to add out-of-sync annotation to the NAD %s from namespace %s : %v", nadName, namespaceName, err)
×
1013
                                return
×
1014
                        }
×
1015
                        cont.submitEvent(nadDetails, deleteReason, cont.getNADDeleteMessage(deleteReason))
×
1016
                        cont.log.Infof("Added annotation out-of-sync for NAD %s from namespace %s", nadName, namespaceName)
×
1017
                        return
×
1018
                }
1019

1020
                delete(nadDetails.ObjectMeta.Annotations, "managed-by")
×
1021
                _, err = nadClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespaceName).Update(context.TODO(), nadDetails, metav1.UpdateOptions{})
×
1022
                if err != nil {
×
1023
                        cont.log.Errorf("Failed to remove VMM lite annotation from NetworkAttachmentDefinition %s from namespace %s: %v", nadName, namespaceName, err)
×
1024
                        return
×
1025
                }
×
NEW
1026
                err := nadClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespaceName).Delete(context.TODO(), nadName, metav1.DeleteOptions{})
×
1027
                if err != nil {
×
NEW
1028
                        cont.log.Errorf("Failed to delete NetworkAttachmentDefinition %s from namespace %s : %v", nadName, namespaceName, err)
×
NEW
1029
                        return
×
UNCOV
1030
                }
×
NEW
1031
                cont.log.Infof("Deleted NAD %s from %s namespace", nadName, namespaceName)
×
1032
        } else {
×
NEW
1033
                cont.log.Debugf("NAD %s not there to delete in namespace %s", nadName, namespaceName)
×
1034
        }
×
1035
}
1036

1037
func (cont *AciController) getVlanId(encap string) int {
×
1038
        if after, ok := strings.CutPrefix(encap, "vlan-"); ok {
×
1039
                vlanStr := after
×
1040
                if vlanID, err := strconv.Atoi(vlanStr); err == nil && vlanID > 0 {
×
1041
                        return vlanID
×
1042
                }
×
1043
        } else if after, ok := strings.CutPrefix(encap, "vlan"); ok {
×
1044
                vlanStr := after
×
1045
                if vlanID, err := strconv.Atoi(vlanStr); err == nil && vlanID > 0 {
×
1046
                        return vlanID
×
1047
                }
×
1048
        }
1049

1050
        return 0
×
1051
}
1052

1053
func (cont *AciController) getAaepDiff(crAaeps []string) (addedAaeps, removedAaeps []string) {
×
1054
        crAaepMap := make(map[string]bool)
×
1055
        for _, crAaep := range crAaeps {
×
1056
                crAaepMap[crAaep] = true
×
1057
        }
×
1058

1059
        for _, crAaep := range crAaeps {
×
1060
                if _, ok := cont.sharedAaepMonitor[crAaep]; !ok {
×
1061
                        addedAaeps = append(addedAaeps, crAaep)
×
1062
                }
×
1063
        }
1064

1065
        for cachedAaep := range cont.sharedAaepMonitor {
×
1066
                if !crAaepMap[cachedAaep] {
×
1067
                        removedAaeps = append(removedAaeps, cachedAaep)
×
1068
                }
×
1069
        }
1070

1071
        return
×
1072
}
1073

NEW
1074
func (cont *AciController) getEncapFromAaepEpgAttachObj(aaepName, epgDn string) *apicapi.ApicObjectBody {
×
1075
        uri := fmt.Sprintf("/api/node/mo/uni/infra/attentp-%s/gen-default/rsfuncToEpg-[%s].json?query-target=self", aaepName, epgDn)
×
1076
        resp, err := cont.apicConn.GetApicResponse(uri)
×
1077
        if err != nil {
×
1078
                cont.log.Errorf("Failed to get response from APIC: AAEP %s and EPG %s ERROR: %v", aaepName, epgDn, err)
×
NEW
1079
                return nil
×
1080
        }
×
1081

NEW
1082
        if len(resp.Imdata) == 0 {
×
NEW
1083
                cont.log.Debugf("Can't find infraRsFuncToEpg object for AAEP %s and EPG %s", aaepName, epgDn)
×
NEW
1084
                return nil
×
NEW
1085
        }
×
NEW
1086
        lresp, ok := resp.Imdata[0]["infraRsFuncToEpg"]
×
NEW
1087
        if !ok {
×
NEW
1088
                cont.log.Errorf("InfraRsFuncToEpg object not found in response for AAEP %s and EPG %s", aaepName, epgDn)
×
NEW
1089
                return nil
×
UNCOV
1090
        }
×
1091

NEW
1092
        return lresp
×
1093
}
1094

1095
func (cont *AciController) isVmmLiteNAD(nadDetails *nadapi.NetworkAttachmentDefinition) bool {
×
1096
        return nadDetails.ObjectMeta.Annotations["managed-by"] == "cisco-network-operator"
×
1097
}
×
1098

1099
func (cont *AciController) isNADinUse(namespaceName string, nadName string) bool {
×
1100
        kubeClient := cont.env.(*K8sEnvironment).kubeClient
×
1101
        pods, err := kubeClient.CoreV1().Pods(namespaceName).List(context.TODO(), metav1.ListOptions{})
×
1102
        if err == nil {
×
1103
                var networks []map[string]string
×
1104
                for _, pod := range pods.Items {
×
1105
                        networksAnn, ok := pod.Annotations["k8s.v1.cni.cncf.io/networks"]
×
1106
                        if ok && (networksAnn == nadName) {
×
1107
                                cont.log.Infof("NAD %s is still used by Pod %s/%s", nadName, namespaceName, pod.Name)
×
1108
                                return true
×
1109
                        }
×
1110
                        if err := json.Unmarshal([]byte(networksAnn), &networks); err != nil {
×
1111
                                cont.log.Errorf("Error while getting pod annotations: %v", err)
×
1112
                                return false
×
1113
                        }
×
1114
                        for _, network := range networks {
×
1115
                                if ok && (network["name"] == nadName) {
×
1116
                                        cont.log.Infof("NAD %s is still used by VM %s/%s", nadName, namespaceName, pod.Name)
×
1117
                                        return true
×
1118
                                }
×
1119
                        }
1120
                }
1121
        }
1122
        return false
×
1123
}
1124

1125
func (cont *AciController) getNADDeleteMessage(deleteReason string) string {
×
1126
        messagePrefix := "NAD is in use by pods: "
×
1127
        switch {
×
1128
        case deleteReason == "EpgDeleted":
×
1129
                return messagePrefix + "EPG deleted"
×
1130
        case deleteReason == "NamespaceAnnotationRemoved":
×
1131
                return messagePrefix + "Namespace name EPG annotaion removed"
×
1132
        case deleteReason == "NamespaceAnnotationModified":
×
1133
                return messagePrefix + "Namespace name EPG annotation modified"
×
1134
        case deleteReason == "AaepEpgDetached":
×
1135
                return messagePrefix + "EPG detached from AAEP"
×
1136
        case deleteReason == "CRDeleted":
×
1137
                return messagePrefix + "aaepmonitor CR deleted"
×
1138
        case deleteReason == "AaepRemovedFromCR":
×
1139
                return messagePrefix + "AAEP removed from aaepmonitor CR"
×
1140
        case deleteReason == "AaepEpgAttachedWithVlanUsedInCluster":
×
1141
                return messagePrefix + "EPG with AAEP has overlapping VLAN with existing cluster VLAN"
×
1142
        case deleteReason == "AaepEpgAttachedWithOverlappingVlan":
×
1143
                return messagePrefix + "EPG with AAEP has overlapping VLAN with another EPG"
×
1144
        case deleteReason == "NamespaceDeleted":
×
1145
                return messagePrefix + "Namespace deleted"
×
1146
        case deleteReason == "AaepEpgAttachedWithVlanInUse":
×
1147
                return messagePrefix + "EPG with AAEP has VLAN already used in cluster"
×
1148
        case deleteReason == "VlanUpdatedInAaepEpgAttach":
×
1149
                return messagePrefix + "VLAN updated in AAEP EPG attachment"
×
NEW
1150
        case deleteReason == "StaleNadCleanup":
×
NEW
1151
                return messagePrefix + "Stale NAD detected"
×
1152
        }
1153
        return messagePrefix + "One or many pods are using NAD"
×
1154
}
1155

1156
func (cont *AciController) getNADRevampMessage(createReason string) string {
×
1157
        messagePrefix := "NAD is in sync: "
×
1158
        switch {
×
1159
        case createReason == "NamespaceAnnotationAdded":
×
1160
                return messagePrefix + "Namespace name EPG annotaion added"
×
1161
        case createReason == "AaepEpgAttached":
×
1162
                return messagePrefix + "EPG attached with AAEP"
×
1163
        case createReason == "AaepAddedInCR":
×
1164
                return messagePrefix + "AAEP added back in aaepmonitor CR"
×
1165
        case createReason == "NamespaceCreated":
×
1166
                return messagePrefix + "Namespace created back"
×
1167
        case createReason == "NextEpgNadCreation":
×
1168
                return messagePrefix + "NAD creation for next EPG with same VLAN"
×
1169
        }
1170
        return messagePrefix + "NAD synced with ACI"
×
1171
}
1172

NEW
1173
func (cont *AciController) isEpgAttachedWithAaep(aaepName, epgDn string, locked bool) bool {
×
NEW
1174
        if !locked {
×
NEW
1175
                cont.indexMutex.Lock()
×
NEW
1176
                defer cont.indexMutex.Unlock()
×
NEW
1177
        }
×
NEW
1178
        if aaepName != "" {
×
NEW
1179
                // Used in sync process to check if EPG is still attached with AAEP
×
NEW
1180
                // if EPG not attached, then need to delete NAD
×
NEW
1181
                aaepEpgAttachObj := cont.getEncapFromAaepEpgAttachObj(aaepName, epgDn)
×
NEW
1182
                if aaepEpgAttachObj == nil {
×
NEW
1183
                        return false
×
NEW
1184
                }
×
NEW
1185
                if state, hasState := aaepEpgAttachObj.Attributes["state"].(string); hasState {
×
NEW
1186
                        if state == "formed" {
×
NEW
1187
                                return true
×
NEW
1188
                        }
×
1189
                }
NEW
1190
        } else {
×
NEW
1191
                for aaepName := range cont.sharedAaepMonitor {
×
NEW
1192
                        aaepEpgAttachObj := cont.getEncapFromAaepEpgAttachObj(aaepName, epgDn)
×
NEW
1193
                        if aaepEpgAttachObj == nil {
×
NEW
1194
                                continue
×
1195
                        }
NEW
1196
                        if _, ok := aaepEpgAttachObj.Attributes["encap"]; ok {
×
NEW
1197
                                return true
×
NEW
1198
                        }
×
1199
                }
1200
        }
1201
        return false
×
1202
}
1203

1204
func (cont *AciController) checkDuplicateAaepEpgAttachRequest(aaepName, epgDn string, vlanID int) bool {
×
1205
        cont.indexMutex.Lock()
×
1206
        defer cont.indexMutex.Unlock()
×
1207
        aaepEpgAttachDataMap, exists := cont.sharedAaepMonitor[aaepName]
×
1208
        if !exists || aaepEpgAttachDataMap == nil {
×
1209
                return false
×
1210
        }
×
1211

1212
        aaepEpgAttachData, exists := aaepEpgAttachDataMap[epgDn]
×
1213
        if !exists || aaepEpgAttachData == nil {
×
1214
                return false
×
1215
        }
×
1216

1217
        if vlanID == aaepEpgAttachData.encapVlan && aaepEpgAttachData.nadCreated {
×
1218
                return true
×
1219
        }
×
1220
        return false
×
1221
}
1222

NEW
1223
func (cont *AciController) checkIfEpgWithOverlappingVlan(aaepName string, vlanId int, epgDn string, locked bool) bool {
×
NEW
1224
        if !locked {
×
NEW
1225
                cont.indexMutex.Lock()
×
NEW
1226
                defer cont.indexMutex.Unlock()
×
NEW
1227
        }
×
1228
        oldAaepEpgAttachData := cont.getAaepEpgAttachDataLocked(aaepName, epgDn)
×
1229

×
1230
        if oldAaepEpgAttachData != nil {
×
1231
                // If old data exists with NAD created and VLAN is same in old and new data, skip checking for overlapping VLAN for the same EPG
×
1232
                if oldAaepEpgAttachData.encapVlan == vlanId && oldAaepEpgAttachData.nadCreated {
×
1233
                        return false
×
1234
                }
×
1235
        }
1236
        aaepEpgAttachDataMap, exists := cont.sharedAaepMonitor[aaepName]
×
1237
        if !exists || aaepEpgAttachDataMap == nil {
×
1238
                return false
×
1239
        }
×
1240

1241
        for _, aaepEpgData := range aaepEpgAttachDataMap {
×
1242
                if vlanId == aaepEpgData.encapVlan && aaepEpgData.nadCreated {
×
1243
                        return true
×
1244
                }
×
1245
        }
1246
        return false
×
1247
}
1248

1249
func (cont *AciController) createNadForNextEpg(aaepName string, vlanId int) {
×
1250
        cont.indexMutex.Lock()
×
1251
        defer cont.indexMutex.Unlock()
×
1252

×
1253
        aaepEpgAttachDataMap := cont.sharedAaepMonitor[aaepName]
×
1254
        for epgDn, aaepEpgAttachData := range aaepEpgAttachDataMap {
×
1255
                if aaepEpgAttachData.encapVlan != vlanId || aaepEpgAttachData.nadCreated {
×
1256
                        continue
×
1257
                } else if !cont.namespaceChecks(aaepEpgAttachData.namespaceName, epgDn) {
×
1258
                        cont.log.Debugf("Skipping NAD creation for next EPG %s with AAEP %s and VLAN %d: Namespace checks failed", epgDn, aaepName, vlanId)
×
1259
                        continue
×
1260
                } else {
×
1261
                        cont.log.Infof("Creating NAD for next EPG %s with AAEP %s and VLAN %d, for which previously NAD not created due to overlapping Vlan",
×
1262
                                epgDn, aaepName, vlanId)
×
1263
                        needCacheChange := cont.createNetworkAttachmentDefinition(aaepName, epgDn, aaepEpgAttachData, "NextEpgNadCreation")
×
1264
                        if needCacheChange {
×
1265
                                aaepEpgAttachData.nadCreated = true
×
1266
                                if cont.sharedAaepMonitor[aaepName] == nil {
×
1267
                                        cont.sharedAaepMonitor[aaepName] = make(map[string]*AaepEpgAttachData)
×
1268
                                }
×
1269
                                cont.sharedAaepMonitor[aaepName][epgDn] = aaepEpgAttachData
×
1270
                        }
1271
                        cont.log.Infof("Created NAD for next EPG %s with AAEP %s and VLAN %d", epgDn, aaepName, vlanId)
×
1272
                        break
×
1273
                }
1274
        }
1275
}
1276

1277
func (cont *AciController) checkVlanUsedInCluster(vlanId int) bool {
×
1278
        if vlanId == cont.config.KubeapiVlan {
×
1279
                return true
×
1280
        }
×
1281
        return false
×
1282
}
1283

1284
func (cont *AciController) checkMonitorDataModified(oldData, newData *AaepEpgAttachData) bool {
×
1285
        if oldData == nil {
×
1286
                return true
×
1287
        }
×
1288
        if oldData.namespaceName != newData.namespaceName ||
×
1289
                oldData.nadName != newData.nadName ||
×
1290
                oldData.encapVlan != newData.encapVlan {
×
1291
                return true
×
1292
        }
×
1293
        return false
×
1294
}
1295

1296
func (cont *AciController) handleOldNadDeletion(aaepName, epgDn string,
1297
        oldAaepMonitorData *AaepEpgAttachData, deleteReason string) {
×
1298
        if oldAaepMonitorData == nil {
×
1299
                return
×
1300
        }
×
1301
        cont.indexMutex.Lock()
×
1302
        delete(cont.sharedAaepMonitor[aaepName], epgDn)
×
1303
        if oldAaepMonitorData.nadCreated {
×
1304
                cont.log.Debugf("Deleting NAD for EPG %s with AAEP %s due to %s", epgDn, aaepName, deleteReason)
×
1305
                cont.deleteNetworkAttachmentDefinition(aaepName, epgDn, oldAaepMonitorData, deleteReason)
×
1306
        }
×
1307
        cont.indexMutex.Unlock()
×
1308
        if oldAaepMonitorData.nadCreated {
×
1309
                cont.createNadForNextEpg(aaepName, oldAaepMonitorData.encapVlan)
×
1310
        }
×
1311
}
1312

1313
func (cont *AciController) handleNewNadCreation(aaepName, epgDn string,
NEW
1314
        newAaepMonitorData *AaepEpgAttachData, createReason string, locked bool) string {
×
NEW
1315
        if !locked {
×
NEW
1316
                cont.indexMutex.Lock()
×
NEW
1317
                defer cont.indexMutex.Unlock()
×
NEW
1318
        }
×
1319
        // If there are multiple EPGs associated with same vlan for an aaep,
1320
        // then only one NAD will be there.
1321
        // After controller restart, if any other EPG notification comes before the already present NAD
1322
        // it will result in 2 NADs.
1323
        // Below check is to avoid the condition
1324
        NADAlreadyInCluster, oldEpgDn := cont.isNADWithSameVlanAlreadyPresent(aaepName, epgDn, newAaepMonitorData)
×
1325
        if NADAlreadyInCluster != nil {
×
1326
                cont.log.Errorf("Cannot create NAD for EPG %s as there is a NAD already present %v with EPG %s attached to AAEP %s", epgDn, NADAlreadyInCluster, oldEpgDn, aaepName)
×
1327
                if cont.sharedAaepMonitor[aaepName] == nil {
×
1328
                        cont.sharedAaepMonitor[aaepName] = make(map[string]*AaepEpgAttachData)
×
1329
                }
×
1330
                cont.sharedAaepMonitor[aaepName][oldEpgDn] = NADAlreadyInCluster
×
1331
                newAaepMonitorData.nadCreated = false
×
1332
                cont.sharedAaepMonitor[aaepName][epgDn] = newAaepMonitorData
×
1333
                return oldEpgDn
×
1334
        }
1335
        needCacheChange := cont.createNetworkAttachmentDefinition(aaepName, epgDn, newAaepMonitorData, createReason)
×
1336
        if needCacheChange {
×
1337
                if cont.sharedAaepMonitor[aaepName] == nil {
×
1338
                        cont.sharedAaepMonitor[aaepName] = make(map[string]*AaepEpgAttachData)
×
1339
                }
×
1340
                cont.sharedAaepMonitor[aaepName][epgDn] = newAaepMonitorData
×
1341
        }
1342
        return ""
×
1343
}
1344

1345
func (cont *AciController) checkAnnotationModified(oldAaepEpgAttachData, newAaepEpgAttachData *AaepEpgAttachData) bool {
×
1346
        if oldAaepEpgAttachData.nadName != newAaepEpgAttachData.nadName || oldAaepEpgAttachData.namespaceName != newAaepEpgAttachData.namespaceName {
×
1347
                return true
×
1348
        }
×
1349
        return false
×
1350
}
1351

1352
func (cont *AciController) isNADWithSameVlanAlreadyPresent(aaepName string, epgDn string, newAaepMonitorData *AaepEpgAttachData) (*AaepEpgAttachData, string) {
×
1353
        allNADs, err := cont.getAllNADs()
×
1354
        if err != nil {
×
1355
                cont.log.Errorf("Failed to get all NADs: %v", err)
×
1356
                return nil, ""
×
1357
        }
×
1358

1359
        for _, nad := range allNADs {
×
1360
                ns := nad.Namespace
×
1361
                annotations := nad.ObjectMeta.Annotations
×
1362
                if annotations == nil {
×
1363
                        continue
×
1364
                }
1365
                // Check for required annotations
1366
                if annotations["managed-by"] != "cisco-network-operator" ||
×
1367
                        annotations["aci-sync-status"] != "in-sync" ||
×
1368
                        annotations["aaep-name"] != aaepName {
×
1369
                        continue
×
1370
                }
1371

1372
                existingEpgDn := annotations["epg-dn"]
×
1373
                // Skip if same EPG DN
×
1374
                if existingEpgDn == epgDn {
×
1375
                        cont.log.Infof("Found existing NAD %s/%s for epg %s ", ns, nad.Name, epgDn)
×
1376
                        return nil, ""
×
1377
                }
×
1378
                // Compare VLAN
1379
                existingVlan, err := strconv.Atoi(annotations["vlan"])
×
1380
                if err != nil {
×
1381
                        cont.log.Warnf("Invalid VLAN in NAD %s/%s: %v", ns, nad.Name, err)
×
1382
                        continue
×
1383
                }
1384

1385
                if existingVlan == newAaepMonitorData.encapVlan {
×
1386
                        cont.log.Infof("Found existing NAD %s/%s with same VLAN %d for aaep %s", ns, nad.Name, existingVlan, aaepName)
×
1387

×
1388
                        return &AaepEpgAttachData{
×
1389
                                nadName:       annotations["cno-name"],
×
1390
                                namespaceName: ns,
×
1391
                                encapVlan:     existingVlan,
×
1392
                                nadCreated:    true,
×
1393
                        }, existingEpgDn
×
1394
                }
×
1395
        }
1396

1397
        return nil, ""
×
1398
}
1399

1400
func (cont *AciController) getAllNADs() ([]nadapi.NetworkAttachmentDefinition, error) {
×
1401
        nadClient := cont.env.(*K8sEnvironment).nadClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions("")
×
1402

×
1403
        nadList, err := nadClient.List(context.TODO(), metav1.ListOptions{})
×
1404
        if err != nil {
×
1405
                return nil, fmt.Errorf("failed to list all NADs: %v", err)
×
1406
        }
×
1407

1408
        return nadList.Items, nil
×
1409
}
1410

1411
func (cont *AciController) syncAndCleanNadCache() {
×
1412
        var reason string
×
1413
        cont.indexMutex.Lock()
×
1414
        defer cont.indexMutex.Unlock()
×
NEW
1415
        cont.log.Infof("Starting sync and clean of NAD cache")
×
1416
        for aaepName, aaepEpgAttachDataMap := range cont.sharedAaepMonitor {
×
1417
                // Get the latest EPG attachment details from APIC for the AAEP
×
1418
                epgVlanMapList := cont.getAaepEpgAttObjDetails(aaepName)
×
1419

×
1420
                // Create a set of EPGs from the APIC state for quick lookup
×
1421
                apicEpgSet := make(map[string]*EpgVlanMap)
×
1422
                for _, epgVlanMap := range epgVlanMapList {
×
1423
                        apicEpgSet[epgVlanMap.epgDn] = &epgVlanMap
×
1424
                }
×
1425

1426
                var namespaceName string
×
1427
                // Iterate through the cache and find entries not in the APIC state
×
1428
                for epgDn, oldAaepEpgAttachData := range aaepEpgAttachDataMap {
×
1429
                        if oldAaepEpgAttachData == nil {
×
1430
                                continue
×
1431
                        }
1432
                        epgAnnotations := cont.getEpgAnnotations(epgDn)
×
1433
                        if epgAnnotations == nil {
×
1434
                                namespaceName = ""
×
1435
                        } else {
×
1436
                                namespaceName, _ = cont.getSpecificEPGAnnotation(epgAnnotations)
×
1437
                        }
×
1438
                        if _, exists := apicEpgSet[epgDn]; !exists {
×
1439
                                cont.log.Infof("Deleting stale cache entry: EPG %s for AAEP %s. Reason: EPG detached or deleted.", epgDn, aaepName)
×
1440
                                delete(cont.sharedAaepMonitor[aaepName], epgDn)
×
1441
                                if oldAaepEpgAttachData.nadCreated {
×
1442
                                        cont.log.Debugf("Deleting NAD for EPG %s with AAEP %s due to AaepEpgDetached", epgDn, aaepName)
×
1443
                                        cont.deleteNetworkAttachmentDefinition(aaepName, epgDn, oldAaepEpgAttachData, "AaepEpgDetached")
×
1444
                                }
×
1445
                                cont.log.Infof("Successfully deleted cache entry: EPG %s for AAEP %s.", epgDn, aaepName)
×
1446
                        } else if namespaceName != aaepEpgAttachDataMap[epgDn].namespaceName {
×
1447
                                cont.log.Infof("Deleting stale cache entry: EPG %s for AAEP %s. Reason: namespace annotation changed from %s to %s",
×
1448
                                        epgDn, aaepName, aaepEpgAttachDataMap[epgDn].namespaceName, namespaceName)
×
1449
                                delete(cont.sharedAaepMonitor[aaepName], epgDn)
×
1450
                                if oldAaepEpgAttachData.nadCreated {
×
1451
                                        if cont.checkIfEPGExists(epgDn) {
×
1452
                                                if namespaceName == "" {
×
1453
                                                        reason = "NamespaceAnnotationRemoved"
×
1454
                                                } else {
×
1455
                                                        reason = "NamespaceAnnotationModified"
×
1456
                                                }
×
1457
                                        } else {
×
1458
                                                reason = "EpgDeleted"
×
1459
                                        }
×
1460
                                        cont.log.Debugf("Deleting NAD for EPG %s with AAEP %s due to AaepEpgDetached", epgDn, aaepName)
×
1461
                                        cont.deleteNetworkAttachmentDefinition(aaepName, epgDn, oldAaepEpgAttachData, reason)
×
1462
                                }
1463
                                cont.log.Infof("Successfully deleted cache entry: EPG %s for AAEP %s.", epgDn, aaepName)
×
1464
                        } else if apicEpgSet[epgDn].encapVlan != aaepEpgAttachDataMap[epgDn].encapVlan {
×
1465
                                cont.log.Infof("Updating NAD for EPG %s with AAEP %s due to VLAN change from %d to %d",
×
1466
                                        epgDn, aaepName, aaepEpgAttachDataMap[epgDn].encapVlan, apicEpgSet[epgDn].encapVlan)
×
1467
                                delete(cont.sharedAaepMonitor[aaepName], epgDn)
×
1468
                                if oldAaepEpgAttachData.nadCreated {
×
1469
                                        cont.log.Debugf("Deleting NAD for EPG %s with AAEP %s due to AaepEpgDetached", epgDn, aaepName)
×
1470
                                        cont.deleteNetworkAttachmentDefinition(aaepName, epgDn, oldAaepEpgAttachData, "VlanUpdatedInAaepEpgAttach")
×
1471
                                }
×
1472
                                cont.log.Infof("Successfully deleted cache entry: EPG %s for AAEP %s.", epgDn, aaepName)
×
1473
                        }
1474
                }
1475
        }
NEW
1476
        cont.log.Infof("completed sync and clean of NAD cache")
×
1477
}
1478

NEW
1479
func (cont *AciController) syncAndCleanNads() {
×
NEW
1480
        cont.indexMutex.Lock()
×
NEW
1481
        defer cont.indexMutex.Unlock()
×
NEW
1482
        cont.log.Infof("Starting sync and clean of NADs")
×
NEW
1483
        allNADs, err := cont.getAllNADs()
×
NEW
1484
        if err != nil {
×
NEW
1485
                cont.log.Errorf("Failed to get all NADs during sync and clean: %v", err)
×
NEW
1486
                return
×
NEW
1487
        }
×
1488

NEW
1489
        for _, nad := range allNADs {
×
NEW
1490
                annotations := nad.ObjectMeta.Annotations
×
NEW
1491
                if annotations == nil {
×
NEW
1492
                        continue
×
1493
                }
1494
                // Check for required annotations
NEW
1495
                if annotations["managed-by"] != "cisco-network-operator" ||
×
NEW
1496
                        annotations["aci-sync-status"] != "in-sync" {
×
NEW
1497
                        continue
×
1498
                }
1499

NEW
1500
                aaepName := annotations["aaep-name"]
×
NEW
1501
                epgDn := annotations["epg-dn"]
×
NEW
1502
                // Check if EPG is still attached with AAEP
×
NEW
1503
                if !cont.isEpgAttachedWithAaep(aaepName, epgDn, true) {
×
NEW
1504
                        cont.log.Infof("Deleting stale NAD %s/%s: EPG %s not attached to AAEP %s", nad.Namespace, nad.Name, epgDn, aaepName)
×
NEW
1505
                        delete(cont.sharedAaepMonitor[aaepName], epgDn)
×
NEW
1506
                        aaepEpgAttachData := &AaepEpgAttachData{
×
NEW
1507
                                nadName:       annotations["cno-name"],
×
NEW
1508
                                namespaceName: nad.Namespace,
×
NEW
1509
                                encapVlan:     cont.getVlanId(annotations["vlan"]),
×
NEW
1510
                                nadCreated:    true,
×
NEW
1511
                        }
×
NEW
1512
                        cont.deleteNetworkAttachmentDefinition(aaepName, epgDn, aaepEpgAttachData, "StaleNadCleanup")
×
NEW
1513
                }
×
1514
        }
NEW
1515
        cont.log.Infof("Completed sync and clean of NADs")
×
1516
}
1517

NEW
1518
func (cont *AciController) subscribeSafely(epgDn string) {
×
NEW
1519
        const maxRetries = 5
×
NEW
1520
        retries := 0
×
NEW
1521

×
NEW
1522
        for retries < maxRetries {
×
NEW
1523
                defer func() {
×
NEW
1524
                        if r := recover(); r != nil && cont.checkIfEPGExists(epgDn) {
×
NEW
1525
                                retries++
×
NEW
1526
                                cont.log.Infof("Found issue while subscribing for %s: %v. Retry: %d", epgDn, r, retries)
×
NEW
1527
                        }
×
1528
                }()
1529

NEW
1530
                cont.apicConn.AddImmediateSubscriptionDnLocked(epgDn, []string{"tagAnnotation"}, cont.handleAnnotationAdded,
×
NEW
1531
                        cont.handleAnnotationDeleted)
×
NEW
1532
                return
×
1533
        }
1534

NEW
1535
        cont.log.Errorf("Failed to subscribe to EPG %s after %d retries", epgDn, maxRetries)
×
1536
}
1537

NEW
1538
func (cont *AciController) getNAD(aaepName, epgDn string) *AaepEpgAttachData {
×
NEW
1539
        allNADs, err := cont.getAllNADs()
×
NEW
1540
        if err != nil {
×
NEW
1541
                cont.log.Errorf("Failed to get all NADs during getting NAD details: %v", err)
×
NEW
1542
                return nil
×
NEW
1543
        }
×
1544

NEW
1545
        for _, nad := range allNADs {
×
NEW
1546
                annotations := nad.ObjectMeta.Annotations
×
NEW
1547
                if annotations == nil {
×
NEW
1548
                        continue
×
1549
                }
1550
                // Check for required annotations
NEW
1551
                if annotations["managed-by"] != "cisco-network-operator" ||
×
NEW
1552
                        annotations["aci-sync-status"] != "in-sync" {
×
NEW
1553
                        continue
×
1554
                }
1555

NEW
1556
                nadAaepName := annotations["aaep-name"]
×
NEW
1557
                nadEpgDn := annotations["epg-dn"]
×
NEW
1558
                // Check if EPG is still attached with AAEP
×
NEW
1559
                if aaepName == nadAaepName && epgDn == nadEpgDn {
×
NEW
1560
                        aaepEpgAttachData := &AaepEpgAttachData{
×
NEW
1561
                                nadName:       annotations["cno-name"],
×
NEW
1562
                                namespaceName: nad.Namespace,
×
NEW
1563
                                encapVlan:     cont.getVlanId(annotations["vlan"]),
×
NEW
1564
                                nadCreated:    true,
×
NEW
1565
                        }
×
NEW
1566
                        return aaepEpgAttachData
×
NEW
1567
                }
×
1568
        }
NEW
1569
        return nil
×
1570
}
1571

NEW
1572
func (cont *AciController) cleanStaleNADLocked(aaepName, epgDn string) {
×
NEW
1573
        aaepEpgAttachData := cont.getNAD(aaepName, epgDn)
×
NEW
1574
        if aaepEpgAttachData != nil {
×
NEW
1575
                delete(cont.sharedAaepMonitor[aaepName], epgDn)
×
NEW
1576
                cont.deleteNetworkAttachmentDefinition(aaepName, epgDn, aaepEpgAttachData, "StaleNadCleanup")
×
NEW
1577
                cont.log.Infof("Deleted stale NAD for EPG: %s detached from AAEP: %s", epgDn, aaepName)
×
NEW
1578
        }
×
1579
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc