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

noironetworks / aci-containers / 11917

04 May 2026 06:57AM UTC coverage: 63.383% (+0.4%) from 63.028%
11917

Pull #1706

travis-pro

jeffinkottaram
Fix per-service SNAT IP assignment for no-SnatIp policies

Two bugs are fixed:

1. Namespace-level no-SnatIp SNAT policies with multiple LoadBalancer
   services incorrectly assigned all LB external IPs to all pods.
   The controller now tags each global info entry with the originating
   service key. The hostagent filters SNAT UIDs per-pod by checking
   service endpoint membership, ensuring each pod only egresses with
   the external IP of the service it belongs to.

2. After hostagent pod restarts, namespace-level no-SnatIp policies
   lost their service SNAT entries because pod events did not evaluate
   service membership for the namespace-scoped policy path. The
   namespace-scoped branch now checks for matching services when
   processing pod events, consistent with the label-scoped path.

Additionally, two  inconsistencies are corrected:

1. The deployment path for no-SnatIp policies incorrectly matched
deployment metadata labels against service pod selectors. Since
services select pods (not deployments), and pod template changes
produce new pod events handled separately, this check was both
inaccurate and redundant. It is removed.

2. Explicit-SnatIp policies matching services now consistently apply the
specified SNAT IP to the service's backend pods across all policy
evaluation paths
Pull Request #1706: Fix per-service SNAT IP assignment for no-SnatIp policies

57 of 87 new or added lines in 2 files covered. (65.52%)

1119 existing lines in 9 files now uncovered.

13628 of 21501 relevant lines covered (63.38%)

0.72 hits per line

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

96.3
/pkg/controller/config.go
1
// Copyright 2017 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 WARRANTIES 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
package controller
16

17
import (
18
        "flag"
19

20
        "github.com/noironetworks/aci-containers/pkg/ipam"
21
)
22

23
type OpflexGroup struct {
24
        PolicySpace string `json:"policy-space,omitempty"`
25
        Name        string `json:"name,omitempty"`
26
}
27

28
type delayService struct {
29
        Delay     int    `json:"delay,omitempty"`
30
        Name      string `json:"name,omitempty"`
31
        Namespace string `json:"namespace,omitempty"`
32
}
33

34
type serviceGraphEpAddDelay struct {
35
        Delay    int            `json:"delay,omitempty"`
36
        Services []delayService `json:"services,omitempty"`
37
}
38

39
type NodeSnatRedirectExclude struct {
40
        Group  string   `json:"group"`
41
        Labels []string `json:"labels"`
42
}
43

