From 944efce3a68a39649f1276a553ad131dcc92f33b Mon Sep 17 00:00:00 2001 From: varsha teratipally Date: Fri, 13 Nov 2020 03:16:14 +0000 Subject: [PATCH 1/2] add code for retrieving kernel modules --- pkg/util/metrics/system/common.go | 38 +++++++++ pkg/util/metrics/system/module_stats.go | 85 ++++++++++++++++++++ pkg/util/metrics/system/module_stats_test.go | 44 ++++++++++ 3 files changed, 167 insertions(+) create mode 100644 pkg/util/metrics/system/common.go create mode 100644 pkg/util/metrics/system/module_stats.go create mode 100644 pkg/util/metrics/system/module_stats_test.go diff --git a/pkg/util/metrics/system/common.go b/pkg/util/metrics/system/common.go new file mode 100644 index 00000000..a2e31dc8 --- /dev/null +++ b/pkg/util/metrics/system/common.go @@ -0,0 +1,38 @@ +/* +Copyright 2020 The Kubernetes Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package system + +import ( + "bufio" + "os" +) + +// ReadFile reads contents from a file and returns lines. +func ReadFile(filename string) ([]string, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + defer file.Close() + + var result []string + s := bufio.NewScanner(file) + for s.Scan() { + result = append(result, s.Text()) + } + if s.Err() != nil { + return nil, err + } + return result, nil +} diff --git a/pkg/util/metrics/system/module_stats.go b/pkg/util/metrics/system/module_stats.go new file mode 100644 index 00000000..80a114fd --- /dev/null +++ b/pkg/util/metrics/system/module_stats.go @@ -0,0 +1,85 @@ +/* +Copyright 2020 The Kubernetes Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package system + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" +) + +type ModuleStat struct { + ModuleName string `json:"moduleName"` + Instances uint64 `json:"instances"` + Proprietary bool `json:"proprietary"` + OutOfTree bool `json:"outOfTree"` + Unsigned bool `json:"unsigned"` +} + +func (d ModuleStat) String() string { + s, _ := json.Marshal(d) + return string(s) +} + +// Module returns all the kernel modules and their +// usage. It is read from cat /proc/modules. +func Modules() ([]ModuleStat, error) { + filename := "/proc/modules" + lines, err := ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("Error reading the contents of %s: %s", filename, err) + } + var result = make([]ModuleStat, 0, len(lines)) + + /* a line of /proc/modules has the following structure + nf_nat 61440 2 xt_MASQUERADE,iptable_nat, Live 0x0000000000000000 (O) + (1) (2) (3) (4) (5) (6) (7) + (1) name of the module + (2) memory size of the module, in bytes + (3) instances of the module are currently loaded + (4) module dependencies + (5) load state of the module: live, loading or unloading + (6) memory offset for the loaded module. + (7) return a string to represent the kernel taint state. (used here: "P" - Proprietary, "O" - out of tree kernel module, "E" - unsigned module + */ + for _, line := range lines { + fields := strings.Fields(line) + moduleName := fields[0] // name of the module + numberOfInstances, err := + strconv.ParseUint((fields[1]), 10, 64) // instances of the module are currently loaded + if err != nil { + return nil, err + } + + var isProprietary = false + var isOutofTree = false + var isUnsigned = false + // if the len of the fields is greater than 6, then the kernel taint state is available. + if len(fields) > 6 { + isProprietary = strings.Contains(fields[6], "P") + isOutofTree = strings.Contains(fields[6], "O") + isUnsigned = strings.Contains(fields[6], "E") + } + var stats = ModuleStat{ + ModuleName: moduleName, + Instances: numberOfInstances, + Proprietary: isProprietary, + OutOfTree: isOutofTree, + Unsigned: isUnsigned, + } + result = append(result, stats) + } + return result, nil +} diff --git a/pkg/util/metrics/system/module_stats_test.go b/pkg/util/metrics/system/module_stats_test.go new file mode 100644 index 00000000..717c94d0 --- /dev/null +++ b/pkg/util/metrics/system/module_stats_test.go @@ -0,0 +1,44 @@ +/* +Copyright 2019 The Kubernetes Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package system + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestModules(t *testing.T) { + modules, err := Modules() + if err != nil { + t.Errorf("error %v", err) + } + if modules == nil { + t.Error("Error retrieving modules") + } +} + +func TestModuleStat_String(t *testing.T) { + v := ModuleStat{ + ModuleName: "test", + Instances: 2, + OutOfTree: false, + Unsigned: false, + } + e := `{"moduleName":"test","instances":2,"proprietary":false,"outOfTree":false,"unsigned":false}` + assert.Equal(t, + e, fmt.Sprintf("%v", v), "ModuleStat string is invalid: %v", v) + +} From 2b50e4af1ade60adaefa8a9883eefbf68d54c991 Mon Sep 17 00:00:00 2001 From: varsha teratipally Date: Thu, 19 Nov 2020 10:29:12 +0000 Subject: [PATCH 2/2] add testcases for cos and ubuntu to retrieve modules --- pkg/util/metrics/system/common.go | 2 +- pkg/util/metrics/system/module_stats.go | 42 ++++---- pkg/util/metrics/system/module_stats_test.go | 96 +++++++++++++++++-- .../metrics/system/testdata/modules_cos.txt | 4 + .../system/testdata/modules_ubuntu.txt | 4 + 5 files changed, 117 insertions(+), 31 deletions(-) create mode 100644 pkg/util/metrics/system/testdata/modules_cos.txt create mode 100644 pkg/util/metrics/system/testdata/modules_ubuntu.txt diff --git a/pkg/util/metrics/system/common.go b/pkg/util/metrics/system/common.go index a2e31dc8..77a6125d 100644 --- a/pkg/util/metrics/system/common.go +++ b/pkg/util/metrics/system/common.go @@ -19,7 +19,7 @@ import ( ) // ReadFile reads contents from a file and returns lines. -func ReadFile(filename string) ([]string, error) { +func ReadFileIntoLines(filename string) ([]string, error) { file, err := os.Open(filename) if err != nil { return nil, err diff --git a/pkg/util/metrics/system/module_stats.go b/pkg/util/metrics/system/module_stats.go index 80a114fd..5b5da12a 100644 --- a/pkg/util/metrics/system/module_stats.go +++ b/pkg/util/metrics/system/module_stats.go @@ -20,7 +20,9 @@ import ( "strings" ) -type ModuleStat struct { +var modulesFilePath = "/proc/modules" + +type Module struct { ModuleName string `json:"moduleName"` Instances uint64 `json:"instances"` Proprietary bool `json:"proprietary"` @@ -28,20 +30,19 @@ type ModuleStat struct { Unsigned bool `json:"unsigned"` } -func (d ModuleStat) String() string { +func (d Module) String() string { s, _ := json.Marshal(d) return string(s) } // Module returns all the kernel modules and their // usage. It is read from cat /proc/modules. -func Modules() ([]ModuleStat, error) { - filename := "/proc/modules" - lines, err := ReadFile(filename) +func Modules() ([]Module, error) { + lines, err := ReadFileIntoLines(modulesFilePath) if err != nil { - return nil, fmt.Errorf("Error reading the contents of %s: %s", filename, err) + return nil, fmt.Errorf("error reading the contents of %s: %s", modulesFilePath, err) } - var result = make([]ModuleStat, 0, len(lines)) + var result = make([]Module, 0, len(lines)) /* a line of /proc/modules has the following structure nf_nat 61440 2 xt_MASQUERADE,iptable_nat, Live 0x0000000000000000 (O) @@ -58,28 +59,23 @@ func Modules() ([]ModuleStat, error) { fields := strings.Fields(line) moduleName := fields[0] // name of the module numberOfInstances, err := - strconv.ParseUint((fields[1]), 10, 64) // instances of the module are currently loaded + strconv.ParseUint((fields[2]), 10, 64) // instances of the module are currently loaded if err != nil { - return nil, err + numberOfInstances = 0 } - var isProprietary = false - var isOutofTree = false - var isUnsigned = false + var module = Module{ + ModuleName: moduleName, + Instances: numberOfInstances, + } // if the len of the fields is greater than 6, then the kernel taint state is available. if len(fields) > 6 { - isProprietary = strings.Contains(fields[6], "P") - isOutofTree = strings.Contains(fields[6], "O") - isUnsigned = strings.Contains(fields[6], "E") + module.Proprietary = strings.Contains(fields[6], "P") + module.OutOfTree = strings.Contains(fields[6], "O") + module.Unsigned = strings.Contains(fields[6], "E") } - var stats = ModuleStat{ - ModuleName: moduleName, - Instances: numberOfInstances, - Proprietary: isProprietary, - OutOfTree: isOutofTree, - Unsigned: isUnsigned, - } - result = append(result, stats) + + result = append(result, module) } return result, nil } diff --git a/pkg/util/metrics/system/module_stats_test.go b/pkg/util/metrics/system/module_stats_test.go index 717c94d0..b80f0088 100644 --- a/pkg/util/metrics/system/module_stats_test.go +++ b/pkg/util/metrics/system/module_stats_test.go @@ -21,17 +21,99 @@ import ( ) func TestModules(t *testing.T) { - modules, err := Modules() - if err != nil { - t.Errorf("error %v", err) + testcases := []struct { + name string + fakeModuleFilePath string + expectedModules []Module + }{ + { + name: "default_cos", + fakeModuleFilePath: "testdata/modules_cos.txt", + expectedModules: []Module{ + { + ModuleName: "crypto_simd", + Instances: 0x1, + Proprietary: false, + OutOfTree: false, + Unsigned: false, + }, + { + ModuleName: "virtio_balloon", + Instances: 0x0, + Proprietary: false, + OutOfTree: false, + Unsigned: false, + }, + { + ModuleName: "cryptd", + Instances: 0x1, + Proprietary: false, + OutOfTree: false, + Unsigned: false, + }, + { + ModuleName: "loadpin_trigger", + Instances: 0x0, + Proprietary: false, + OutOfTree: true, + Unsigned: false, + }, + }, + }, + { + name: "default_ubuntu", + fakeModuleFilePath: "testdata/modules_ubuntu.txt", + expectedModules: []Module{ + { + ModuleName: "drm", + Instances: 0x0, + Proprietary: false, + OutOfTree: false, + Unsigned: false, + }, + { + ModuleName: "virtio_rng", + Instances: 0x0, + Proprietary: false, + OutOfTree: false, + Unsigned: false, + }, + { + ModuleName: "x_tables", + Instances: 0x1, + Proprietary: false, + OutOfTree: false, + Unsigned: false, + }, + { + ModuleName: "autofs4", + Instances: 0x2, + Proprietary: false, + OutOfTree: false, + Unsigned: false, + }, + }, + }, } - if modules == nil { - t.Error("Error retrieving modules") + for _, test := range testcases { + t.Run(test.name, func(t *testing.T) { + originalModuleFilePath := modulesFilePath + defer func() { + modulesFilePath = originalModuleFilePath + }() + + modulesFilePath = test.fakeModuleFilePath + modules, err := Modules() + if err != nil { + t.Errorf("Unexpected error retrieving modules: %v\nModulesFilePath: %s\n", err, modulesFilePath) + } + assert.Equal(t, modules, test.expectedModules, "unpected modules retrieved: %v, expected: %v", modules, test.expectedModules) + }) } } func TestModuleStat_String(t *testing.T) { - v := ModuleStat{ + v := Module{ ModuleName: "test", Instances: 2, OutOfTree: false, @@ -39,6 +121,6 @@ func TestModuleStat_String(t *testing.T) { } e := `{"moduleName":"test","instances":2,"proprietary":false,"outOfTree":false,"unsigned":false}` assert.Equal(t, - e, fmt.Sprintf("%v", v), "ModuleStat string is invalid: %v", v) + e, fmt.Sprintf("%v", v), "Module string is invalid: %v", v) } diff --git a/pkg/util/metrics/system/testdata/modules_cos.txt b/pkg/util/metrics/system/testdata/modules_cos.txt new file mode 100644 index 00000000..535ce478 --- /dev/null +++ b/pkg/util/metrics/system/testdata/modules_cos.txt @@ -0,0 +1,4 @@ +crypto_simd 16384 1 aesni_intel, Live 0x0000000000000000 +virtio_balloon 24576 0 - Live 0x0000000000000000 +cryptd 24576 1 crypto_simd, Live 0x0000000000000000 +loadpin_trigger 12288 0 [permanent], Live 0x0000000000000000 (O) \ No newline at end of file diff --git a/pkg/util/metrics/system/testdata/modules_ubuntu.txt b/pkg/util/metrics/system/testdata/modules_ubuntu.txt new file mode 100644 index 00000000..de05e3a7 --- /dev/null +++ b/pkg/util/metrics/system/testdata/modules_ubuntu.txt @@ -0,0 +1,4 @@ +drm 491520 0 - Live 0x0000000000000000 +virtio_rng 16384 0 - Live 0x0000000000000000 +x_tables 40960 1 ip_tables, Live 0x0000000000000000 +autofs4 45056 2 - Live 0x0000000000000000 \ No newline at end of file