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

noironetworks / aci-containers / 11144

22 Oct 2025 03:03PM UTC coverage: 65.289% (-0.6%) from 65.843%
11144

push

travis-pro

web-flow
Merge pull request #1597 from noironetworks/mmr-6.1.1-vmm_lite_bridge_nad

Add optional NAD bridge configuration fields to ControllerConfig

0 of 46 new or added lines in 1 file covered. (0.0%)

333 existing lines in 5 files now uncovered.

13383 of 20498 relevant lines covered (65.29%)

0.75 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

156
        addedAaeps, removedAaeps := cont.getAaepDiff(aaepMonitorConfig.Spec.Aaeps)
×
157
        for _, aaepName := range addedAaeps {
×
158
                cont.reconcileNadData(aaepName)
×
159
        }
×
160

161
        for _, aaepName := range removedAaeps {
×
162
                cont.cleanAnnotationSubscriptions(aaepName)
×
163

×
164
                cont.indexMutex.Lock()
×
165
                aaepEpgAttachDataMap := cont.sharedAaepMonitor[aaepName]
×
166
                delete(cont.sharedAaepMonitor, aaepName)
×
167

×
168
                for epgDn, aaepEpgAttachData := range aaepEpgAttachDataMap {
×
169
                        cont.deleteNetworkAttachmentDefinition(aaepName, epgDn, aaepEpgAttachData, "AaepRemovedFromCR")
×
170
                }
×
171
                cont.indexMutex.Unlock()
×
172
        }
173

174
        return false
×
175
}
176

177
func (cont *AciController) handleAaepMonitorConfigurationDelete(key string) bool {
×
178
        cont.indexMutex.Lock()
×
179
        defer cont.indexMutex.Unlock()
×
180
        for aaepName, aaepEpgAttachDataMap := range cont.sharedAaepMonitor {
×
181
                cont.cleanAnnotationSubscriptions(aaepName)
×
182

×
183
                for epgDn, aaepEpgAttachData := range aaepEpgAttachDataMap {
×
184
                        cont.deleteNetworkAttachmentDefinition(aaepName, epgDn, aaepEpgAttachData, "CRDeleted")
×
185
                }
×
186
        }
187

188
        cont.sharedAaepMonitor = make(map[string]map[string]*AaepEpgAttachData)
×
189
        return false
×
190
}
191

192
func (cont *AciController) handleAaepEpgAttach(infraRsObj apicapi.ApicObject) {
×
193
        infraRsObjDn := infraRsObj.GetDn()
×
194
        aaepName := cont.matchesAEPFilter(infraRsObjDn)
×
195
        if aaepName == "" {
×
196
                cont.log.Debugf("Unable to find AAEP from %s in monitoring list", infraRsObjDn)
×
197
                return
×
198
        }
×
199

200
        state := infraRsObj.GetAttrStr("state")
×
201
        if state != "formed" {
×
202
                cont.log.Debugf("Skipping NAD creation: %s is with state: %s", infraRsObjDn, state)
×
203
                return
×
204
        }
×
205

206
        epgDn := infraRsObj.GetAttrStr("tDn")
×
207
        encap := infraRsObj.GetAttrStr("encap")
×
208
        vlanID := cont.getVlanId(encap)
×
209

×
210
        if cont.checkDuplicateAaepEpgAttachRequest(aaepName, epgDn, vlanID) {
×
211
                cont.log.Infof("AAEP %s EPG %s attachment data already exists", aaepName, epgDn)
×
212
                return
×
213
        }
×
214

215
        if cont.checkVlanUsedInCluster(vlanID) {
×
216
                // This is needed when user updates vlan to the vlan already used in cluster
×
217
                cont.handleOverlappingVlan(aaepName, epgDn, vlanID)
×
218

×
219
                cont.log.Errorf("Skipping NAD creation: VLAN %d is already used in cluster", vlanID)
×
220
                return
×
221
        }
×
222

UNCOV
223
        defer cont.apicConn.AddImmediateSubscriptionDnLocked(epgDn, []string{"tagAnnotation"}, cont.handleAnnotationAdded,
×
224
                cont.handleAnnotationDeleted)
×
225

×
226
        epgVlanMap := &EpgVlanMap{
×
227
                epgDn:     epgDn,
×
228
                encapVlan: vlanID,
×
UNCOV
229
        }
×
UNCOV
230

×
231
        aaepEpgAttachData := cont.collectNadData(epgVlanMap)
×
232
        if aaepEpgAttachData == nil {
×
233
                return
×
234
        }
×
235

236
        if cont.checkIfEpgWithOverlappingVlan(aaepName, vlanID) {
×
UNCOV
237
                // This is needed when user updates vlan from non-overlapping to overlapping
×
238
                cont.handleOverlappingVlan(aaepName, epgDn, vlanID)
×
239

×
240
                cont.indexMutex.Lock()
×
241
                if cont.sharedAaepMonitor[aaepName] == nil {
×
242
                        cont.sharedAaepMonitor[aaepName] = make(map[string]*AaepEpgAttachData)
×
UNCOV
243
                }
×
244
                aaepEpgAttachData.nadCreated = false
×
245
                cont.sharedAaepMonitor[aaepName][epgDn] = aaepEpgAttachData
×
246
                cont.indexMutex.Unlock()
×
247
                cont.log.Errorf("Skipping NAD creation: EPG %s with AAEP %s has overlapping VLAN %d", epgDn, aaepName, vlanID)
×
248
                return
×
249
        }
250
        cont.indexMutex.Lock()
×
251
        oldAaepMonitorData := cont.getAaepEpgAttachDataLocked(aaepName, epgDn)
×
252
        cont.indexMutex.Unlock()
×
UNCOV
253

×
254
        cont.syncNADsWithAciState(aaepName, epgDn, oldAaepMonitorData, aaepEpgAttachData, "AaepEpgAttached")
×
255
}
256

257
func (cont *AciController) handleAaepEpgDetach(infraRsObjDn string) {
×
UNCOV
258
        aaepName := cont.matchesAEPFilter(infraRsObjDn)
×
UNCOV
259
        if aaepName == "" {
×
260
                cont.log.Debugf("Unable to find AAEP from %s in monitoring list", infraRsObjDn)
×
261
                return
×
262
        }
×
263

264
        epgDn := cont.getEpgDnFromInfraRsDn(infraRsObjDn)
×
265

×
266
        // Need to check if EPG is not attached with any other AAEP
×
267
        if !cont.isEpgAttachedWithAaep(epgDn) {
×
268
                cont.apicConn.UnsubscribeImmediateDnLocked(epgDn, []string{"tagAnnotation"})
×
UNCOV
269
        }
×
270

271
        cont.indexMutex.Lock()
×
272
        aaepEpgAttachData := cont.getAaepEpgAttachDataLocked(aaepName, epgDn)
×
273
        cont.indexMutex.Unlock()
×
UNCOV
274

×
UNCOV
275
        if aaepEpgAttachData == nil {
×
276
                cont.log.Debugf("Monitoring data not available for EPG %s with AAEP %s", epgDn, aaepName)
×
277
                return
×
278
        }
×
279
        if !cont.namespaceChecks(aaepEpgAttachData.namespaceName, epgDn) {
×
UNCOV
280
                cont.log.Debugf("Namespace %s not found", aaepEpgAttachData.namespaceName)
×
UNCOV
281
                return
×
282
        }
×
283

UNCOV
284
        if aaepEpgAttachData.nadCreated == false {
×
285
                cont.indexMutex.Lock()
×
286
                delete(cont.sharedAaepMonitor[aaepName], epgDn)
×
287
                cont.indexMutex.Unlock()
×
288
                return
×
289
        }
×
290

291
        cont.indexMutex.Lock()
×
292
        delete(cont.sharedAaepMonitor[aaepName], epgDn)
×
293
        cont.deleteNetworkAttachmentDefinition(aaepName, epgDn, aaepEpgAttachData, "AaepEpgDetached")
×
294
        cont.indexMutex.Unlock()
×
295
        cont.createNadForNextEpg(aaepName, aaepEpgAttachData.encapVlan)
×
296
}
297