44
// Configuration for the controller
45
type ControllerConfig struct {
46
        // Log level
47
        LogLevel string `json:"log-level,omitempty"`
48

49
        // Absolute path to a kubeconfig file
50
        KubeConfig string `json:"kubeconfig,omitempty"`
51

52
        // TCP port to run status server on (or 0 to disable)
53
        StatusPort int `json:"status-port,omitempty"`
54

55
        // Default endpoint group annotation value
56
        DefaultEg OpflexGroup `json:"default-endpoint-group,omitempty"`
57

58
        // Default security group annotation value
59
        DefaultSg []OpflexGroup `json:"default-security-group,omitempty"`
60

61
        // Override default endpoint group assignments for a namespace
62
        // map ns name -> group
63
        NamespaceDefaultEg map[string]OpflexGroup `json:"namespace-default-endpoint-group,omitempty"`
64

65
        // Override default security group assignments for namespaces
66
        // map ns name -> slice of groups
67
        NamespaceDefaultSg map[string][]OpflexGroup `json:"namespace-default-security-group,omitempty"`
68

69
        // The hostnames or IPs for connecting to apic
70
        ApicHosts []string `json:"apic-hosts,omitempty"`
71

72
        // The username for connecting to APIC
73
        ApicUsername string `json:"apic-username,omitempty"`
74

75
        // The password for connecting to APIC
76
        ApicPassword string `json:"apic-password,omitempty"`
77

78
        // The number of seconds that APIC should wait before timing
79
        // out a subscription on a websocket connection. If not
80
        // explicitly set, then a default of 1800 seconds will
81
        // be sent in websocket subscriptions. If it is set to 0,
82
        // then a timeout will not be sent in websocket
83
        // subscriptions, and APIC will use it's default timeout
84
        // of 80 seconds. If set to a non-zero value, then the
85
        // timeout value will be provided when we subscribe to
86
        // a URL on APIC. NOTE: the subscription timeout is not
87
        // supported by APIC versions before 3.2(3), so this
88
        // value must not be set when used with APIC versions
89
        // older than that release.
90
        // Also, note that this is a string.
91
        ApicRefreshTimer string `json:"apic-refreshtime,omitempty"`
92

93
        // Interval in seconds between periodic check for a leaf reboot
94
        // Will be defaulted to 900s.
95
        LeafRebootCheckInterval int `json:"leaf-reboot-check-interval,omitempty"`
96

97
        // Delay in milliseconds after each subscription query
98
        // Will be defaulted to 100ms.
99
        ApicSubscriptionDelay int `json:"apic-subscription-delay,omitempty"`
100

101
        // How early (seconds) the subscriptions to be refreshed than
102
        // actual subscription refresh-timeout. Will be defaulted to 150Seconds.
103
        ApicRefreshTickerAdjust string `json:"apic-refreshticker-adjust,omitempty"`
104

105
        // A path for a PEM-encoded private key for client certificate
106
        // authentication for APIC API
107
        ApicPrivateKeyPath string `json:"apic-private-key-path,omitempty"`
108

109
        // A path for a PEM-encoded public certificate for APIC server to
110
        // enable secure TLS server verifification
111
        ApicCertPath string `json:"apic-cert-path,omitempty"`
112

113
        // The type of the ACI VMM domain: either "kubernetes",
114
        // "openshift"
115
        AciVmmDomainType string `json:"aci-vmm-type,omitempty"`
116

117
        // The name of the ACI VMM domain
118
        AciVmmDomain string `json:"aci-vmm-domain,omitempty"`
119

120
        // The name of the ACI VMM domain controller instance
121
        AciVmmController string `json:"aci-vmm-controller,omitempty"`
122

123
        // Name prefix to use when creating policy to avoid namespace
124
        // collisions
125
        AciPrefix string `json:"aci-prefix,omitempty"`
126

127
        // Tenant to use when creating policy objects in APIC
128
        AciPolicyTenant string `json:"aci-policy-tenant,omitempty"`
129

130
        // Physical domain used for service device clusters
131
        AciServicePhysDom string `json:"aci-service-phys-dom,omitempty"`
132

133
        // Encap used for service device clusters
134
        AciServiceEncap string `json:"aci-service-encap,omitempty"`
135

136
        // Time in seconds between service node ICMP probes for more
137
        // quickly removing failed nodes from service pools
138
        // 0 (default) means don't monitor
139
        AciServiceMonitorInterval int `json:"aci-service-monitor-interval,omitempty"`
140

141
        // Whether to enable PBR tracking for non-SNAT services
142
        // when AciServiceMonitorInterval is set to non-zero, PBR tracking
143
        // is enabled for snat
144
        AciPbrTrackingNonSnat bool `json:"aci-pbr-tracking-non-snat,omitempty"`
145

146
        // By default, the Resilient Hashing Enabled field of vnsSvcRedirectPol is
147
        // set to "yes". If DisableResilientHashing is true, it will be set to "no"
148
        DisableResilientHashing bool `json:"disable-resilient-hashing,omitempty"`
149

150
        // To ignore the opflexODev which belongs to different vmmDomain
151
        FilterOpflexDevice bool `json:"filter-opflex-device,omitempty"`
152

153
        // The tenants related to AciVrf where BDs/EPGs/Subnets could exist.
154
        // Usually AciVrfTenant and AciPolicyTenant
155
        AciVrfRelatedTenants []string `json:"aci-vrf-related-tenants,omitempty"`
156

157
        // ACI Pod-BD for this kubernetes instance
158
        AciPodBdDn string `json:"aci-podbd-dn,omitempty"`
159

160
        // ACI Node-BD for this kubernetes instance
161
        AciNodeBdDn string `json:"aci-nodebd-dn,omitempty"`
162

163
        // ACI VRF for this kubernetes instance
164
        AciVrf string `json:"aci-vrf,omitempty"`
165

166
        // ACI VRF for this kubernetes instance
167
        AciVrfDn string `json:"aci-vrf-dn,omitempty"`
168

169
        // Tenant containing the ACI VRF for this kubernetes instance
170
        AciVrfTenant string `json:"aci-vrf-tenant,omitempty"`
171

172
        // L3 out to use for services, service device clusters need to be
173
        // created in this tenant
174
        AciL3Out string `json:"aci-l3out,omitempty"`
175

176
        // L3 external networks (within the l3out) that will be able to
177
        // access the service IPs
178
        AciExtNetworks []string `json:"aci-ext-networks,omitempty"`
179

180
        // IP addresses used for pod network
181
        PodIpPool []ipam.IpRange `json:"pod-ip-pool,omitempty"`
182

183
        // The number of IP addresses to allocate when a pod starts to run low
184
        PodIpPoolChunkSize int `json:"pod-subnet-chunk-size,omitempty"`
185

186
        // Pod subnet CIDRs in the form <gateway-address>/<prefix-length> that
187
        // cover all pod-ip-pools
188
        PodSubnet []string `json:"pod-subnet,omitempty"`
189

190
        // Whether to allocate service IPs or to assume they will be
191
        // allocated by another controller
192
        AllocateServiceIps *bool `json:"allocate-service-ips,omitempty"`
193

194
        // IP addresses used for externally exposed load balanced services
195
        ServiceIpPool []ipam.IpRange `json:"service-ip-pool,omitempty"`
196

197
        // IP addresses that can be requested as static service IPs in
198
        // service spec
199
        StaticServiceIpPool []ipam.IpRange `json:"static-service-ip-pool,omitempty"`
200

201
        // IP addresses to use for node service endpoints
202
        NodeServiceIpPool []ipam.IpRange `json:"node-service-ip-pool,omitempty"`
203

204
        // a list of subnet/gateway CIDR addresses that cover the
205
        // addresses in the node service IP pool
206
        NodeServiceSubnets []string `json:"node-service-subnets,omitempty"`
207

208
        // default port range to use for SNAT svc graph filter
209
        SnatDefaultPortRangeStart int `json:"snat-default-port-range-start,omitempty"`
210
        SnatDefaultPortRangeEnd   int `json:"snat-default-port-range-end,omitempty"`
211

212
        // Contract scope used for SNAT svc graph
213
        SnatSvcContractScope string `json:"snat-contract-scope,omitempty"`
214

215
        // Maximum number of nodes permitted in a svc graph
216
        MaxSvcGraphNodes int `json:"max-nodes-svc-graph,omitempty"`
217

218
        // Disable routine to sync snatglobalinfo with nodeinfo
219
        // periodically
220
        DisablePeriodicSnatGlobalInfoSync bool `json:"disable-periodic-snat-global-info-sync,omitempty"`
221

222
        // True when we dont want to wait for service ep to be ready
223
        // before adding it to service graph
224
        // Default is false
225
        NoWaitForServiceEpReadiness bool `json:"no-wait-for-service-ep-readiness,omitempty"`
226

227
        ServiceGraphEndpointAddDelay serviceGraphEpAddDelay `json:"service-graph-endpoint-add-delay,omitempty"`
228
        // True when to add extern_dynamic and extern_static subnets to rdconfig
229
        // Default is false
230
        AddExternalSubnetsToRdconfig bool `json:"add-external-subnets-to-rdconfig,omitempty"`
231

232
        ExternStatic []string `json:"extern-static,omitempty"`
233

234
        ExternDynamic []string `json:"extern-dynamic,omitempty"`
235

236
        // Default is false
237
        HppOptimization bool `json:"hpp-optimization,omitempty"`
238

239
        // Default is false
240
        AciMultipod bool `json:"aci-multipod,omitempty"`
241

242
        // If true, enable opflex agent reconnect after vm migration
243
        // Default is false
244
        EnableOpflexAgentReconnect bool `json:"enable-opflex-agent-reconnect,omitempty"`
245

246
        // Timeout in seconds to wait for reconnect when opflexOdev is diconnected for a node
247
        // before triggering a dhcp release and renew of vlan interface
248
        // Applicable only for multipod case
249
        // default is 5s
250
        OpflexDeviceReconnectWaitTimeout int `json:"opflex-device-reconnect-wait-timeout,omitempty"`
251

252
        // Install Istio ControlPlane components
253
        InstallIstio bool `json:"install-istio,omitempty"`
254

255
        // enable EndpointSlice
256
        EnabledEndpointSlice bool `json:"enable_endpointslice,omitempty"`
257

258
        // Cluster Flavour
259
        Flavor string `json:"flavor,omitempty"`
260

261
        // Enable creation of VmmInjectedLabel, default is false
262
        EnableVmmInjectedLabels bool `json:"enable-vmm-injected-labels,omitempty"`
263

264
        // Timeout to delete old opflex devices
265
        OpflexDeviceDeleteTimeout float64 `json:"opflex-device-delete-timeout,omitempty"`
266

267
        // Configure sleep time for global SNAT sync
268
        SleepTimeSnatGlobalInfoSync int `json:"sleep-time-snat-global-info-sync,omitempty"`
269

270
        // Configure unkMacUcastAct attribute of service BD
271
        // The forwarding method for unknown layer 2 destinations
272
        UnknownMacUnicastAction string `json:"unknown-mac-unicast-action,omitempty"`
273

274
        // To disable service vlan preprovisioning on OpenShift on OpenStack Clusters
275
        // By default the feature will be enabled
276
        DisableServiceVlanPreprovisioning bool `json:"disable-service-vlan-preprovisioning"`
277

278
        // PhysDom for additional networks in chained mode
279
        AciPhysDom string `json:"aci-phys-dom,omitempty"`
280

281
        // L3Dom for additional networks in chained mode
282
        AciL3Dom string `json:"aci-l3-dom,omitempty"`
283

284
        // CNI is in chained mode
285
        ChainedMode bool `json:"chained-mode,omitempty"`
286

287
        // AEP for additional networks in chained mode
288
        AciAdditionalAep string `json:"aci-additional-aep,omitempty"`
289

290
        //User can provision Static Objects separately, so have a knob
291
        ReconcileStaticObjects bool `json:"reconcileStaticObjects,omitempty"`
292

293
        //In chained mode, global l2 port policy has been configured, so enable shared vlan pool
294
        AciUseGlobalScopeVlan bool `json:"aci-use-global-scope-vlan,omitempty"`
295

296
        //In chained mode, use system-id for auto-generated names
297
        AciUseSystemIdForSecondaryNames bool `json:"aci-use-system-id-for-secondary-names,omitempty"`
298

299
        // Metrics
300
        EnableMetrics bool `json:"enable-metrics,omitempty"`
301
        MetricsPort   int  `json:"metrics-port,omitempty"`
302

303
        // Labels to filter nodes from SNAT redirect policy
304
        NodeSnatRedirectExclude []NodeSnatRedirectExclude `json:"node-snat-redirect-exclude,omitempty"`
305

306
        AEP string `json:"aep,omitempty"`
307
        // Application Profile
308
        AppProfile string `json:"app-profile,omitempty"`
309

310
        // Add external contract to default epg (contract is created for LoadBalancer Service type), default is false
311
        AddExternalContractToDefaultEPG bool `json:"add-external-contract-to-default-epg,omitempty"`
312

313
        // Number of times the connection to APIC should be retried before switching to another APIC
314
        ApicConnectionRetryLimit int `json:"apic-connection-retry-limit,omitempty"`
315

316
        // Base delay in seconds for exponential backoff between APIC request retries
317
        ApicRequestRetryDelayBase int `json:"apic-request-retry-delay-base,omitempty"`
318

319
        // Enable retying request to APIC when a 503 error is encountered
320
        EnableApicRequestRetry bool `json:"enable-apic-request-retry-delay,omitempty"`
321

322
        // Disable hpp rendering if set to true
323
        DisableHppRendering bool `json:"disable-hpp-rendering,omitempty"`
324

325
        // Enable/disable making node unschedulable when it's not ready
326
        TaintNotReadyNode bool `json:"taint-not-ready-node,omitempty"`
327

328
        // Enable/disable local hpp distribution
329
        EnableHppDirect bool `json:"enable-hpp-direct,omitempty"`
330

331
        // Enable/disable proactive conf
332
        ProactiveConf bool `json:"proactive-conf,omitempty"`
333

334
        // Enable/disable aaep monitoring for vmm lite feature
335
        VmmLite bool `json:"aci-aaep-monitoring-enabled,omitempty"`
336

337
        // Name of linux-bridge for NAD creation in vmm lite feature
338
        BridgeName string `json:"bridge-name,omitempty"`
339

340
        // Optional fields for linux-bridge NAD creation in vmm lite feature
341
        IsGateway                 *bool                  `json:"isGateway,omitempty"`
342
        IsDefaultGateway          *bool                  `json:"isDefaultGateway,omitempty"`
343
        ForceAddress              *bool                  `json:"forceAddress,omitempty"`
344
        IpMasq                    *bool                  `json:"ipMasq,omitempty"`
345
        IpMasqBackend             string                 `json:"ipMasqBackend,omitempty"`
346
        Mtu                       *int                   `json:"mtu,omitempty"`
347
        HairpinMode               *bool                  `json:"hairpinMode,omitempty"`
348
        PromiscMode               *bool                  `json:"promiscMode,omitempty"`
349
        Enabledad                 *bool                  `json:"enabledad,omitempty"`
350
        Macspoofchk               *bool                  `json:"macspoofchk,omitempty"`
351
        DisableContainerInterface *bool                  `json:"disableContainerInterface,omitempty"`
352
        PortIsolation             *bool                  `json:"portIsolation,omitempty"`
353
        Ipam                      map[string]interface{} `json:"ipam,omitempty"`
354

355
        // Prefix for EPG annotation to identify CNO, default is "cno"
356
        CnoIdentifier string `json:"cno-identifier,omitempty"`
357

358
        KubeapiVlan int `json:"kubeapi-vlan,omitempty"`
359
}
360

