@@ -422,6 +422,15 @@ func getSecurityGroupName(service *corev1.Service) string {
422
422
return securityGroupName
423
423
}
424
424
425
+ func getSecurityGroupRules (client * gophercloud.ServiceClient , opts rules.ListOpts ) ([]rules.SecGroupRule , error ) {
426
+ mc := metrics .NewMetricContext ("security_group_rule" , "list" )
427
+ page , err := rules .List (client , opts ).AllPages ()
428
+ if mc .ObserveRequest (err ) != nil {
429
+ return nil , err
430
+ }
431
+ return rules .ExtractRules (page )
432
+ }
433
+
425
434
func getListenerProtocol (protocol corev1.Protocol , svcConf * serviceConfig ) listeners.Protocol {
426
435
// Make neutron-lbaas code work
427
436
if svcConf != nil {
@@ -2060,30 +2069,16 @@ func (lbaas *LbaasV2) listSubnetsForNetwork(networkID string, tweak ...TweakSubN
2060
2069
}
2061
2070
2062
2071
// group, if it not present.
2063
- func (lbaas * LbaasV2 ) ensureSecurityRule (
2064
- direction rules.RuleDirection ,
2065
- protocol rules.RuleProtocol ,
2066
- etherType rules.RuleEtherType ,
2067
- remoteIPPrefix , secGroupID string ,
2068
- portRangeMin , portRangeMax int ,
2069
- ) error {
2070
- sgRuleCreateOpts := rules.CreateOpts {
2071
- Direction : direction ,
2072
- Protocol : protocol ,
2073
- PortRangeMax : portRangeMin ,
2074
- PortRangeMin : portRangeMax ,
2075
- RemoteIPPrefix : remoteIPPrefix ,
2076
- SecGroupID : secGroupID ,
2077
- EtherType : etherType ,
2078
- }
2079
-
2072
+ func (lbaas * LbaasV2 ) ensureSecurityRule (sgRuleCreateOpts rules.CreateOpts ) error {
2080
2073
mc := metrics .NewMetricContext ("security_group_rule" , "create" )
2081
2074
_ , err := rules .Create (lbaas .network , sgRuleCreateOpts ).Extract ()
2082
2075
if err != nil && cpoerrors .IsConflictError (err ) {
2083
2076
// Conflict means the SG rule already exists, so ignoring that error.
2077
+ klog .Warningf ("Security group rule already found when trying to create it. This indicates concurrent " +
2078
+ "updates to the SG %s and is unexpected" , sgRuleCreateOpts .SecGroupID )
2084
2079
return mc .ObserveRequest (nil )
2085
2080
} else if mc .ObserveRequest (err ) != nil {
2086
- return fmt .Errorf ("failed to create rule for security group %s: %v" , secGroupID , err )
2081
+ return fmt .Errorf ("failed to create rule for security group %s: %v" , sgRuleCreateOpts . SecGroupID , err )
2087
2082
}
2088
2083
return nil
2089
2084
}
@@ -2174,6 +2169,50 @@ func (lbaas *LbaasV2) UpdateLoadBalancer(ctx context.Context, clusterName string
2174
2169
return mc .ObserveReconcile (err )
2175
2170
}
2176
2171
2172
+ func compareSecurityGroupRuleAndCreateOpts (rule rules.SecGroupRule , opts rules.CreateOpts ) bool {
2173
+ return rule .Direction == string (opts .Direction ) &&
2174
+ rule .Protocol == string (opts .Protocol ) &&
2175
+ rule .EtherType == string (opts .EtherType ) &&
2176
+ rule .RemoteIPPrefix == opts .RemoteIPPrefix &&
2177
+ rule .PortRangeMin == opts .PortRangeMin &&
2178
+ rule .PortRangeMax == opts .PortRangeMax
2179
+ }
2180
+
2181
+ func getRulesToCreateAndDelete (wantedRules []rules.CreateOpts , existingRules []rules.SecGroupRule ) ([]rules.CreateOpts , []rules.SecGroupRule ) {
2182
+ toCreate := make ([]rules.CreateOpts , 0 , len (wantedRules )) // Max is all rules need creation
2183
+ toDelete := make ([]rules.SecGroupRule , 0 , len (existingRules )) // Max will be all the existing rules to be deleted
2184
+ // Surely this can be done in a more efficient way. Is it worth optimizing if most of
2185
+ // the time we'll deal with just 1 or 2 elements in each array? I doubt it.
2186
+ for _ , existingRule := range existingRules {
2187
+ found := false
2188
+ for _ , wantedRule := range wantedRules {
2189
+ if compareSecurityGroupRuleAndCreateOpts (existingRule , wantedRule ) {
2190
+ found = true
2191
+ break
2192
+ }
2193
+ }
2194
+ if ! found {
2195
+ // in existingRules but not in wantedRules, delete
2196
+ toDelete = append (toDelete , existingRule )
2197
+ }
2198
+ }
2199
+ for _ , wantedRule := range wantedRules {
2200
+ found := false
2201
+ for _ , existingRule := range existingRules {
2202
+ if compareSecurityGroupRuleAndCreateOpts (existingRule , wantedRule ) {
2203
+ found = true
2204
+ break
2205
+ }
2206
+ }
2207
+ if ! found {
2208
+ // in wantedRules but not in exisitngRules, create
2209
+ toCreate = append (toCreate , wantedRule )
2210
+ }
2211
+ }
2212
+
2213
+ return toCreate , toDelete
2214
+ }
2215
+
2177
2216
// ensureAndUpdateOctaviaSecurityGroup handles the creation and update of the security group and the securiry rules for the octavia load balancer
2178
2217
func (lbaas * LbaasV2 ) ensureAndUpdateOctaviaSecurityGroup (clusterName string , apiService * corev1.Service , nodes []* corev1.Node , memberSubnetID string ) error {
2179
2218
// get service ports
@@ -2220,47 +2259,75 @@ func (lbaas *LbaasV2) ensureAndUpdateOctaviaSecurityGroup(clusterName string, ap
2220
2259
etherType = rules .EtherType6
2221
2260
}
2222
2261
2262
+ existingRules , err := getSecurityGroupRules (lbaas .network , rules.ListOpts {SecGroupID : lbSecGroupID })
2263
+ if err != nil {
2264
+ return fmt .Errorf (
2265
+ "failed to find security group rules in %s: %v" , lbSecGroupID , err )
2266
+ }
2267
+
2268
+ // List of the security group rules wanted in the SG.
2269
+ // Number of Ports plus the potential HealthCheckNodePort.
2270
+ wantedRules := make ([]rules.CreateOpts , 0 , len (ports )+ 1 )
2271
+
2223
2272
if apiService .Spec .HealthCheckNodePort != 0 {
2224
- err = lbaas .ensureSecurityRule (
2225
- rules .DirIngress ,
2226
- rules .ProtocolTCP ,
2227
- etherType ,
2228
- subnet .CIDR ,
2229
- lbSecGroupID ,
2230
- int (apiService .Spec .HealthCheckNodePort ),
2231
- int (apiService .Spec .HealthCheckNodePort ),
2273
+ wantedRules = append (wantedRules ,
2274
+ rules.CreateOpts {
2275
+ Direction : rules .DirIngress ,
2276
+ Protocol : rules .ProtocolTCP ,
2277
+ EtherType : etherType ,
2278
+ RemoteIPPrefix : subnet .CIDR ,
2279
+ SecGroupID : lbSecGroupID ,
2280
+ PortRangeMin : int (apiService .Spec .HealthCheckNodePort ),
2281
+ PortRangeMax : int (apiService .Spec .HealthCheckNodePort ),
2282
+ },
2232
2283
)
2233
- if err != nil {
2234
- return fmt .Errorf (
2235
- "failed to apply security rule for health check node port, %w" ,
2236
- err )
2237
- }
2238
2284
}
2239
2285
2240
- // ensure rules for node security group
2241
2286
for _ , port := range ports {
2242
2287
if port .NodePort == 0 { // It's 0 when AllocateLoadBalancerNodePorts=False
2243
2288
continue
2244
2289
}
2245
- err = lbaas .ensureSecurityRule (
2246
- rules .DirIngress ,
2247
- rules .RuleProtocol (port .Protocol ),
2248
- etherType ,
2249
- subnet .CIDR ,
2250
- lbSecGroupID ,
2251
- int (port .NodePort ),
2252
- int (port .NodePort ),
2290
+ wantedRules = append (wantedRules ,
2291
+ rules.CreateOpts {
2292
+ Direction : rules .DirIngress ,
2293
+ Protocol : rules .RuleProtocol (port .Protocol ),
2294
+ EtherType : etherType ,
2295
+ RemoteIPPrefix : subnet .CIDR ,
2296
+ SecGroupID : lbSecGroupID ,
2297
+ PortRangeMin : int (port .NodePort ),
2298
+ PortRangeMax : int (port .NodePort ),
2299
+ },
2253
2300
)
2301
+ }
2302
+
2303
+ toCreate , toDelete := getRulesToCreateAndDelete (wantedRules , existingRules )
2304
+
2305
+ // create new rules
2306
+ for _ , opts := range toCreate {
2307
+ err := lbaas .ensureSecurityRule (opts )
2254
2308
if err != nil {
2255
- return fmt .Errorf (
2256
- "failed to apply security rule for port %d, %w" ,
2257
- port .NodePort , err )
2309
+ return fmt .Errorf ("failed to apply security rule (%v), %w" , opts , err )
2258
2310
}
2311
+ }
2259
2312
2260
- if err := applyNodeSecurityGroupIDForLB (lbaas .compute , lbaas .network , nodes , lbSecGroupID ); err != nil {
2261
- return err
2313
+ // delete unneeded rules
2314
+ for _ , existingRule := range toDelete {
2315
+ klog .Infof ("Deleting rule %s from security group %s (%s)" , existingRule .ID , existingRule .SecGroupID , lbSecGroupName )
2316
+ mc := metrics .NewMetricContext ("security_group_rule" , "delete" )
2317
+ err := rules .Delete (lbaas .network , existingRule .ID ).ExtractErr ()
2318
+ if err != nil && cpoerrors .IsNotFound (err ) {
2319
+ // ignore 404
2320
+ klog .Warningf ("Security group rule %s found missing when trying to delete it. This indicates concurrent " +
2321
+ "updates to the SG %s and is unexpected" , existingRule .ID , existingRule .SecGroupID )
2322
+ return mc .ObserveRequest (nil )
2323
+ } else if mc .ObserveRequest (err ) != nil {
2324
+ return fmt .Errorf ("failed to delete security group rule %s: %w" , existingRule .ID , err )
2262
2325
}
2263
2326
}
2327
+
2328
+ if err := applyNodeSecurityGroupIDForLB (lbaas .compute , lbaas .network , nodes , lbSecGroupID ); err != nil {
2329
+ return err
2330
+ }
2264
2331
return nil
2265
2332
}
2266
2333
0 commit comments