UNCOV
298
func (cont *AciController) handleAnnotationAdded(obj apicapi.ApicObject) bool {
×
UNCOV
299
        annotationDn := obj.GetDn()
×
300
        epgDn := annotationDn[:strings.Index(annotationDn, "/annotationKey-")]
×
301
        aaepMonitorDataMap := cont.getAaepMonitoringDataForEpg(epgDn)
×
302

×
303
        for aaepName, aaepMonitorData := range aaepMonitorDataMap {
×
304
                if aaepMonitorData == nil {
×
305
                        cont.log.Debugf("Insufficient data for NAD creation: Monitoring data not available for EPG %s with AAEP %s", epgDn, aaepName)
×
UNCOV
306
                        continue
×
307
                }
308

UNCOV
309
                if _, exists := aaepMonitorData[epgDn]; !exists {
×
UNCOV
310
                        cont.log.Debugf("EPG %s not found in monitoring data for AAEP %s", epgDn, aaepName)
×
UNCOV
311
                        continue
×
312
                }
313

314
                aaepEpgAttachData := aaepMonitorData[epgDn]
×
315
                if !cont.namespaceChecks(aaepEpgAttachData.namespaceName, epgDn) {
×
316
                        cont.log.Debugf("Insufficient data for NAD creation: Namespace not exist, in case of EPG %s with AAEP %s", epgDn, aaepName)
×
317
                        continue
×
318
                }
319

UNCOV
320
                if cont.checkIfEpgWithOverlappingVlan(aaepName, aaepEpgAttachData.encapVlan) {
×
321
                        cont.log.Debugf("Skipping EPG annotation add handling: EPG %s with AAEP %s has overlapping VLAN %d", epgDn,
×
322
                                aaepName, aaepEpgAttachData.encapVlan)
×
323
                        continue
×
324
                }
325

326
                cont.indexMutex.Lock()
×
327
                oldAaepMonitorData := cont.getAaepEpgAttachDataLocked(aaepName, epgDn)
×
UNCOV
328
                cont.indexMutex.Unlock()
×
UNCOV
329
                cont.syncNADsWithAciState(aaepName, epgDn, oldAaepMonitorData, aaepEpgAttachData, "NamespaceAnnotationAdded")
×
330
        }
331

332
        return true
×
333
}
334

335
func (cont *AciController) handleAnnotationDeleted(annotationDn string) {
×
UNCOV
336
        epgDn := annotationDn[:strings.Index(annotationDn, "/annotationKey-")]
×
337

×
338
        aaepMonitorDataMap := cont.getAaepMonitoringDataForEpg(epgDn)
×
339

×
340
        for aaepName, aaepMonitorData := range aaepMonitorDataMap {
×
341
                cont.indexMutex.Lock()
×
UNCOV
342
                oldAaepMonitorData := cont.getAaepEpgAttachDataLocked(aaepName, epgDn)
×
343
                cont.indexMutex.Unlock()
×
UNCOV
344

×
UNCOV
345
                if oldAaepMonitorData == nil {
×
346
                        cont.log.Debugf("Monitoring data not available for EPG %s with AAEP %s", epgDn, aaepName)
×
347
                        continue
×
348
                }
349

350
                if oldAaepMonitorData.nadCreated == false {
×
351
                        cont.indexMutex.Lock()
×
352
                        delete(cont.sharedAaepMonitor[aaepName], epgDn)
×
353
                        cont.indexMutex.Unlock()
×
354
                        continue
×
355
                }
356

UNCOV
357
                if aaepMonitorData == nil {
×
UNCOV
358
                        cont.indexMutex.Lock()
×
359
                        delete(cont.sharedAaepMonitor[aaepName], epgDn)
×
360
                        cont.deleteNetworkAttachmentDefinition(aaepName, epgDn, oldAaepMonitorData, "NamespaceAnnotationRemoved")
×
361
                        cont.indexMutex.Unlock()
×
362
                        continue
×
363
                }
364

365
                aaepEpgAttachData, exists := aaepMonitorData[epgDn]
×
366
                if !exists || aaepEpgAttachData == nil {
×
367
                        cont.indexMutex.Lock()
×
UNCOV
368
                        delete(cont.sharedAaepMonitor[aaepName], epgDn)
×
369
                        cont.deleteNetworkAttachmentDefinition(aaepName, epgDn, oldAaepMonitorData, "NamespaceAnnotationRemoved")
×
UNCOV
370
                        cont.indexMutex.Unlock()
×
UNCOV
371
                        continue
×
372
                }
373

374
                cont.syncNADsWithAciState(aaepName, epgDn, oldAaepMonitorData, aaepEpgAttachData, "NamespaceAnnotationRemoved")
×
375
        }
376
}
377

378
func (cont *AciController) collectNadData(epgVlanMap *EpgVlanMap) *AaepEpgAttachData {
×
379
        epgDn := epgVlanMap.epgDn
×
380
        epgAnnotations := cont.getEpgAnnotations(epgDn)
×
381
        namespaceName, nadName := cont.getSpecificEPGAnnotation(epgAnnotations)
×
382

×
383
        if !cont.namespaceChecks(namespaceName, epgDn) {
×
384
                cont.log.Debugf("Namespace not exist, in case of EPG %s", epgDn)
×
385
                return nil
×
386
        }
×
387

388
        aaepMonitoringData := &AaepEpgAttachData{
×
389
                encapVlan:     epgVlanMap.encapVlan,
×
UNCOV
390
                nadName:       nadName,
×
UNCOV
391
                namespaceName: namespaceName,
×
392
                nadCreated:    true,
×
UNCOV
393
        }
×
UNCOV
394

×
395
        return aaepMonitoringData
×
396
}
397

398
func (cont *AciController) getAaepEpgAttachDataLocked(aaepName, epgDn string) *AaepEpgAttachData {
×
399
        aaepEpgAttachDataMap, exists := cont.sharedAaepMonitor[aaepName]
×
UNCOV
400
        if !exists || aaepEpgAttachDataMap == nil {
×
401
                cont.log.Debugf("AAEP %s EPG %s attachment data not found", aaepName, epgDn)
×
402
                return nil
×
403
        }
×
404

UNCOV
405
        aaepEpgAttachData, exists := aaepEpgAttachDataMap[epgDn]
×
UNCOV
406
        if !exists || aaepEpgAttachData == nil {
×
407
                cont.log.Debugf("AAEP %s EPG %s attachment data not found", aaepName, epgDn)
×
408
                return nil
×
409
        }
×
410

411
        cont.log.Infof("Found attachment data: %v for EPG : %s AAEP: %s", *aaepEpgAttachData, epgDn, aaepName)
×
412
        return aaepEpgAttachData
×
413
}
414