361
type netIps struct {
362
        V4 *ipam.IpAlloc
363
        V6 *ipam.IpAlloc
364
}
365

366
func newNetIps() *netIps {
1✔
367
        return &netIps{
1✔
368
                V4: ipam.New(),
1✔
369
                V6: ipam.New(),
1✔
370
        }
1✔
371
}
1✔
372

373
func NewConfig() *ControllerConfig {
1✔
374
        t := true
1✔
375
        return &ControllerConfig{
1✔
376
                DefaultSg:          make([]OpflexGroup, 0),
1✔
377
                NamespaceDefaultEg: make(map[string]OpflexGroup),
1✔
378
                NamespaceDefaultSg: make(map[string][]OpflexGroup),
1✔
379
                AciVmmDomainType:   "Kubernetes",
1✔
380
                AciPolicyTenant:    "kubernetes",
1✔
381
                AciPrefix:          "kube",
1✔
382
                AllocateServiceIps: &t,
1✔
383
        }
1✔
384
}
1✔
385

386
func InitFlags(config *ControllerConfig) {
1✔
387
        flag.StringVar(&config.LogLevel, "log-level", "info", "Log level")
1✔
388

1✔
389
        flag.StringVar(&config.KubeConfig, "kube-config", "", "Absolute path to a kubeconfig file")
1✔
390

1✔
391
        flag.IntVar(&config.StatusPort, "status-port", 8091, " TCP port to run status server on (or 0 to disable)")
1✔
392
        flag.BoolVar(&config.EnableVmmInjectedLabels, "enable-vmm-injected-labels", false, "Enable creation of VmmInjectedLabel")
1✔
393
        flag.StringVar(&config.UnknownMacUnicastAction, "unkown-mac-unicast-action", "proxy", "Set the forwarding method for unknown mac for service BD")
1✔
394
        flag.BoolVar(&config.ChainedMode, "chained-mode", false, "CNI is in chained mode")
1✔
395
        flag.BoolVar(&config.VmmLite, "aci-aaep-monitoring-enabled", false, "Enables AAEP monitoring for VMM Lite mode")
1✔
396
        flag.BoolVar(&config.ReconcileStaticObjects, "reconcile-static-objects", false, "controller will reconcile implicit static objects")
1✔
397
        flag.BoolVar(&config.AciUseGlobalScopeVlan, "aci-use-global-scope-vlan", false, "Use global vlans for NADs in chained mode")
1✔
398
        flag.BoolVar(&config.AciUseSystemIdForSecondaryNames, "aci-use-system-id-for-secondary-names", false, "Use system id for auto-generated names in chained mode")
1✔
399
        flag.BoolVar(&config.EnableMetrics, "enable-metrics", false, "Enable metrics")
1✔
400
        flag.IntVar(&config.MetricsPort, "metrics-port", 8191, "Port to expose metrics on")
1✔
401
}
1✔
402

