Skip to content

Commit

Permalink
fix: move GetProcKernelConfigMap back to sys
Browse files Browse the repository at this point in the history
Signed-off-by: Raphanus Lo <[email protected]>
  • Loading branch information
COLDTURNIP committed Nov 29, 2024
1 parent 0c62595 commit 12acdb8
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 61 deletions.
19 changes: 9 additions & 10 deletions nfs/nfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,47 @@ package nfs
import (
"bufio"
"fmt"
"github.com/pkg/errors"
"os"
"path/filepath"
"strconv"
"strings"

"github.com/pkg/errors"
)

const (
nfsmountGlobalSection = "NFSMount_Global_Options"
nfsmountDefaultVersion = "Defaultvers"

defaultNFSVersionMajor = 4
defaultNFSVersionMinor = 0
)

// GetSystemDefaultNFSVersion reads the system default NFS version. This config can be overridden by nfsmount.conf under
// configPrefix. If configPrefix is empty, it will be /etc by default.
func GetSystemDefaultNFSVersion(configPrefix string) (major, minor int, err error) {
// configPrefix. If configPrefix is empty, it will be /etc by default. If the nfsmount.conf is absent, or the default
// version is not overridden in the configuration file, the returned version will fallback to defaultMajor and defaultMinor.
func GetSystemDefaultNFSVersion(configPrefix string, defaultMajor, defaultMinor int) (major, minor int, err error) {
if configPrefix == "" {
configPrefix = "/etc"
}

Check warning on line 25 in nfs/nfs.go

View check run for this annotation

Codecov / codecov/patch

nfs/nfs.go#L24-L25

Added lines #L24 - L25 were not covered by tests

configMap, err := getSystemNFSMountConfigMap(configPrefix)
if os.IsNotExist(err) {
return defaultNFSVersionMajor, defaultNFSVersionMinor, nil
return defaultMajor, defaultMinor, nil
} else if err != nil {
return 0, 0, errors.Wrap(err, "failed to read default NFS version from nfsmount.conf")
}

Check warning on line 32 in nfs/nfs.go

View check run for this annotation

Codecov / codecov/patch

nfs/nfs.go#L31-L32

Added lines #L31 - L32 were not covered by tests
if globalSection, ok := configMap[nfsmountGlobalSection]; ok {
if configured, ok := globalSection[nfsmountDefaultVersion]; ok {
majorStr, minorStr, _ := strings.Cut(configured, ".")
if major, err = strconv.Atoi(majorStr); err != nil {
return 0, 0, fmt.Errorf("invalid NFS Version '%s'", configured)
return 0, 0, fmt.Errorf("invalid NFS Version %q", configured)
} else if minorStr == "" {
minor = 0
} else if minor, err = strconv.Atoi(minorStr); err != nil {
return 0, 0, fmt.Errorf("invalid NFS Version '%s'", configured)
return 0, 0, fmt.Errorf("invalid NFS Version %q", configured)
}

Check warning on line 42 in nfs/nfs.go

View check run for this annotation

Codecov / codecov/patch

nfs/nfs.go#L41-L42

Added lines #L41 - L42 were not covered by tests
return major, minor, nil
}
}
return defaultNFSVersionMajor, defaultNFSVersionMinor, nil
return defaultMajor, defaultMinor, nil
}

// getSystemNFSMountConfigMap reads the nfsmount.conf under configPrefix. The returned result is in
Expand Down
50 changes: 34 additions & 16 deletions nfs/nfs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,70 +18,88 @@ type TestSuite struct{}
var _ = Suite(&TestSuite{})

func (s *TestSuite) TestGetSystemDefaultNFSVersion(c *C) {
genNFSMountConf := func(prefix string, nfsVer string) {
genNFSMountConf := func(dir string, nfsVer string) {
data := fmt.Sprintf("[ NFSMount_Global_Options ]\nDefaultvers=%s\n", nfsVer)
err := os.WriteFile(filepath.Join(prefix, "nfsmount.conf"), []byte(data), 0644)
err := os.WriteFile(filepath.Join(dir, "nfsmount.conf"), []byte(data), 0644)
c.Assert(err, IsNil)
}

type testCase struct {
setup func(prefix string)
setup func(dir string)
defaultMajor int
defaultMinor int
expectedMajor int
expectedMinor int
expectedError bool
}
testCases := map[string]testCase{
"GetSystemDefaultNFSVersion(...): system NFS mount config absent": {
setup: func(prefix string) {
_, err := os.Stat(filepath.Join(prefix, "nfsmount.conf"))
setup: func(dir string) {
_, err := os.Stat(filepath.Join(dir, "nfsmount.conf"))
c.Assert(os.IsNotExist(err), Equals, true)
},
expectedMajor: 4,
defaultMajor: 3,
defaultMinor: 0,
expectedMajor: 3,
expectedMinor: 0,
expectedError: false,
},
"GetSystemDefaultNFSVersion(...): system NFS mount config exist without default ver": {
setup: func(prefix string) {
setup: func(dir string) {
data := "[ NFSMount_Global_Options ]\notherkey=val\n"
err := os.WriteFile(filepath.Join(prefix, "nfsmount.conf"), []byte(data), 0644)
err := os.WriteFile(filepath.Join(dir, "nfsmount.conf"), []byte(data), 0644)
c.Assert(err, IsNil)
},
defaultMajor: 4,
defaultMinor: 1,
expectedMajor: 4,
expectedMinor: 0,
expectedMinor: 1,
expectedError: false,
},
"GetSystemDefaultNFSVersion(...): system NFS mount config set default ver to 3": {
setup: func(prefix string) { genNFSMountConf(prefix, "3") },
setup: func(dir string) { genNFSMountConf(dir, "3") },
defaultMajor: 4,
defaultMinor: 0,
expectedMajor: 3,
expectedMinor: 0,
expectedError: false,
},
"GetSystemDefaultNFSVersion(...): system NFS mount config set default ver to 4": {
setup: func(prefix string) { genNFSMountConf(prefix, "4") },
setup: func(dir string) { genNFSMountConf(dir, "4") },
defaultMajor: 4,
defaultMinor: 0,
expectedMajor: 4,
expectedMinor: 0,
expectedError: false,
},
"GetSystemDefaultNFSVersion(...): system NFS mount config set default ver to 4.0": {
setup: func(prefix string) { genNFSMountConf(prefix, "4.0") },
setup: func(dir string) { genNFSMountConf(dir, "4.0") },
defaultMajor: 4,
defaultMinor: 0,
expectedMajor: 4,
expectedMinor: 0,
expectedError: false,
},
"GetSystemDefaultNFSVersion(...): system NFS mount config set default ver to 4.2": {
setup: func(prefix string) { genNFSMountConf(prefix, "4.2") },
setup: func(dir string) { genNFSMountConf(dir, "4.2") },
defaultMajor: 4,
defaultMinor: 0,
expectedMajor: 4,
expectedMinor: 2,
expectedError: false,
},
"GetSystemDefaultNFSVersion(...): system NFS mount config default ver to invalid value": {
setup: func(prefix string) { genNFSMountConf(prefix, "???") },
setup: func(dir string) { genNFSMountConf(dir, "???") },
defaultMajor: 4,
defaultMinor: 0,
expectedMajor: 0,
expectedMinor: 0,
expectedError: true,
},
"GetSystemDefaultNFSVersion(...): system NFS mount config default ver to empty value": {
setup: func(prefix string) { genNFSMountConf(prefix, "") },
setup: func(dir string) { genNFSMountConf(dir, "") },
defaultMajor: 4,
defaultMinor: 0,
expectedMajor: 0,
expectedMinor: 0,
expectedError: true,
Expand All @@ -97,7 +115,7 @@ func (s *TestSuite) TestGetSystemDefaultNFSVersion(c *C) {
defer os.Remove(filepath.Join(configDir, "nfsmount.conf"))
testCase.setup(configDir)

major, minor, err := GetSystemDefaultNFSVersion(configDir)
major, minor, err := GetSystemDefaultNFSVersion(configDir, testCase.defaultMajor, testCase.defaultMinor)
c.Assert(major, Equals, testCase.expectedMajor, Commentf(test.ErrResultFmt, testName))
c.Assert(minor, Equals, testCase.expectedMinor, Commentf(test.ErrResultFmt, testName))
if testCase.expectedError {
Expand Down
23 changes: 0 additions & 23 deletions ns/sys.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,26 +97,3 @@ func GetSystemBlockDevices() (result map[string]types.BlockDeviceInfo, err error
}
return result, nil
}

// GetProcKernelConfigMap reads the kernel config into a key-value map. It tries to read kernel config from
// procfs mounted at procPrefix from the view of processName in namespace. If the procPrefix is empty, it points to /proc by default.
func GetProcKernelConfigMap(procPrefix, processName string, namespaces []types.Namespace) (configMap map[string]string, err error) {
defer func() {
err = errors.Wrap(err, "failed to get kernel config map")
}()

if procPrefix == "" {
procPrefix = "/proc/"
}

nsexec, err := NewNamespaceExecutor(processName, procPrefix, namespaces)
if err != nil {
return nil, err
}

configContent, err := nsexec.Execute(nil, "zcat", []string{"/proc/config.gz"}, types.ExecuteDefaultTimeout)
if err != nil {
return nil, err
}
return sys.ParseKernelModuleConfigMap(configContent)
}
55 changes: 43 additions & 12 deletions sys/sys.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package sys

import (
"bufio"
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
Expand All @@ -12,7 +14,7 @@ import (
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"

"github.com/longhorn/go-common-libs/io"
commonio "github.com/longhorn/go-common-libs/io"
"github.com/longhorn/go-common-libs/types"
)

Expand Down Expand Up @@ -131,29 +133,58 @@ func getSystemBlockDeviceInfo(sysClassBlockDirectory string, readDirFn func(stri
}

// GetBootKernelConfigMap reads the kernel config into a key-value map. It tries to read kernel config from
// ${bootPrefix}/config-${kernelVersion}, and comments are ignored. If the bootPrefix is empty, it points to /boot by
// default.
func GetBootKernelConfigMap(bootPrefix, kernelVersion string) (map[string]string, error) {
// ${bootDir}/config-${kernelVersion}, and comments are ignored. If the bootDir is empty, it points to /boot by default.
func GetBootKernelConfigMap(bootDir, kernelVersion string) (configMap map[string]string, err error) {
if kernelVersion == "" {
return nil, fmt.Errorf("kernelVersion cannot be empty")
}

Check warning on line 140 in sys/sys.go

View check run for this annotation

Codecov / codecov/patch

sys/sys.go#L139-L140

Added lines #L139 - L140 were not covered by tests
if bootPrefix == "" {
bootPrefix = "/boot/"
if bootDir == "" {
bootDir = types.SysBootDirectory
}

Check warning on line 143 in sys/sys.go

View check run for this annotation

Codecov / codecov/patch

sys/sys.go#L142-L143

Added lines #L142 - L143 were not covered by tests

defer func() {
err = errors.Wrap(err, "failed to get kernel config map")
}()

configPath := filepath.Join(bootDir, "config-"+kernelVersion)
configContent, err := commonio.ReadFileContent(configPath)
if err != nil {
return nil, err
}

Check warning on line 153 in sys/sys.go

View check run for this annotation

Codecov / codecov/patch

sys/sys.go#L152-L153

Added lines #L152 - L153 were not covered by tests
return parseKernelModuleConfigMap(strings.NewReader(configContent))
}

// GetProcKernelConfigMap reads the kernel config into a key-value map. It tries to read kernel config from
// procfs mounted at procDir from the view of processName in namespace. If the procDir is empty, it points to /proc by
// default.
func GetProcKernelConfigMap(procDir string) (configMap map[string]string, err error) {
if procDir == "" {
procDir = types.SysProcDirectory
}

Check warning on line 163 in sys/sys.go

View check run for this annotation

Codecov / codecov/patch

sys/sys.go#L162-L163

Added lines #L162 - L163 were not covered by tests

configPath := filepath.Join(bootPrefix, "config-"+kernelVersion)
configContent, err := io.ReadFileContent(configPath)
defer func() {
err = errors.Wrap(err, "failed to get kernel config map")
}()

configPath := filepath.Join(procDir, "config.gz")
configFile, err := os.Open(configPath)
if err != nil {
return nil, err
}

Check warning on line 173 in sys/sys.go

View check run for this annotation

Codecov / codecov/patch

sys/sys.go#L172-L173

Added lines #L172 - L173 were not covered by tests
defer configFile.Close()
gzReader, err := gzip.NewReader(configFile)
if err != nil {
return nil, err
}

Check warning on line 178 in sys/sys.go

View check run for this annotation

Codecov / codecov/patch

sys/sys.go#L177-L178

Added lines #L177 - L178 were not covered by tests
return ParseKernelModuleConfigMap(configContent)
defer gzReader.Close()
return parseKernelModuleConfigMap(gzReader)
}

// ParseKernelModuleConfigMap parses the kernel config into key-value map. All commented items will be ignored.
func ParseKernelModuleConfigMap(kernelConfigContent string) (map[string]string, error) {
// parseKernelModuleConfigMap parses the kernel config into key-value map. All commented items will be ignored.
func parseKernelModuleConfigMap(contentReader io.Reader) (map[string]string, error) {
configMap := map[string]string{}

scanner := bufio.NewScanner(strings.NewReader(kernelConfigContent))
scanner := bufio.NewScanner(contentReader)
for scanner.Scan() {
config := scanner.Text()
if !strings.HasPrefix(config, "CONFIG_") {
Expand Down
88 changes: 88 additions & 0 deletions sys/sys_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package sys

import (
"compress/gzip"
"fmt"
"io/fs"
"os"
"path/filepath"
"reflect"
"testing"

"github.com/longhorn/go-common-libs/io"
. "gopkg.in/check.v1"

"github.com/longhorn/go-common-libs/test"
Expand Down Expand Up @@ -198,3 +201,88 @@ CONFIG_NFS_V4_2=y`,
}
}
}
func (s *TestSuite) TestGetProcKernelConfigMap(c *C) {
genKernelConfig := func(dir string, lines ...string) {
path := filepath.Join(dir, "config.gz")
file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
c.Assert(err, IsNil)
defer file.Close()
gzWriter := gzip.NewWriter(file)
defer gzWriter.Close()
for _, line := range lines {
_, err := fmt.Fprintln(gzWriter, line)
c.Assert(err, IsNil)
}
}

type testCase struct {
setup func(dir string)
expectedConfigMap map[string]string
expectedConfigMapChecker func(map[string]string) bool
expectedError bool
}

testCases := map[string]testCase{
"GetProcKernelConfigMap(...): read kernel config": {
setup: func(dir string) {
genKernelConfig(dir,
"CONFIG_DM_CRYPT=y",
"# comment should be ignored",
"CONFIG_NFS_V4=m",
"CONFIG_NFS_V4_1=m",
"CONFIG_NFS_V4_2=y")
},
expectedConfigMap: map[string]string{
"CONFIG_DM_CRYPT": "y",
"CONFIG_NFS_V4": "m",
"CONFIG_NFS_V4_1": "m",
"CONFIG_NFS_V4_2": "y",
},
expectedError: false,
},
"GetProcKernelConfigMap(...): empty kernel config": {
setup: func(dir string) { genKernelConfig(dir) },
expectedConfigMap: map[string]string{},
expectedError: false,
},
"GetProcKernelConfigMap(...): invalid content": {
setup: func(dir string) { genKernelConfig(dir, "key=val\nCONFIG_invalid_content\n") },
expectedConfigMap: nil,
expectedError: true,
},
}

procDir := c.MkDir()

realConfigPath := filepath.Join(types.SysProcDirectory, "config.gz")
if _, err := os.ReadFile(realConfigPath); err == nil {
testCases["GetProcKernelConfigMap(...): real config"] = testCase{
setup: func(dir string) {
err := io.CopyFile(realConfigPath, filepath.Join(dir, "config.gz"), true)
c.Assert(err, IsNil)
},
expectedConfigMapChecker: func(cm map[string]string) bool {
return len(cm) > 0
},
expectedError: false,
}
}

for testName, testCase := range testCases {
c.Logf("testing utils.%v", testName)

testCase.setup(procDir)

exact, err := GetProcKernelConfigMap(procDir)
if testCase.expectedConfigMapChecker == nil {
c.Assert(exact, DeepEquals, testCase.expectedConfigMap, Commentf(test.ErrResultFmt, testName))
} else {
c.Assert(testCase.expectedConfigMapChecker(exact), Equals, true, Commentf(test.ErrResultFmt, testName))
}
if testCase.expectedError {
c.Assert(err, NotNil, Commentf(test.ErrErrorFmt, testName, err))
} else {
c.Assert(err, IsNil)
}
}
}
Loading

0 comments on commit 12acdb8

Please sign in to comment.