415
func (cont *AciController) matchesAEPFilter(infraRsObjDn string) string {
×
416
        cont.indexMutex.Lock()
×
417
        defer cont.indexMutex.Unlock()
×
418
        var aaepName string
×
419
        for aaepName = range cont.sharedAaepMonitor {
×
420
                expectedPrefix := fmt.Sprintf("uni/infra/attentp-%s/", aaepName)
×
421
                if strings.HasPrefix(infraRsObjDn, expectedPrefix) {
×
422
                        return aaepName
×
423
                }
×
424
        }
425
        return ""
×
426
}
427

428
func (cont *AciController) getEpgDnFromInfraRsDn(infraRsObjDn string) string {
×
UNCOV
429
        re := regexp.MustCompile(`\[(.*?)\]`)
×
UNCOV
430
        match := re.FindStringSubmatch(infraRsObjDn)
×
431

×
432
        var epgDn string
×
433
        if len(match) > 1 {
×
434
                epgDn = match[1]
×
435
                return epgDn
×
436
        }
×
437

438
        return epgDn
×
439
}
440

UNCOV
441
func (cont *AciController) getAaepMonitoringDataForEpg(epgDn string) map[string]map[string]*AaepEpgAttachData {
×
UNCOV
442
        aaepEpgAttachData := make(map[string]map[string]*AaepEpgAttachData)
×
443

×
444
        cont.indexMutex.Lock()
×
445
        defer cont.indexMutex.Unlock()
×
446
        for aaepName := range cont.sharedAaepMonitor {
×
447
                encap := cont.getEncapFromAaepEpgAttachObj(aaepName, epgDn)
×
448

×
449
                if encap != "" {
×
450
                        vlanID := cont.getVlanId(encap)
×
UNCOV
451
                        epgVlanMap := EpgVlanMap{
×
UNCOV
452
                                epgDn:     epgDn,
×
453
                                encapVlan: vlanID,
×
454
                        }
×
455
                        if aaepEpgAttachData[aaepName] == nil {
×
456
                                aaepEpgAttachData[aaepName] = make(map[string]*AaepEpgAttachData)
×
UNCOV
457
                        }
×
UNCOV
458
                        aaepEpgAttachData[aaepName][epgDn] = cont.collectNadData(&epgVlanMap)
×
459
                }
460
        }
461

462
        return aaepEpgAttachData
×
463
}
464

UNCOV
465
func (cont *AciController) cleanAnnotationSubscriptions(aaepName string) {
×
UNCOV
466
        epgVlanMapList := cont.getAaepEpgAttObjDetails(aaepName)
×
467
        if epgVlanMapList == nil {
×
468
                return
×
469
        }
×
470

471
        for _, epgVlanMap := range epgVlanMapList {
×
472
                cont.apicConn.UnsubscribeImmediateDnLocked(epgVlanMap.epgDn, []string{"tagAnnotation"})
×
473
        }
×
474
}
475

476
func (cont *AciController) syncNADsWithAciState(aaepName string, epgDn string, oldAaepEpgAttachData,
477
        aaepEpgAttachData *AaepEpgAttachData, syncReason string) {
×
478
        if oldAaepEpgAttachData == nil {
×
479
                cont.indexMutex.Lock()
×
UNCOV
480
                needCacheChange := cont.createNetworkAttachmentDefinition(aaepName, epgDn, aaepEpgAttachData, syncReason)
×
481
                if needCacheChange {
×
UNCOV
482
                        if cont.sharedAaepMonitor[aaepName] == nil {
×
UNCOV
483
                                cont.sharedAaepMonitor[aaepName] = make(map[string]*AaepEpgAttachData)
×
UNCOV
484
                        }
×
485
                        cont.sharedAaepMonitor[aaepName][epgDn] = aaepEpgAttachData
×
486
                }
487
                cont.indexMutex.Unlock()
×
488
        } else {
×
489
                if oldAaepEpgAttachData.namespaceName != aaepEpgAttachData.namespaceName {
×
490
                        cont.indexMutex.Lock()
×
491
                        delete(cont.sharedAaepMonitor[aaepName], epgDn)
×
492
                        cont.deleteNetworkAttachmentDefinition(aaepName, epgDn, oldAaepEpgAttachData, syncReason)
×
UNCOV
493
                        cont.indexMutex.Unlock()
×
494

×
495
                        cont.indexMutex.Lock()
×
496
                        needCacheChange := cont.createNetworkAttachmentDefinition(aaepName, epgDn, aaepEpgAttachData, syncReason)
×
497
                        if needCacheChange {
×
UNCOV
498
                                if cont.sharedAaepMonitor[aaepName] == nil {
×
499
                                        cont.sharedAaepMonitor[aaepName] = make(map[string]*AaepEpgAttachData)
×
500
                                }
×
501
                                cont.sharedAaepMonitor[aaepName][epgDn] = aaepEpgAttachData
×
502
                        }
503
                        cont.indexMutex.Unlock()
×
504
                        return
×
505
                }
506

507
                if oldAaepEpgAttachData.nadName != aaepEpgAttachData.nadName || oldAaepEpgAttachData.encapVlan != aaepEpgAttachData.encapVlan {
×
508
                        cont.indexMutex.Lock()
×
509
                        needCacheChange := cont.createNetworkAttachmentDefinition(aaepName, epgDn, aaepEpgAttachData, syncReason)
×
510
                        if needCacheChange {
×
511
                                delete(cont.sharedAaepMonitor[aaepName], epgDn)
×
UNCOV
512
                                cont.sharedAaepMonitor[aaepName][epgDn] = aaepEpgAttachData
×
UNCOV
513
                        }
×
514
                        cont.indexMutex.Unlock()
×
515
                }
516

517
                if oldAaepEpgAttachData.encapVlan != aaepEpgAttachData.encapVlan {
×
UNCOV
518
                        cont.createNadForNextEpg(aaepName, oldAaepEpgAttachData.encapVlan)
×
519
                }
×
520
        }
521
}
522

523
func (cont *AciController) addDeferredNADs(namespaceName string) {
×
UNCOV
524
        cont.indexMutex.Lock()
×
UNCOV
525
        defer cont.indexMutex.Unlock()
×
526
        for aaepName := range cont.sharedAaepMonitor {
×
UNCOV
527
                epgVlanMapList := cont.getAaepEpgAttObjDetails(aaepName)
×
UNCOV
528

×
529
                if epgVlanMapList == nil {
×
530
                        continue
×
531
                }
532

533
                for _, epgVlanMap := range epgVlanMapList {
×
534
                        aaepEpgAttachData := cont.collectNadData(&epgVlanMap)
×
535
                        if aaepEpgAttachData == nil || aaepEpgAttachData.namespaceName != namespaceName {
×
UNCOV
536
                                continue
×
537
                        }
538

539
                        epgDn := epgVlanMap.epgDn
×
540
                        needCacheChange := cont.createNetworkAttachmentDefinition(aaepName, epgDn, aaepEpgAttachData, "NamespaceCreated")
×
541
                        if needCacheChange {
×
542
                                if cont.sharedAaepMonitor[aaepName] == nil {
×
UNCOV
543
                                        cont.sharedAaepMonitor[aaepName] = make(map[string]*AaepEpgAttachData)
×
UNCOV
544
                                }
×
545
                                cont.sharedAaepMonitor[aaepName][epgDn] = aaepEpgAttachData
×
546
                        }
547
                }
548
        }
549
}
550

