11package commatrixcreator
22
33import (
4+ "context"
45 "fmt"
56 "io"
67 "os"
78 "path/filepath"
89 "slices"
10+ "strconv"
11+ "strings"
912
1013 log "github.com/sirupsen/logrus"
14+ corev1 "k8s.io/api/core/v1"
15+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1116
17+ "github.com/openshift-kni/commatrix/pkg/consts"
1218 "github.com/openshift-kni/commatrix/pkg/endpointslices"
1319 "github.com/openshift-kni/commatrix/pkg/mcp"
1420 "github.com/openshift-kni/commatrix/pkg/types"
21+ "github.com/openshift-kni/commatrix/pkg/utils"
1522 configv1 "github.com/openshift/api/config/v1"
23+ clientOptions "sigs.k8s.io/controller-runtime/pkg/client"
1624)
1725
1826type CommunicationMatrixCreator struct {
@@ -22,16 +30,18 @@ type CommunicationMatrixCreator struct {
2230 platformType configv1.PlatformType
2331 deployment types.Deployment
2432 ipv6Enabled bool
33+ utilsHelpers utils.UtilsInterface
2534}
2635
27- func New (exporter * endpointslices.EndpointSlicesExporter , customEntriesPath string , customEntriesFormat string , platformType configv1.PlatformType , deployment types.Deployment , ipv6Enabled bool ) (* CommunicationMatrixCreator , error ) {
36+ func New (exporter * endpointslices.EndpointSlicesExporter , customEntriesPath string , customEntriesFormat string , platformType configv1.PlatformType , deployment types.Deployment , ipv6Enabled bool , utilsHelpers utils. UtilsInterface ) (* CommunicationMatrixCreator , error ) {
2837 return & CommunicationMatrixCreator {
2938 exporter : exporter ,
3039 customEntriesPath : customEntriesPath ,
3140 customEntriesFormat : customEntriesFormat ,
3241 platformType : platformType ,
3342 deployment : deployment ,
3443 ipv6Enabled : ipv6Enabled ,
44+ utilsHelpers : utilsHelpers ,
3545 }, nil
3646}
3747
@@ -74,23 +84,33 @@ func (cm *CommunicationMatrixCreator) CreateEndpointMatrix() (*types.ComMatrix,
7484 staticEntries = expandStaticEntriesByPool (staticEntries , PoolRolesForStaticEntriesExpansion )
7585 epSliceComDetails = append (epSliceComDetails , staticEntries ... )
7686
87+ var customMatrix * types.ComMatrix
7788 if cm .customEntriesPath != "" {
7889 log .Debug ("Loading custom entries from file" )
79- customComDetails , err : = cm .GetComDetailsListFromFile ()
90+ customMatrix , err = cm .GetComMatrixFromFile ()
8091 if err != nil {
8192 log .Errorf ("Failed adding custom entries: %s" , err )
8293 return nil , fmt .Errorf ("failed adding custom entries: %s" , err )
8394 }
84- epSliceComDetails = append (epSliceComDetails , customComDetails ... )
95+ epSliceComDetails = append (epSliceComDetails , customMatrix . Matrix ... )
8596 }
8697
87- commMatrix := & types.ComMatrix {Matrix : epSliceComDetails }
98+ dynamicRanges , err := cm .getDynamicRanges ()
99+ if err != nil {
100+ log .Errorf ("Failed to get dynamic ranges: %v" , err )
101+ return nil , fmt .Errorf ("failed to get dynamic ranges: %w" , err )
102+ }
103+ if customMatrix != nil && len (customMatrix .DynamicRanges ) > 0 {
104+ dynamicRanges = append (dynamicRanges , customMatrix .DynamicRanges ... )
105+ }
106+
107+ commMatrix := & types.ComMatrix {Matrix : epSliceComDetails , DynamicRanges : dynamicRanges }
88108 log .Debug ("Sorting ComMatrix and removing duplicates" )
89109 commMatrix .SortAndRemoveDuplicates ()
90110 return commMatrix , nil
91111}
92112
93- func (cm * CommunicationMatrixCreator ) GetComDetailsListFromFile () ([] types.ComDetails , error ) {
113+ func (cm * CommunicationMatrixCreator ) GetComMatrixFromFile () (* types.ComMatrix , error ) {
94114 log .Debugf ("Opening file %s" , cm .customEntriesPath )
95115 f , err := os .Open (filepath .Clean (cm .customEntriesPath ))
96116 if err != nil {
@@ -107,7 +127,7 @@ func (cm *CommunicationMatrixCreator) GetComDetailsListFromFile() ([]types.ComDe
107127 }
108128
109129 log .Debugf ("Unmarshalling file content with format %s" , cm .customEntriesFormat )
110- res , err := types .ParseToComDetailsList (raw , cm .customEntriesFormat )
130+ res , err := types .ParseToComMatrix (raw , cm .customEntriesFormat )
111131 if err != nil {
112132 log .Errorf ("Failed to unmarshal %s file: %v" , cm .customEntriesFormat , err )
113133 return nil , fmt .Errorf ("failed to unmarshal custom entries file: %v" , err )
@@ -174,3 +194,150 @@ func expandStaticEntriesByPool(staticEntries []types.ComDetails, poolToRoles map
174194 }
175195 return out
176196}
197+
198+ func (cm * CommunicationMatrixCreator ) getDynamicRanges () ([]types.DynamicRange , error ) {
199+ log .Debug ("Getting dynamic ranges" )
200+ dynamicRanges := types .HostLevelServicesDynamicRange
201+
202+ nodePortDynamicRange , err := cm .getNodePortDynamicRange ()
203+ if err != nil {
204+ log .Errorf ("Failed to get node port dynamic range: %v" , err )
205+ return nil , fmt .Errorf ("failed to get node port dynamic range: %w" , err )
206+ }
207+ dynamicRanges = append (dynamicRanges , nodePortDynamicRange ... )
208+
209+ linuxDynamicPrivateRange , err := cm .getLinuxDynamicPrivateRange ()
210+ if err != nil {
211+ log .Errorf ("Failed to get Linux dynamic private range: %v" , err )
212+ return nil , fmt .Errorf ("failed to get Linux dynamic private range: %w" , err )
213+ }
214+ dynamicRanges = append (dynamicRanges , linuxDynamicPrivateRange ... )
215+
216+ return dynamicRanges , nil
217+ }
218+
219+ // GetNodePortDynamicRange returns the cluster's Service NodePort range as dynamic ranges.
220+ // If the cluster does not define a custom range, it falls back to the Kubernetes default (30000-32767).
221+ func (cm * CommunicationMatrixCreator ) getNodePortDynamicRange () ([]types.DynamicRange , error ) {
222+ log .Debug ("Getting node port dynamic range" )
223+ network := & configv1.Network {}
224+ if err := cm .exporter .Get (context .TODO (), clientOptions.ObjectKey {Name : "cluster" }, network ); err != nil {
225+ log .Errorf ("Failed to get Network config: %v" , err )
226+ return nil , fmt .Errorf ("failed to get Network config: %w" , err )
227+ }
228+
229+ dr := types .KubeletNodePortDefaultDynamicRange
230+ rangeStr := strings .TrimSpace (network .Spec .ServiceNodePortRange )
231+ if rangeStr == "" {
232+ log .Debug ("ServiceNodePortRange not set; using default" )
233+ return dr , nil
234+ }
235+
236+ minPort , maxPort , err := parsePortRange (rangeStr )
237+ if err != nil {
238+ log .Errorf ("Invalid ServiceNodePortRange format %q: %v" , rangeStr , err )
239+ return nil , fmt .Errorf ("invalid ServiceNodePortRange format %q: %w" , rangeStr , err )
240+ }
241+
242+ for i := range dr {
243+ dr [i ].MinPort = minPort
244+ dr [i ].MaxPort = maxPort
245+ }
246+ return dr , nil
247+ }
248+
249+ // getLinuxDynamicPrivateRange retrieves the Linux dynamic/private port range from a cluster node
250+ // by reading the host sysctl:
251+ // - /proc/sys/net/ipv4/ip_local_port_range
252+ func (cm * CommunicationMatrixCreator ) getLinuxDynamicPrivateRange () ([]types.DynamicRange , error ) {
253+ log .Debug ("Getting Linux dynamic/private port range from cluster" )
254+
255+ // Pick an arbitrary node to query (ranges are expected to be consistent across nodes).
256+ nodes , err := cm .exporter .CoreV1Interface .Nodes ().List (context .TODO (), metav1.ListOptions {})
257+ if err != nil {
258+ log .Errorf ("Failed to list nodes: %v" , err )
259+ return nil , fmt .Errorf ("failed to list nodes: %w" , err )
260+ }
261+ if len (nodes .Items ) == 0 {
262+ return nil , fmt .Errorf ("no nodes found in the cluster" )
263+ }
264+ nodeName := nodes .Items [0 ].Name
265+
266+ // Ensure namespace exists.
267+ if err := cm .utilsHelpers .CreateNamespace (consts .DefaultDebugNamespace ); err != nil {
268+ log .Errorf ("Failed to create debug namespace: %v" , err )
269+ return nil , fmt .Errorf ("failed to create debug namespace: %w" , err )
270+ }
271+ defer func () {
272+ if delErr := cm .utilsHelpers .DeleteNamespace (consts .DefaultDebugNamespace ); delErr != nil {
273+ log .Warnf ("failed to delete namespace %s: %v" , consts .DefaultDebugNamespace , delErr )
274+ }
275+ }()
276+
277+ // Create a privileged pod on the selected node.
278+ pod , err := cm .utilsHelpers .CreatePodOnNode (nodeName , consts .DefaultDebugNamespace , consts .DefaultDebugPodImage , []string {})
279+ if err != nil {
280+ log .Errorf ("Failed to create debug pod: %v" , err )
281+ return nil , fmt .Errorf ("failed to create debug pod: %w" , err )
282+ }
283+ defer func () {
284+ if delErr := cm .utilsHelpers .DeletePod (pod ); delErr != nil {
285+ log .Warnf ("failed to delete debug pod %s: %v" , pod .Name , delErr )
286+ }
287+ }()
288+
289+ // Wait for the pod to be running.
290+ if err := cm .utilsHelpers .WaitForPodStatus (consts .DefaultDebugNamespace , pod , corev1 .PodRunning ); err != nil {
291+ log .Errorf ("Debug pod did not reach Running state: %v" , err )
292+ return nil , fmt .Errorf ("debug pod did not reach Running state: %w" , err )
293+ }
294+
295+ // Read IPv4 range (applies to both IPv4 and IPv6 ephemeral ports).
296+ out , err := cm .utilsHelpers .RunCommandOnPod (pod , []string {"/bin/sh" , "-c" , "cat /host/proc/sys/net/ipv4/ip_local_port_range" })
297+ if err != nil {
298+ log .Errorf ("Failed to read IPv4 ip_local_port_range: %v" , err )
299+ return nil , fmt .Errorf ("failed to read IPv4 ip_local_port_range: %w" , err )
300+ }
301+ minPort , maxPort , err := parsePortRange (string (out ))
302+ if err != nil {
303+ log .Errorf ("Failed to parse IPv4 ip_local_port_range output: %v" , err )
304+ return nil , fmt .Errorf ("failed to parse IPv4 ip_local_port_range: %w" , err )
305+ }
306+
307+ return []types.DynamicRange {
308+ {
309+ Direction : "Ingress" ,
310+ Protocol : "TCP" ,
311+ MinPort : minPort ,
312+ MaxPort : maxPort ,
313+ Description : "Linux dynamic/private ports" ,
314+ Optional : true ,
315+ },
316+ {
317+ Direction : "Ingress" ,
318+ Protocol : "UDP" ,
319+ MinPort : minPort ,
320+ MaxPort : maxPort ,
321+ Description : "Linux dynamic/private ports" ,
322+ Optional : true ,
323+ },
324+ }, nil
325+ }
326+
327+ // parsePortRange parses "MIN MAX" or "MIN-MAX" formatted ranges.
328+ func parsePortRange (s string ) (int , int , error ) {
329+ normalized := strings .TrimSpace (strings .ReplaceAll (s , "-" , " " ))
330+ fields := strings .Fields (normalized )
331+ if len (fields ) != 2 {
332+ return 0 , 0 , fmt .Errorf ("unexpected format %q" , s )
333+ }
334+ minPort , err := strconv .Atoi (fields [0 ])
335+ if err != nil {
336+ return 0 , 0 , err
337+ }
338+ maxPort , err := strconv .Atoi (fields [1 ])
339+ if err != nil {
340+ return 0 , 0 , err
341+ }
342+ return minPort , maxPort , nil
343+ }
0 commit comments