@@ -154,54 +154,150 @@ func (c *client) CreateIsolatedNetwork(fd *infrav1.CloudStackFailureDomain, isoN
154154
155155// OpenFirewallRules opens a CloudStack egress firewall for an isolated network.
156156func (c * client ) OpenFirewallRules (isoNet * infrav1.CloudStackIsolatedNetwork ) (retErr error ) {
157+ // Early return if VPC is present
157158 if isoNet .Spec .VPC != nil && isoNet .Spec .VPC .ID != "" {
158159 return nil
159160 }
160161
161- // If network's egress policy is true, then we don't need to open the firewall rules for all protocols
162- network , count , err := c .cs . Network . GetNetworkByID (isoNet .Spec .ID , cloudstack . WithProject ( c . user . Project . ID ) )
162+ // Get network details
163+ network , err := c .getNetwork (isoNet .Spec .ID )
163164 if err != nil {
164- return errors .Wrapf (err , "failed to get network by ID %s" , isoNet .Spec .ID )
165- }
166- if count == 0 {
167- return errors .Errorf ("no network found with ID %s" , isoNet .Spec .ID )
165+ return err
168166 }
167+
168+ // Early return if egress default policy is true
169169 if network .Egressdefaultpolicy {
170+ isoNet .Status .FirewallRulesOpened = true
170171 return nil
171172 }
172173
174+ // Check if all required firewall rules exist
175+ allRulesPresent , err := c .checkFirewallRules (isoNet )
176+ if err != nil {
177+ return err
178+ }
179+ if allRulesPresent {
180+ isoNet .Status .FirewallRulesOpened = true
181+ return nil
182+ }
183+
184+ // Reset status if rules are missing
185+ isoNet .Status .FirewallRulesOpened = false
186+
187+ // Create firewall rules for each protocol
173188 protocols := []string {NetworkProtocolTCP , NetworkProtocolUDP , NetworkProtocolICMP }
174189 for _ , proto := range protocols {
175- var err error
176- if isoNet .Status .RoutingMode != "" {
177- p := c .cs .Firewall .NewCreateRoutingFirewallRuleParams (isoNet .Spec .ID , proto )
178- if proto == "icmp" {
179- p .SetIcmptype (- 1 )
180- p .SetIcmpcode (- 1 )
181- }
182- _ , err = c .cs .Firewall .CreateRoutingFirewallRule (p )
183- } else {
184- p := c .cs .Firewall .NewCreateEgressFirewallRuleParams (isoNet .Spec .ID , proto )
190+ if err := c .createFirewallRule (isoNet , proto ); err != nil {
191+ retErr = err
192+ }
193+ }
194+
195+ // Mark firewall rules as opened if no errors occurred
196+ if retErr == nil {
197+ isoNet .Status .FirewallRulesOpened = true
198+ }
199+
200+ c .customMetrics .EvaluateErrorAndIncrementAcsReconciliationErrorCounter (retErr )
201+ return retErr
202+ }
203+
204+ // Helper function to check if all required firewall rules exist
205+ func (c * client ) checkFirewallRules (isoNet * infrav1.CloudStackIsolatedNetwork ) (bool , error ) {
206+ protocols := []string {NetworkProtocolTCP , NetworkProtocolUDP , NetworkProtocolICMP }
207+ p := c .cs .Firewall .NewListEgressFirewallRulesParams ()
208+ p .SetNetworkid (isoNet .Spec .ID )
209+ setIfNotEmpty (c .user .Project .ID , p .SetProjectid )
210+
211+ rules , err := c .cs .Firewall .ListEgressFirewallRules (p )
212+ if err != nil {
213+ return false , errors .Wrapf (err , "failed to list egress firewall rules for network ID %s" , isoNet .Spec .ID )
214+ }
185215
186- if proto == "icmp" {
187- p .SetIcmptype (- 1 )
188- p .SetIcmpcode (- 1 )
216+ // Create a map to track found protocols
217+ foundProtocols := make (map [string ]bool )
218+ for _ , proto := range protocols {
219+ foundProtocols [proto ] = false
220+ }
221+
222+ // Check each rule for matching protocol and parameters
223+ for _ , rule := range rules .EgressFirewallRules {
224+ if _ , exists := foundProtocols [rule .Protocol ]; exists {
225+ if rule .Protocol == "icmp" {
226+ // For ICMP, ensure icmptype and icmpcode are -1
227+ if rule .Icmptype == - 1 && rule .Icmpcode == - 1 {
228+ foundProtocols [rule .Protocol ] = true
229+ }
230+ } else {
231+ // For TCP/UDP, ensure no specific ports are set (all ports)
232+ if rule .Startport == 0 && rule .Endport == 0 {
233+ foundProtocols [rule .Protocol ] = true
234+ }
189235 }
236+ }
237+ }
190238
191- _ , err = c .cs .Firewall .CreateEgressFirewallRule (p )
239+ // Return true only if all required protocols are found
240+ for _ , proto := range protocols {
241+ if ! foundProtocols [proto ] {
242+ return false , nil
192243 }
193- if err != nil &&
194- // Ignore errors regarding already existing fw rules for TCP/UDP for non-dynamic routing mode
195- ! strings .Contains (strings .ToLower (err .Error ()), "there is already" ) &&
196- ! strings .Contains (strings .ToLower (err .Error ()), "conflicts with rule" ) &&
197- // Ignore errors regarding already existing fw rule for ICMP
198- ! strings .Contains (strings .ToLower (err .Error ()), "new rule conflicts with existing rule" ) {
199- retErr = errors .Wrapf (
200- err , "failed creating egress firewall rule for network ID %s protocol %s" , isoNet .Spec .ID , proto )
244+ }
245+ return true , nil
246+ }
247+
248+ // Helper function to get network details
249+ func (c * client ) getNetwork (networkID string ) (* cloudstack.Network , error ) {
250+ network , count , err := c .cs .Network .GetNetworkByID (networkID , cloudstack .WithProject (c .user .Project .ID ))
251+ if err != nil {
252+ return nil , errors .Wrapf (err , "failed to get network by ID %s" , networkID )
253+ }
254+ if count == 0 {
255+ return nil , errors .Errorf ("no network found with ID %s" , networkID )
256+ }
257+ return network , nil
258+ }
259+
260+ // Helper function to create a firewall rule for a given protocol
261+ func (c * client ) createFirewallRule (isoNet * infrav1.CloudStackIsolatedNetwork , proto string ) error {
262+ var p interface {}
263+ if isoNet .Status .RoutingMode != "" {
264+ p = c .cs .Firewall .NewCreateRoutingFirewallRuleParams (isoNet .Spec .ID , proto )
265+ } else {
266+ p = c .cs .Firewall .NewCreateEgressFirewallRuleParams (isoNet .Spec .ID , proto )
267+ }
268+
269+ // Set ICMP-specific parameters
270+ if proto == "icmp" {
271+ if routingP , ok := p .(* cloudstack.CreateRoutingFirewallRuleParams ); ok {
272+ routingP .SetIcmptype (- 1 )
273+ routingP .SetIcmpcode (- 1 )
274+ } else if egressP , ok := p .(* cloudstack.CreateEgressFirewallRuleParams ); ok {
275+ egressP .SetIcmptype (- 1 )
276+ egressP .SetIcmpcode (- 1 )
201277 }
202278 }
203- c .customMetrics .EvaluateErrorAndIncrementAcsReconciliationErrorCounter (retErr )
204- return retErr
279+
280+ // Create the firewall rule
281+ var err error
282+ if routingP , ok := p .(* cloudstack.CreateRoutingFirewallRuleParams ); ok {
283+ _ , err = c .cs .Firewall .CreateRoutingFirewallRule (routingP )
284+ } else if egressP , ok := p .(* cloudstack.CreateEgressFirewallRuleParams ); ok {
285+ _ , err = c .cs .Firewall .CreateEgressFirewallRule (egressP )
286+ }
287+
288+ // Ignore specific errors
289+ if err != nil && ! c .isIgnorableError (err ) {
290+ return errors .Wrapf (err , "failed creating firewall rule for network ID %s protocol %s" , isoNet .Spec .ID , proto )
291+ }
292+ return nil
293+ }
294+
295+ // Helper function to check if an error is ignorable
296+ func (c * client ) isIgnorableError (err error ) bool {
297+ errorMsg := strings .ToLower (err .Error ())
298+ return strings .Contains (errorMsg , "there is already" ) ||
299+ strings .Contains (errorMsg , "conflicts with rule" ) ||
300+ strings .Contains (errorMsg , "new rule conflicts with existing rule" )
205301}
206302
207303// GetPublicIP gets a public IP with ID for cluster endpoint.
0 commit comments