UNCOV
551
func (cont *AciController) cleanNADs(namespaceName string) {
×
552
        cont.indexMutex.Lock()
×
553
        defer cont.indexMutex.Unlock()
×
554
        for aaepName := range cont.sharedAaepMonitor {
×
555
                aaepEpgAttachDataMap, exists := cont.sharedAaepMonitor[aaepName]
×
556
                if !exists || aaepEpgAttachDataMap == nil {
×
557
                        continue
×
558
                }
559

560
                var epgsToDelete []string
×
561
                for epgDn, aaepEpgData := range aaepEpgAttachDataMap {
×
562
                        if aaepEpgData.namespaceName != namespaceName {
×
563
                                epgsToDelete = append(epgsToDelete, epgDn)
×
564
                        }
×
565
                }
566

567
                for _, epgDn := range epgsToDelete {
×
568
                        delete(cont.sharedAaepMonitor[aaepName], epgDn)
×
569
                }
×
570
        }
571
}
572

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

×
576
        resp, err := cont.apicConn.GetApicResponse(uri)
×
577
        if err != nil {
×
578
                cont.log.Errorf("Failed to get response from APIC: %v", err)
×
579
                return nil
×
UNCOV
580
        }
×
581

UNCOV
582
        if len(resp.Imdata) == 0 {
×
UNCOV
583
                cont.log.Debugf("Can't find EPGs attached with AAEP %s", aaepName)
×
584
                return nil
×
585
        }
×
586

587
        epgVlanMapList := make([]EpgVlanMap, 0)
×
588
        for _, respImdata := range resp.Imdata {
×
589
                aaepEpgAttachObj, ok := respImdata["infraRsFuncToEpg"]
×
590
                if !ok {
×
591
                        cont.log.Debugf("Empty AAEP EPG attachment object")
×
592
                        continue
×
593
                }
594

UNCOV
595
                if state, hasState := aaepEpgAttachObj.Attributes["state"].(string); hasState {
×
596
                        if state != "formed" {
×
597
                                aaepEpgAttchDn := aaepEpgAttachObj.Attributes["dn"].(string)
×
598
                                cont.log.Debugf("%s is with state: %s", aaepEpgAttchDn, state)
×
599
                                continue
×
600
                        }
601
                }
602
                vlanID := 0
×
603
                if encap, hasEncap := aaepEpgAttachObj.Attributes["encap"].(string); hasEncap {
×
604
                        vlanID = cont.getVlanId(encap)
×
605
                }
×
606

UNCOV
607
                epgVlanMap := EpgVlanMap{
×
608
                        epgDn:     aaepEpgAttachObj.Attributes["tDn"].(string),
×
609
                        encapVlan: vlanID,
×
610
                }
×
611
                epgVlanMapList = append(epgVlanMapList, epgVlanMap)
×
612
        }
613

UNCOV
614
        return epgVlanMapList
×
615
}
616

617
func (cont *AciController) getEpgAnnotations(epgDn string) map[string]string {
×
618
        uri := fmt.Sprintf("/api/node/mo/%s.json?query-target=subtree&target-subtree-class=tagAnnotation", epgDn)
×
619
        resp, err := cont.apicConn.GetApicResponse(uri)
×
620
        if err != nil {
×
621
                cont.log.Errorf("Failed to get response from APIC: %v", err)
×
622
                return nil
×
623
        }
×
624

625
        annotationsMap := make(map[string]string)
×
626
        for _, respImdata := range resp.Imdata {
×
627
                annotationObj, ok := respImdata["tagAnnotation"]
×
UNCOV
628
                if !ok {
×
UNCOV
629
                        cont.log.Debugf("Empty tag annotation of EPG %s", epgDn)
×
630
                        continue
×
631
                }
632

633
                key := annotationObj.Attributes["key"].(string)
×
634
                annotationsMap[key] = annotationObj.Attributes["value"].(string)
×
635
        }
636

637
        return annotationsMap
×
638
}
639

640
func (cont *AciController) getSpecificEPGAnnotation(annotations map[string]string) (string, string) {
×
641
        namespaceNameAnnotationKey := cont.config.CnoIdentifier + "-namespace"
×
642
        namespaceName, exists := annotations[namespaceNameAnnotationKey]
×
643
        if !exists {
×
644
                cont.log.Debugf("Annotation with key '%s' not found", namespaceNameAnnotationKey)
×
645
        }
×
646

UNCOV
647
        nadNameAnnotationKey := cont.config.CnoIdentifier + "-nad"
×
648
        nadName, exists := annotations[nadNameAnnotationKey]
×
649
        if !exists {
×
650
                cont.log.Debugf("Annotation with key '%s' not found", nadNameAnnotationKey)
×
651
        }
×
652
        return namespaceName, nadName
×
653
}
654

655
func (cont *AciController) namespaceChecks(namespaceName string, epgDn string) bool {
×
656
        if namespaceName == "" {
×
657
                cont.log.Debugf("Defering NAD operation for EPG %s: Namespace name not provided in EPG annotation", epgDn)
×
658
                return false
×
659
        }
×
660

661
        kubeClient := cont.env.(*K8sEnvironment).kubeClient
×
662
        _, err := kubeClient.CoreV1().Namespaces().Get(context.TODO(), namespaceName, metav1.GetOptions{})
×
663
        namespaceExists := err == nil
×
664
        if !namespaceExists {
×
665
                cont.log.Debugf("Defering NAD operation for EPG %s: Namespace %s not exists", epgDn, namespaceName)
×
666
                return false
×
667
        }
×
668

669
        return true
×
670
}
671

672
func (cont *AciController) reconcileNadData(aaepName string) {
×
673
        epgVlanMapList := cont.getAaepEpgAttObjDetails(aaepName)
×
674

×
UNCOV
675
        for _, epgVlanMap := range epgVlanMapList {
×
UNCOV
676
                if cont.checkVlanUsedInCluster(epgVlanMap.encapVlan) {
×
UNCOV
677
                        cont.log.Errorf("Skipping NAD creation: VLAN %d is already used in cluster", epgVlanMap.encapVlan)
×
UNCOV
678
                        continue
×
679
                }
UNCOV
680
                aaepEpgAttachData := cont.collectNadData(&epgVlanMap)
×
UNCOV
681
                if aaepEpgAttachData == nil {
×
682
                        cont.apicConn.AddImmediateSubscriptionDnLocked(epgVlanMap.epgDn,
×
683
                                []string{"tagAnnotation"}, cont.handleAnnotationAdded,
×
684
                                cont.handleAnnotationDeleted)
×
685
                        continue
×
686
                }
687

UNCOV
688
                epgDn := epgVlanMap.epgDn
×
689
                if cont.checkIfEpgWithOverlappingVlan(aaepName, aaepEpgAttachData.encapVlan) {
×
690
                        aaepEpgAttachData.nadCreated = false
×
691
                        cont.indexMutex.Lock()
×
692
                        if cont.sharedAaepMonitor[aaepName] == nil {
×
693
                                cont.sharedAaepMonitor[aaepName] = make(map[string]*AaepEpgAttachData)
×
694
                        }
×
695
                        cont.sharedAaepMonitor[aaepName][epgDn] = aaepEpgAttachData
×
696
                        cont.indexMutex.Unlock()
×
697
                        cont.log.Errorf("Skipping NAD creation: EPG %s with AAEP %s has overlapping VLAN %d",
×
698
                                epgDn, aaepName, aaepEpgAttachData.encapVlan)
×
699
                        continue
×
700
                }
701

702
                cont.indexMutex.Lock()
×
UNCOV
703
                needCacheChange := cont.createNetworkAttachmentDefinition(aaepName, epgDn, aaepEpgAttachData, "AaepAddedInCR")
×
704
                if needCacheChange {
×
705
                        if cont.sharedAaepMonitor[aaepName] == nil {
×
706
                                cont.sharedAaepMonitor[aaepName] = make(map[string]*AaepEpgAttachData)
×
707
                        }
×
708
                        cont.sharedAaepMonitor[aaepName][epgDn] = aaepEpgAttachData
×
709
                }
710
                cont.indexMutex.Unlock()
×
711

×
712
                cont.apicConn.AddImmediateSubscriptionDnLocked(epgDn,
×
713
                        []string{"tagAnnotation"}, cont.handleAnnotationAdded,
×
714
                        cont.handleAnnotationDeleted)
×
715
        }
716

717
        cont.indexMutex.Lock()
×
718
        if _, ok := cont.sharedAaepMonitor[aaepName]; !ok {
×
719
                cont.sharedAaepMonitor[aaepName] = make(map[string]*AaepEpgAttachData)
×
720
        }
×
721
        cont.indexMutex.Unlock()
×
722
}
723

