diff --git a/nodes/srl/srl.go b/nodes/srl/srl.go index 1cd18e120..867dbc082 100644 --- a/nodes/srl/srl.go +++ b/nodes/srl/srl.go @@ -109,6 +109,12 @@ var ( srlCfgTpl, _ = template.New("srl-tls-profile"). Funcs(gomplate.CreateFuncs(context.Background(), new(data.Data))). Parse(srlConfigCmdsTpl) + + requiredKernelVersion = &utils.KernelVersion{ + Major: 4, + Minor: 10, + Revision: 0, + } ) // Register registers the node in the NodeRegistry. @@ -366,6 +372,31 @@ func (s *srl) Ready(ctx context.Context) error { } } +// checkKernelVersion emits a warning if the present kernel version is lower than the required one. +func (s *srl) checkKernelVersion() error { + // retrieve running kernel version + kv, err := utils.GetKernelVersion() + if err != nil { + return err + } + + // do the comparison + if !kv.GreaterOrEqual(requiredKernelVersion) { + log.Infof("Nokia SR Linux v23.3.1+ requires a kernel version greater than %s. Detected kernel version: %s", requiredKernelVersion, kv) + } + return nil +} + +func (s *srl) CheckDeploymentConditions(ctx context.Context) error { + // perform the srl specific kernel version check + err := s.checkKernelVersion() + if err != nil { + return err + } + + return s.DefaultNode.CheckDeploymentConditions(ctx) +} + func (s *srl) createSRLFiles() error { log.Debugf("Creating directory structure for SRL container: %s", s.Cfg.ShortName) var src string diff --git a/utils/file.go b/utils/file.go index ae7a3b169..daef9ade9 100644 --- a/utils/file.go +++ b/utils/file.go @@ -178,7 +178,8 @@ func ExpandHome(p string) string { userId, isSet := os.LookupEnv("SUDO_UID") if !isSet { - return curUserHomeDir + p = strings.Replace(p, "~", curUserHomeDir, 1) + return p } // lookup user to figure out Home Directory diff --git a/utils/kernel_module.go b/utils/kernel_module.go index 83b023c7c..5c36525eb 100644 --- a/utils/kernel_module.go +++ b/utils/kernel_module.go @@ -2,8 +2,13 @@ package utils import ( "bufio" + "fmt" "os" + "regexp" + "strconv" "strings" + + log "github.com/sirupsen/logrus" ) // IsKernelModuleLoaded checks if a kernel module is loaded by parsing /proc/modules file. @@ -21,3 +26,67 @@ func IsKernelModuleLoaded(name string) (bool, error) { } return false, f.Close() } + +const kernelOSReleasePath = "/proc/sys/kernel/osrelease" + +// GetKernelVersion returns the parsed OS kernel version. +func GetKernelVersion() (*KernelVersion, error) { + ver, err := os.ReadFile(kernelOSReleasePath) + if err != nil { + return nil, err + } + + log.Debugf("kernel version: %s", string(ver)) + + return parseKernelVersion(ver) +} + +// KernelVersion holds the parsed OS kernel version. +type KernelVersion struct { + Major int + Minor int + Revision int + Remainder string // the rest of the version string, e.g. "-amd64" +} + +func parseKernelVersion(v []byte) (*KernelVersion, error) { + // https: //regex101.com/r/cWqad0/1 + re := regexp.MustCompile(`(?P\d+)\.(?P\d+)\.(?P\d+)(?P.*)`) + + matches := re.FindSubmatch(v) + + if len(matches) > 0 { + kv := &KernelVersion{} + + kv.Major, _ = strconv.Atoi(string(matches[re.SubexpIndex("Major")])) + kv.Minor, _ = strconv.Atoi(string(matches[re.SubexpIndex("Minor")])) + kv.Revision, _ = strconv.Atoi(string(matches[re.SubexpIndex("Revision")])) + kv.Remainder = string(matches[re.SubexpIndex("Remainder")]) + + return kv, nil + } + + return nil, fmt.Errorf("failed to parse kernel version") +} + +// String returns the Kernel version as string. +func (kv *KernelVersion) String() string { + return fmt.Sprintf("%d.%d.%d", kv.Major, kv.Minor, kv.Revision) +} + +// GreaterOrEqual returns true if the Kernel version is greater or equal to the compared Kernel version. +func (kv *KernelVersion) GreaterOrEqual(cmpKv *KernelVersion) bool { + if kv.Major < cmpKv.Major { + return false + } + if kv.Minor < cmpKv.Minor { + return false + } + // this must be >= because we're implementing GreaterEqual + // and this is the last position + if kv.Revision < cmpKv.Revision { + return false + } + + return true +} diff --git a/utils/kernel_module_test.go b/utils/kernel_module_test.go index d24090994..a0584efe2 100644 --- a/utils/kernel_module_test.go +++ b/utils/kernel_module_test.go @@ -1,6 +1,10 @@ package utils -import "testing" +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) func TestIsKernelModuleLoaded(t *testing.T) { type args struct { @@ -42,3 +46,52 @@ func TestIsKernelModuleLoaded(t *testing.T) { }) } } + +func TestParseKernelVersion(t *testing.T) { + tests := []struct { + input []byte + expectErr bool + expected *KernelVersion + }{ + {[]byte("123.45.6789 example"), false, &KernelVersion{Major: 123, Minor: 45, Revision: 6789, Remainder: " example"}}, + {[]byte("1.2.3-abc"), false, &KernelVersion{Major: 1, Minor: 2, Revision: 3, Remainder: "-abc"}}, + {[]byte("invalid"), true, nil}, + {[]byte(""), true, nil}, + } + + for _, test := range tests { + result, err := parseKernelVersion(test.input) + + if (err != nil) != test.expectErr { + t.Errorf("Error expectation mismatch. Input: %s, Expected Error: %v, Actual Error: %v", test.input, test.expectErr, err) + } + + if d := cmp.Diff(result, test.expected); d != "" { + t.Errorf("parseKernelVersion got = %+v, want %+v; Diff: %s", result, test.expected, d) + } + } +} + +func TestKernelVersionGreaterOrEqual(t *testing.T) { + tests := []struct { + version *KernelVersion + compare *KernelVersion + expectResult bool + }{ + {&KernelVersion{Major: 1, Minor: 2, Revision: 3}, &KernelVersion{Major: 1, Minor: 2, Revision: 3}, true}, + {&KernelVersion{Major: 1, Minor: 2, Revision: 3}, &KernelVersion{Major: 1, Minor: 2, Revision: 2}, true}, + {&KernelVersion{Major: 1, Minor: 2, Revision: 3}, &KernelVersion{Major: 1, Minor: 3, Revision: 3}, false}, + {&KernelVersion{Major: 2, Minor: 3, Revision: 4}, &KernelVersion{Major: 1, Minor: 2, Revision: 3}, true}, + {&KernelVersion{Major: 2, Minor: 3, Revision: 4}, &KernelVersion{Major: 3, Minor: 4, Revision: 5}, false}, + {&KernelVersion{Major: 2, Minor: 3, Revision: 4}, &KernelVersion{Major: 2, Minor: 3, Revision: 5}, false}, + } + + for _, test := range tests { + result := test.version.GreaterOrEqual(test.compare) + + if result != test.expectResult { + t.Errorf("Result mismatch. Version: %+v, Compare: %+v, Expected: %v, Actual: %v", + test.version, test.compare, test.expectResult, result) + } + } +}