403
func (cont *AciController) loadIpRanges(v4, v6 *ipam.IpAlloc, ipranges []ipam.IpRange) {
1✔
404
        for _, r := range ipranges {
2✔
405
                if r.Start.To4() != nil && r.End.To4() != nil {
2✔
406
                        v4.AddRange(r.Start, r.End)
1✔
407
                } else if r.Start.To16() != nil && r.End.To16() != nil {
3✔
408
                        v6.AddRange(r.Start, r.End)
1✔
409
                } else {
1✔
UNCOV
410
                        cont.log.Warn("Range invalid: ", r)
×
UNCOV
411
                }
×
412
        }
413
}
414

415
func (cont *AciController) initIpam() {
1✔
416
        cont.loadIpRanges(cont.configuredPodNetworkIps.V4, cont.configuredPodNetworkIps.V6,
1✔
417
                cont.config.PodIpPool)
1✔
418
        cont.podNetworkIps.V4.AddAll(cont.configuredPodNetworkIps.V4)
1✔
419
        cont.podNetworkIps.V6.AddAll(cont.configuredPodNetworkIps.V6)
1✔
420
        cont.serviceIps.LoadRanges(cont.config.ServiceIpPool)
1✔
421
        cont.loadIpRanges(cont.staticServiceIps.V4, cont.staticServiceIps.V6,
1✔
422
                cont.config.StaticServiceIpPool)
1✔
423
        cont.loadIpRanges(cont.nodeServiceIps.V4, cont.nodeServiceIps.V6,
1✔
424
                cont.config.NodeServiceIpPool)
1✔
425
}
1✔
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