724
// clean converts a string to lowercase, removes underscores and dots,
725
// and replaces any other invalid character with a hyphen.
726
func cleanApicResourceNames(apicResource string) string {
×
727
        apicResource = strings.ToLower(apicResource)
×
728
        var stringBuilder strings.Builder
×
729
        for _, character := range apicResource {
×
730
                switch {
×
731
                case character == '_' || character == '.':
×
732
                        continue
×
733
                case (character >= 'a' && character <= 'z') || (character >= '0' && character <= '9') || character == '-':
×
734
                        stringBuilder.WriteRune(character)
×
735
                default:
×
736
                        stringBuilder.WriteRune('-')
×
737
                }
738
        }
739
        return strings.Trim(stringBuilder.String(), "-")
×
740
}
741

742
func (cont *AciController) generateDefaultNadName(aaepName, epgDn string) string {
×
743
        parts := strings.Split(epgDn, "/")
×
744

×
745
        tenant := parts[1][3:]
×
746
        appProfile := parts[2][3:]
×
747
        epgName := parts[3][4:]
×
748

×
749
        apicResourceNames := tenant + appProfile + epgName + aaepName
×
750
        hashBytes := sha256.Sum256([]byte(apicResourceNames))
×
751
        hash := hex.EncodeToString(hashBytes[:])[:16]
×
752

×
753
        return fmt.Sprintf("%s-%s-%s-%s",
×
754
                cleanApicResourceNames(tenant), cleanApicResourceNames(appProfile), cleanApicResourceNames(epgName), hash)
×
755
}
×
756

757
func (cont *AciController) isNADUpdateRequired(aaepName string, epgDn string, nadData *AaepEpgAttachData,
UNCOV
758
        existingNAD *nadapi.NetworkAttachmentDefinition) bool {
×
759
        vlanID := nadData.encapVlan
×
760
        namespaceName := nadData.namespaceName
×
761
        customNadName := nadData.nadName
×
762
        defaultNadName := cont.generateDefaultNadName(aaepName, epgDn)
×
763
        existingAnnotaions := existingNAD.ObjectMeta.Annotations
×
764
        if existingAnnotaions != nil {
×
765
                if existingNAD.ObjectMeta.Annotations["aci-sync-status"] == "out-of-sync" || existingNAD.ObjectMeta.Annotations["cno-name"] != customNadName {
×
766
                        return true
×
767
                }
×
768
        } else {
×
769
                // NAD exists, check if VLAN needs to be updated
×
770
                existingConfig := existingNAD.Spec.Config
×
UNCOV
771
                if existingConfig != "" {
×
UNCOV
772
                        var existingCNVConfig map[string]interface{}
×
773
                        if json.Unmarshal([]byte(existingConfig), &existingCNVConfig) == nil {
×
UNCOV
774
                                if existingVLAN, ok := existingCNVConfig["vlan"].(float64); ok {
×
UNCOV
775
                                        if int(existingVLAN) == vlanID {
×
776
                                                // VLAN hasn't changed, no update needed
×
777
                                                cont.log.Infof("NetworkAttachmentDefinition %s already exists with correct VLAN %d in namespace %s",
×
778
                                                        defaultNadName, vlanID, namespaceName)
×
779
                                                return false
×
780
                                        }
×
781
                                } else if vlanID == 0 {
×
782
                                        // Both existing and new have no VLAN, no update needed
×
783
                                        cont.log.Infof("NetworkAttachmentDefinition %s already exists with no VLAN in namespace %s", defaultNadName, namespaceName)
×
784
                                        return false
×
785
                                }
×
786
                        }
787
                }
788
        }
789

790
        return true
×
791
}
792

793
func (cont *AciController) createNetworkAttachmentDefinition(aaepName string, epgDn string, nadData *AaepEpgAttachData, createReason string) bool {
×
794
        bridge := cont.config.BridgeName
×
795
        if bridge == "" {
×
796
                cont.log.Errorf("Linux bridge name must be specified when creating NetworkAttachmentDefinitions")
×
797
                return false
×
UNCOV
798
        }
×
799

800
        vlanID := nadData.encapVlan
×
801
        namespaceName := nadData.namespaceName
×
802
        customNadName := nadData.nadName
×
803
        defaultNadName := cont.generateDefaultNadName(aaepName, epgDn)
×
804
        nadClient := cont.env.(*K8sEnvironment).nadClient
×
805
        mtu := 1500
×
806

×
807
        // Check if NAD already exists
×
808
        existingNAD, err := nadClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespaceName).Get(context.TODO(), defaultNadName, metav1.GetOptions{})
×
UNCOV
809
        nadExists := err == nil
×
UNCOV
810

×
811
        if nadExists && !cont.isNADUpdateRequired(aaepName, epgDn, nadData, existingNAD) {
×
812
                return true
×
813
        }
×
814

815
        cnvBridgeConfig := map[string]any{
×
NEW
816
                "cniVersion":                "0.3.1",
×
NEW
817
                "name":                      defaultNadName,
×
NEW
818
                "type":                      "bridge",
×
NEW
819
                "bridge":                    bridge,
×
NEW
820
                "mtu":                       mtu,
×
NEW
821
                "disableContainerInterface": true,
×
UNCOV
822
        }
×
UNCOV
823

×
NEW
824
        // Add optional parameters from controller config if they are set
×
NEW
825
        if cont.config.IsGateway != nil {
×
NEW
826
                cnvBridgeConfig["isGateway"] = *cont.config.IsGateway
×
NEW
827
        }
×
NEW
828
        if cont.config.IsDefaultGateway != nil {
×
NEW
829
                cnvBridgeConfig["isDefaultGateway"] = *cont.config.IsDefaultGateway
×
NEW
830
        }
×
NEW
831
        if cont.config.ForceAddress != nil {
×
NEW
832
                cnvBridgeConfig["forceAddress"] = *cont.config.ForceAddress
×
NEW
833
        }
×
NEW
834
        if cont.config.IpMasq != nil {
×
NEW
835
                cnvBridgeConfig["ipMasq"] = *cont.config.IpMasq
×
NEW
836
        }
×
NEW
837
        if cont.config.IpMasqBackend != "" {
×
NEW
838
                cnvBridgeConfig["ipMasqBackend"] = cont.config.IpMasqBackend
×
NEW
839
        }
×
NEW
840
        if cont.config.Mtu != nil {
×
NEW
841
                cnvBridgeConfig["mtu"] = *cont.config.Mtu
×
NEW
842
        }
×
NEW
843
        if cont.config.HairpinMode != nil {
×
NEW
844
                cnvBridgeConfig["hairpinMode"] = *cont.config.HairpinMode
×
NEW
845
        }
×
NEW
846
        if cont.config.PromiscMode != nil {
×
NEW
847
                cnvBridgeConfig["promiscMode"] = *cont.config.PromiscMode
×
NEW
848
        }
×
NEW
849
        if cont.config.Enabledad != nil {
×
NEW
850
                cnvBridgeConfig["enabledad"] = *cont.config.Enabledad
×
NEW
851
        }
×
NEW
852
        if cont.config.Macspoofchk != nil {
×
NEW
853
                cnvBridgeConfig["macspoofchk"] = *cont.config.Macspoofchk
×
NEW
854
        }
×
NEW
855
        if cont.config.DisableContainerInterface != nil {
×
NEW
856
                cnvBridgeConfig["disableContainerInterface"] = *cont.config.DisableContainerInterface
×
NEW
857
        }
×
NEW
858
        if cont.config.PortIsolation != nil {
×
NEW
859
                cnvBridgeConfig["portIsolation"] = *cont.config.PortIsolation
×
NEW
860
        }
×
NEW
861
        if len(cont.config.Ipam) > 0 {
×
NEW
862
                cnvBridgeConfig["ipam"] = cont.config.Ipam
×
NEW
863
        }
×
864
        if vlanID > 0 {
×
865
                cnvBridgeConfig["vlan"] = vlanID
×
866
        }
×
867

868
        configJSON, err := json.Marshal(cnvBridgeConfig)
×
869
        if err != nil {
×
870
                cont.log.Errorf("Failed to marshal CNV bridge config: %v", err)
×
871
                return false
×
872
        }
×
873

874
        nad := &nadapi.NetworkAttachmentDefinition{
×
875
                TypeMeta: metav1.TypeMeta{
×
UNCOV
876
                        APIVersion: "k8s.cni.cncf.io/v1",
×
UNCOV
877
                        Kind:       "NetworkAttachmentDefinition",
×
878
                },
×
UNCOV
879
                ObjectMeta: metav1.ObjectMeta{
×
UNCOV
880
                        Name:      defaultNadName,
×
881
                        Namespace: namespaceName,
×
882
                        Labels: map[string]string{
×
883
                                "managed-by": "cisco-network-operator",
×
884
                                "vlan":       strconv.Itoa(vlanID),
×
885
                        },
×
UNCOV
886
                        Annotations: map[string]string{
×
887
                                "managed-by":      "cisco-network-operator",
×
888
                                "vlan":            strconv.Itoa(vlanID),
×
889
                                "cno-name":        customNadName,
×
890
                                "aci-sync-status": "in-sync",
×
891
                                "aaep-name":       aaepName,
×
UNCOV
892
                                "epg-dn":          epgDn,
×
893
                        },
×
894
                },
×
895
                Spec: nadapi.NetworkAttachmentDefinitionSpec{
×
896
                        Config: string(configJSON),
×
897
                },
×
898
        }
×
899

×
UNCOV
900
        if nadExists {
×
901
                nad.ObjectMeta.ResourceVersion = existingNAD.ObjectMeta.ResourceVersion
×
902

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

×
UNCOV
905
                if err != nil {
×
906
                        cont.log.Errorf("Failed to update NetworkAttachmentDefinition %s from namespace %s : %v", customNadName, namespaceName, err)
×
907
                        return false
×
908
                }
×
909

910
                cont.log.Debugf("Existing NAD Annotations: %v, %s", existingNAD.ObjectMeta.Annotations, createReason)
×
911
                if existingNAD.ObjectMeta.Annotations["aci-sync-status"] == "out-of-sync" {
×
912
                        cont.submitEvent(updatedNad, createReason, cont.getNADRevampMessage(createReason))
×
UNCOV
913
                }
×
914
                cont.log.Infof("Updated NetworkAttachmentDefinition %s from namespace %s", defaultNadName, namespaceName)
×
915
        } else {
×
916
                _, err = nadClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespaceName).Create(context.TODO(), nad, metav1.CreateOptions{})
×
917
                if err != nil {
×
918
                        cont.log.Errorf("Failed to create NetworkAttachmentDefinition %s in namespace %s : %v", customNadName, namespaceName, err)
×
UNCOV
919
                        return false
×
920
                }
×
921
                cont.log.Infof("Created NetworkAttachmentDefinition %s in namespace %s", defaultNadName, namespaceName)
×
922
        }
923

924
        return true
×
925
}
926

UNCOV
927
func (cont *AciController) deleteNetworkAttachmentDefinition(aaepName string, epgDn string, nadData *AaepEpgAttachData, deleteReason string) {
×
UNCOV
928
        namespaceName := nadData.namespaceName
×
929

×
UNCOV
930
        kubeClient := cont.env.(*K8sEnvironment).kubeClient
×
UNCOV
931
        _, err := kubeClient.CoreV1().Namespaces().Get(context.TODO(), namespaceName, metav1.GetOptions{})
×
932
        namespaceExists := err == nil
×
933
        if !namespaceExists {
×
934
                cont.log.Debugf("Defering NAD deletion for EPG %s: Namespace %s not exists", epgDn, namespaceName)
×
UNCOV
935
                return
×
936
        }
×
937

938
        nadName := cont.generateDefaultNadName(aaepName, epgDn)
×
939
        nadClient := cont.env.(*K8sEnvironment).nadClient
×
940

×
941
        nadDetails, err := nadClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespaceName).Get(context.TODO(), nadName, metav1.GetOptions{})
×
942
        nadExists := err == nil
×
943

×
944
        if nadExists {
×
945
                if !cont.isVmmLiteNAD(nadDetails) {
×
946
                        return
×
947
                }
×
948

949
                if cont.isNADinUse(namespaceName, nadName) {
×
950
                        nadDetails.ObjectMeta.Annotations["aci-sync-status"] = "out-of-sync"
×
951
                        _, err = nadClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespaceName).Update(context.TODO(), nadDetails, metav1.UpdateOptions{})
×
952
                        if err != nil {
×
953
                                cont.log.Errorf("Failed to add out-of-sync annotation to the NAD %s from namespace %s : %v", nadName, namespaceName, err)
×
954
                                return
×
955
                        }
×
UNCOV
956
                        cont.submitEvent(nadDetails, deleteReason, cont.getNADDeleteMessage(deleteReason))
×
UNCOV
957
                        cont.log.Infof("Added annotation out-of-sync for NAD %s from namespace %s", nadName, namespaceName)
×
UNCOV
958
                        return
×
959
                }
960

UNCOV
961
                delete(nadDetails.ObjectMeta.Annotations, "managed-by")
×
962
                _, err = nadClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespaceName).Update(context.TODO(), nadDetails, metav1.UpdateOptions{})
×
963
                if err != nil {
×
964
                        cont.log.Errorf("Failed to remove VMM lite annotation from NetworkAttachmentDefinition %s from namespace %s: %v", nadName, namespaceName, err)
×
965
                        return
×
966
                }
×
967

968
                nadClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespaceName).Delete(context.TODO(), nadName, metav1.DeleteOptions{})
×
969
                cont.log.Infof("Deleted NAD %s from %s namespace", nadName, namespaceName)
×
970
        } else {
×
971
                cont.log.Debugf("NAD %s not there to delete in namespace %s", nadName, namespaceName)
×
972
        }
×
973
}
974

UNCOV
975
func (cont *AciController) getVlanId(encap string) int {
×
UNCOV
976
        if after, ok := strings.CutPrefix(encap, "vlan-"); ok {
×
977
                vlanStr := after
×
978
                if vlanID, err := strconv.Atoi(vlanStr); err == nil && vlanID > 0 {
×
979
                        return vlanID
×
980
                }
×
981
        } else if after, ok := strings.CutPrefix(encap, "vlan"); ok {
×
982
                vlanStr := after
×
983
                if vlanID, err := strconv.Atoi(vlanStr); err == nil && vlanID > 0 {
×
984
                        return vlanID
×
985
                }
×
986
        }
987

UNCOV
988
        return 0
×
989
}
990

UNCOV
991
func (cont *AciController) getAaepDiff(crAaeps []string) (addedAaeps, removedAaeps []string) {
×
992
        crAaepMap := make(map[string]bool)
×
993
        for _, crAaep := range crAaeps {
×
994
                crAaepMap[crAaep] = true
×
995
        }
×
996

997
        cont.indexMutex.Lock()
×
UNCOV
998
        for _, crAaep := range crAaeps {
×
999
                if _, ok := cont.sharedAaepMonitor[crAaep]; !ok {
×
UNCOV
1000
                        addedAaeps = append(addedAaeps, crAaep)
×
UNCOV
1001
                }
×
1002
        }
1003
        cont.indexMutex.Unlock()
×
1004

×
1005
        cont.indexMutex.Lock()
×
1006
        for cachedAaep := range cont.sharedAaepMonitor {
×
1007
                if !crAaepMap[cachedAaep] {
×
1008
                        removedAaeps = append(removedAaeps, cachedAaep)
×
1009
                }
×
1010
        }
1011
        cont.indexMutex.Unlock()
×
UNCOV
1012

×
UNCOV
1013
        return
×
1014
}
1015

UNCOV
1016
func (cont *AciController) getEncapFromAaepEpgAttachObj(aaepName, epgDn string) string {
×
UNCOV
1017
        uri := fmt.Sprintf("/api/node/mo/uni/infra/attentp-%s/gen-default/rsfuncToEpg-[%s].json?query-target=self", aaepName, epgDn)
×
UNCOV
1018
        resp, err := cont.apicConn.GetApicResponse(uri)
×
UNCOV
1019
        if err != nil {
×
UNCOV
1020
                cont.log.Errorf("Failed to get response from APIC: AAEP %s and EPG %s ERROR: %v", aaepName, epgDn, err)
×
UNCOV
1021
                return ""
×
UNCOV
1022
        }
×
1023

UNCOV
1024
        for _, obj := range resp.Imdata {
×
UNCOV
1025
                lresp, ok := obj["infraRsFuncToEpg"]
×
UNCOV
1026
                if !ok {
×
UNCOV
1027
                        cont.log.Errorf("InfraRsFuncToEpg object not found in response for %s", uri)
×
UNCOV
1028
                        break
×
1029
                }
UNCOV
1030
                if val, ok := lresp.Attributes["encap"]; ok {
×
UNCOV
1031
                        encap := val.(string)
×
UNCOV
1032
                        return encap
×
UNCOV
1033
                } else {
×
UNCOV
1034
                        cont.log.Errorf("Encap missing for infraRsFuncToEpg object for %s: %v", uri, err)
×
UNCOV
1035
                        break
×
1036
                }
1037
        }
1038

UNCOV
1039
        return ""
×
1040
}
1041

UNCOV
1042
func (cont *AciController) isVmmLiteNAD(nadDetails *nadapi.NetworkAttachmentDefinition) bool {
×
UNCOV
1043
        return nadDetails.ObjectMeta.Annotations["managed-by"] == "cisco-network-operator"
×
UNCOV
1044
}
×
1045

UNCOV
1046
func (cont *AciController) isNADinUse(namespaceName string, nadName string) bool {
×
UNCOV
1047
        kubeClient := cont.env.(*K8sEnvironment).kubeClient
×
UNCOV
1048
        pods, err := kubeClient.CoreV1().Pods(namespaceName).List(context.TODO(), metav1.ListOptions{})
×
UNCOV
1049
        if err == nil {
×
UNCOV
1050
                var networks []map[string]string
×
UNCOV
1051
                for _, pod := range pods.Items {
×
UNCOV
1052
                        networksAnn, ok := pod.Annotations["k8s.v1.cni.cncf.io/networks"]
×
UNCOV
1053
                        if ok && (networksAnn == nadName) {
×
UNCOV
1054
                                cont.log.Infof("NAD %s is still used by Pod %s/%s", nadName, namespaceName, pod.Name)
×
UNCOV
1055
                                return true
×
UNCOV
1056
                        }
×
UNCOV
1057
                        if err := json.Unmarshal([]byte(networksAnn), &networks); err != nil {
×
UNCOV
1058
                                cont.log.Errorf("Error while getting pod annotations: %v", err)
×
UNCOV
1059
                                return false
×
UNCOV
1060
                        }
×
UNCOV
1061
                        for _, network := range networks {
×
UNCOV
1062
                                if ok && (network["name"] == nadName) {
×
UNCOV
1063
                                        cont.log.Infof("NAD %s is still used by VM %s/%s", nadName, namespaceName, pod.Name)
×
UNCOV
1064
                                        return true
×
UNCOV
1065
                                }
×
1066
                        }
1067
                }
1068
        }
UNCOV
1069
        return false
×
1070
}
1071

UNCOV
1072
func (cont *AciController) getNADDeleteMessage(deleteReason string) string {
×
UNCOV
1073
        messagePrefix := "NAD is in use by pods: "
×
UNCOV
1074
        switch {
×
UNCOV
1075
        case deleteReason == "NamespaceAnnotationRemoved":
×
UNCOV
1076
                return messagePrefix + "Either EPG deleted or namespace name EPG annotaion removed"
×
UNCOV
1077
        case deleteReason == "AaepEpgDetached":
×
UNCOV
1078
                return messagePrefix + "EPG detached from AAEP"
×
UNCOV
1079
        case deleteReason == "CRDeleted":
×
UNCOV
1080
                return messagePrefix + "aaepmonitor CR deleted"
×
UNCOV
1081
        case deleteReason == "AaepRemovedFromCR":
×
UNCOV
1082
                return messagePrefix + "AAEP removed from aaepmonitor CR"
×
UNCOV
1083
        case deleteReason == "AaepEpgAttachedWithVlanUsedInCluster":
×
UNCOV
1084
                return messagePrefix + "EPG with AAEP has overlapping VLAN with existing cluster VLAN"
×
UNCOV
1085
        case deleteReason == "AaepEpgAttachedWithOverlappingVlan":
×
UNCOV
1086
                return messagePrefix + "EPG with AAEP has overlapping VLAN with another EPG"
×
1087
        }
UNCOV
1088
        return messagePrefix + "One or many pods are using NAD"
×
1089
}
1090

UNCOV
1091
func (cont *AciController) getNADRevampMessage(createReason string) string {
×
UNCOV
1092
        messagePrefix := "NAD is in sync: "
×
UNCOV
1093
        switch {
×
UNCOV
1094
        case createReason == "NamespaceAnnotationAdded":
×
UNCOV
1095
                return messagePrefix + "Namespace name EPG annotaion added"
×
UNCOV
1096
        case createReason == "AaepEpgAttached":
×
UNCOV
1097
                return messagePrefix + "EPG attached with AAEP"
×
UNCOV
1098
        case createReason == "AaepAddedInCR":
×
UNCOV
1099
                return messagePrefix + "AAEP added back in aaepmonitor CR"
×
UNCOV
1100
        case createReason == "NamespaceCreated":
×
UNCOV
1101
                return messagePrefix + "Namespace created back"
×
UNCOV
1102
        case createReason == "NextEpgNadCreation":
×
UNCOV
1103
                return messagePrefix + "Next EPG NAD creation"
×
1104
        }
UNCOV
1105
        return messagePrefix + "NAD synced with ACI"
×
1106
}
1107

UNCOV
1108
func (cont *AciController) isEpgAttachedWithAaep(epgDn string) bool {
×
UNCOV
1109
        cont.indexMutex.Lock()
×
UNCOV
1110
        defer cont.indexMutex.Unlock()
×
UNCOV
1111
        for aaepName := range cont.sharedAaepMonitor {
×
UNCOV
1112
                encap := cont.getEncapFromAaepEpgAttachObj(aaepName, epgDn)
×
UNCOV
1113
                if encap != "" {
×
UNCOV
1114
                        return true
×
UNCOV
1115
                }
×
1116
        }
UNCOV
1117
        return false
×
1118
}
1119

UNCOV
1120
func (cont *AciController) checkDuplicateAaepEpgAttachRequest(aaepName, epgDn string, vlanID int) bool {
×
UNCOV
1121
        cont.indexMutex.Lock()
×
UNCOV
1122
        defer cont.indexMutex.Unlock()
×
UNCOV
1123
        aaepEpgAttachDataMap, exists := cont.sharedAaepMonitor[aaepName]
×
UNCOV
1124
        if !exists || aaepEpgAttachDataMap == nil {
×
UNCOV
1125
                return false
×
UNCOV
1126
        }
×
1127

UNCOV
1128
        aaepEpgAttachData, exists := aaepEpgAttachDataMap[epgDn]
×
UNCOV
1129
        if !exists || aaepEpgAttachData == nil {
×
UNCOV
1130
                return false
×
UNCOV
1131
        }
×
1132

UNCOV
1133
        if vlanID == aaepEpgAttachData.encapVlan {
×
UNCOV
1134
                return true
×
UNCOV
1135
        }
×
UNCOV
1136
        return false
×
1137
}
1138

UNCOV
1139
func (cont *AciController) checkIfEpgWithOverlappingVlan(aaepName string, vlanId int) bool {
×
UNCOV
1140
        cont.indexMutex.Lock()
×
UNCOV
1141
        defer cont.indexMutex.Unlock()
×
UNCOV
1142
        aaepEpgAttachDataMap, exists := cont.sharedAaepMonitor[aaepName]
×
UNCOV
1143
        if !exists || aaepEpgAttachDataMap == nil {
×
UNCOV
1144
                return false
×
UNCOV
1145
        }
×
1146

UNCOV
1147
        for _, aaepEpgData := range aaepEpgAttachDataMap {
×
UNCOV
1148
                if vlanId == aaepEpgData.encapVlan {
×
UNCOV
1149
                        return true
×
UNCOV
1150
                }
×
1151
        }
UNCOV
1152
        return false
×
1153
}
1154

UNCOV
1155
func (cont *AciController) createNadForNextEpg(aaepName string, vlanId int) {
×
UNCOV
1156
        cont.indexMutex.Lock()
×
UNCOV
1157
        defer cont.indexMutex.Unlock()
×
UNCOV
1158

×
UNCOV
1159
        aaepEpgAttachDataMap := cont.sharedAaepMonitor[aaepName]
×
UNCOV
1160
        for epgDn, aaepEpgAttachData := range aaepEpgAttachDataMap {
×
UNCOV
1161
                if aaepEpgAttachData.encapVlan != vlanId || aaepEpgAttachData.nadCreated {
×
UNCOV
1162
                        continue
×
1163
                }
UNCOV
1164
                if !cont.namespaceChecks(aaepEpgAttachData.namespaceName, epgDn) {
×
UNCOV
1165
                        continue
×
1166
                }
1167

UNCOV
1168
                needCacheChange := cont.createNetworkAttachmentDefinition(aaepName, epgDn, aaepEpgAttachData, "NextEpgNadCreation")
×
UNCOV
1169
                if needCacheChange {
×
UNCOV
1170
                        aaepEpgAttachData.nadCreated = true
×
UNCOV
1171
                        if cont.sharedAaepMonitor[aaepName] == nil {
×
UNCOV
1172
                                cont.sharedAaepMonitor[aaepName] = make(map[string]*AaepEpgAttachData)
×
UNCOV
1173
                        }
×
UNCOV
1174
                        cont.sharedAaepMonitor[aaepName][epgDn] = aaepEpgAttachData
×
1175
                }
1176
        }
1177
}
1178

UNCOV
1179
func (cont *AciController) checkVlanUsedInCluster(vlanId int) bool {
×
UNCOV
1180
        if vlanId == cont.config.KubeapiVlan {
×
UNCOV
1181
                return true
×
UNCOV
1182
        }
×
UNCOV
1183
        return false
×
1184
}
1185

UNCOV
1186
func (cont *AciController) handleOverlappingVlan(aaepName, epgDn string, vlanID int) {
×
UNCOV
1187
        cont.indexMutex.Lock()
×
UNCOV
1188
        oldAaepMonitorData := cont.getAaepEpgAttachDataLocked(aaepName, epgDn)
×
UNCOV
1189
        cont.indexMutex.Unlock()
×
UNCOV
1190

×
UNCOV
1191
        if oldAaepMonitorData != nil {
×
UNCOV
1192
                cont.indexMutex.Lock()
×
UNCOV
1193
                delete(cont.sharedAaepMonitor[aaepName], epgDn)
×
UNCOV
1194
                if oldAaepMonitorData.nadCreated {
×
UNCOV
1195
                        cont.log.Debugf("Deleting NAD for EPG %s with AAEP %s due to overlapping VLAN %d", epgDn, aaepName, vlanID)
×
UNCOV
1196
                        cont.deleteNetworkAttachmentDefinition(aaepName, epgDn, oldAaepMonitorData, "AaepEpgAttachedWithVlanUsedInCluster")
×
UNCOV
1197
                }
×
UNCOV
1198
                cont.indexMutex.Unlock()
×
UNCOV
1199
                if oldAaepMonitorData.nadCreated {
×
UNCOV
1200
                        cont.createNadForNextEpg(aaepName, oldAaepMonitorData.encapVlan)
×
UNCOV
1201
                }
×
1202
        }
1203
}
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