Skip to content

Commit 1ece975

Browse files
committed
Add collector to get pci and lsmod data
1 parent f6f45ef commit 1ece975

File tree

8 files changed

+364
-0
lines changed

8 files changed

+364
-0
lines changed

build/packaging/suseconnect-ng.spec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ Requires: ca-certificates
5656
%endif
5757

5858
Requires: coreutils
59+
Requires: pciutils
5960
Requires: zypper
6061
Requires: util-linux
6162
Recommends: systemd

cmd/suseconnect/suseconnect.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ func main() {
117117
flag.BoolVar(&info, "i", false, "")
118118

119119
flag.Parse()
120+
// when info set nothing sent to SCC,
121+
// in that case we don't want to update
122+
// contents of the data checksum files
123+
util.UpdateLargeDataIDs = !info
124+
120125
if version {
121126
fmt.Println(connect.GetShortenedVersion())
122127
os.Exit(0)

internal/collectors/lsmod.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package collectors
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"crypto/sha256"
7+
"encoding/hex"
8+
"os"
9+
"sort"
10+
"strings"
11+
12+
"github.com/SUSE/connect-ng/internal/util"
13+
)
14+
15+
type KernMod struct {
16+
KernModProfileID string // sha256 of KernModProfile
17+
KernModProfile []string // sorted unique list of modules from lsmod
18+
}
19+
type LSMOD struct{}
20+
const KernModChecksumFile = "/etc/zypp/chksum-kernmoddata.txt"
21+
22+
func getKernModChecksum (chkSumFilePath string) (string) {
23+
// before system registration chkSumFilePath won't exist
24+
// in that case just return emtpy string
25+
if !util.FileExists(chkSumFilePath) {
26+
return ""
27+
}
28+
// read value in from checksum file
29+
content, err := localOsReadfile(chkSumFilePath)
30+
// if read fails return empty string, should not happed
31+
if err != nil {
32+
return ""
33+
}
34+
return string(content)
35+
36+
}
37+
38+
func putKernModChecksum (chkSumFilePath string, value string) (error) {
39+
perms := os.FileMode(0644)
40+
err := os.WriteFile(chkSumFilePath, []byte(value), perms)
41+
return err
42+
}
43+
44+
45+
func (lsmod LSMOD) run(arch string) (Result, error) {
46+
modInfo, err := util.Execute([]string{"lsmod"}, nil)
47+
// Should not happen, but if command fails
48+
// send nil to SCC to indicate that module data was unavailable.
49+
if err != nil {
50+
return Result{"KernModData": nil}, err
51+
}
52+
scanner := bufio.NewScanner(bytes.NewReader(modInfo))
53+
54+
// Iterate through each line get module name
55+
modList := make(map[string]bool)
56+
for scanner.Scan() {
57+
module := strings.Fields(scanner.Text())[0]
58+
// skip "Mudule" from the hearder line
59+
if module != "Module" {
60+
modList[module] = true // update module list
61+
}
62+
}
63+
// Check for any errors during scanning
64+
if err := scanner.Err(); err != nil {
65+
return Result{"KernModData": nil}, err
66+
}
67+
68+
// run through mudual list removing duplicates
69+
var sortedMods []string
70+
for module := range modList {
71+
sortedMods = append(sortedMods, module)
72+
}
73+
74+
// sort the module list, , since the order does not matter
75+
// and may change over system reboots.
76+
sort.Strings(sortedMods)
77+
78+
// create comma seperated list of sorted modules
79+
output := strings.Join(sortedMods,",")
80+
81+
byteOutput := []byte(output)
82+
// get sha256 has a pci data
83+
mkSha256 := sha256.New()
84+
mkSha256.Write([]byte(byteOutput))
85+
sha256 := mkSha256.Sum(nil)
86+
modProfileId := hex.EncodeToString(sha256)
87+
88+
oldmodProfileId := getKernModChecksum (KernModChecksumFile)
89+
if oldmodProfileId != modProfileId {
90+
if util.UpdateLargeDataIDs {
91+
util.Debug.Print("updating: ", KernModChecksumFile)
92+
err = putPCIChecksum (KernModChecksumFile, modProfileId)
93+
if err != nil {
94+
return Result{"KernModData": nil}, err
95+
}
96+
}
97+
var dataBlob KernMod
98+
dataBlob.KernModProfileID = modProfileId
99+
dataBlob.KernModProfile = sortedMods
100+
return Result{"KernModData": dataBlob}, nil
101+
} else {
102+
var dataBlob KernMod
103+
dataBlob.KernModProfileID = modProfileId
104+
return Result{"KernModData": dataBlob}, nil
105+
}
106+
}

internal/collectors/lsmod_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package collectors
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
12+
func TestLSMODRunSuccess(t *testing.T) {
13+
assert := assert.New(t)
14+
15+
var dataBlob KernMod
16+
dataBlob.KernModProfileID = "0da8174a9f9c4a1f195d9af9804f24ba29e8431467e8203ae5663683292481e4"
17+
dataBlob.KernModProfile = []string{"acpi_tad", "autofs4", "ccp", "dmi_sysfs", "efi_pstore", "hid_generic", "i2c_algo_bit", "i2c_piix4", "i2c_smbus", "input_leds", "ip_tables", "joydev", "k10temp", "lp", "mac_hid", "mc", "msr", "nfnetlink", "nvme", "nvme_core", "parport", "parport_pc", "ppdev", "psmouse", "r8169", "rc_core", "realtek", "sch_fq_codel", "snd_pci_acp3x", "snd_soc_acpi", "soundcore", "usbhid", "x_tables"}
18+
19+
kernModTestData :=`rc_core 73728 1 cec
20+
snd_soc_acpi 16384 3 snd_sof_amd_acp,snd_acp_config,snd_pci_ps
21+
i2c_piix4 32768 0
22+
mc 81920 5 videodev,snd_usb_audio,videobuf2_v4l2,uvcvideo,videobuf2_common
23+
k10temp 16384 0
24+
i2c_algo_bit 16384 1 amdgpu
25+
i2c_smbus 20480 1 i2c_piix4
26+
soundcore 16384 1 snd
27+
ccp 155648 1 kvm_amd
28+
snd_pci_acp3x 16384 0
29+
input_leds 12288 0
30+
joydev 32768 0
31+
acpi_tad 20480 0
32+
mac_hid 12288 0
33+
sch_fq_codel 24576 4
34+
msr 12288 0
35+
parport_pc 53248 0
36+
ppdev 24576 0
37+
lp 28672 0
38+
parport 73728 3 parport_pc,lp,ppdev
39+
efi_pstore 12288 0
40+
nfnetlink 20480 5 nft_compat,nf_tables,ip_set
41+
dmi_sysfs 24576 0
42+
ip_tables 32768 0
43+
x_tables 65536 9 xt_conntrack,nft_compat,xt_tcpudp,xt_addrtype,xt_CHECKSUM,xt_set,ipt_REJECT,ip_tables,xt_MASQUERADE
44+
autofs4 57344 2
45+
hid_generic 12288 0
46+
usbhid 77824 0
47+
nvme 61440 2
48+
psmouse 217088 0
49+
r8169 126976 0
50+
nvme_core 225280 3 nvme
51+
realtek 49152 2
52+
`
53+
54+
mockUtilExecute(kernModTestData, nil)
55+
expected := Result{"KernModData": dataBlob}
56+
57+
collector := LSMOD{}
58+
result, err := collector.run(ARCHITECTURE_X86_64)
59+
60+
assert.Equal(expected, result)
61+
assert.Nil(err)
62+
}
63+
64+
func TestLSMODRunFail(t *testing.T) {
65+
assert := assert.New(t)
66+
67+
mockUtilExecute("", fmt.Errorf("forced error"))
68+
expected := Result{"KernModData": nil}
69+
70+
collector := LSMOD{}
71+
result, err := collector.run(ARCHITECTURE_X86_64)
72+
73+
assert.Equal(expected, result)
74+
assert.ErrorContains(err, "forced error")
75+
}
76+
77+
func TestKernModChkSum(t *testing.T) {
78+
assert := assert.New(t)
79+
80+
err := putKernModChecksum("/tmp/testPutCheckSum", "testthis")
81+
assert.Nil(err)
82+
content, err1 := os.ReadFile("/tmp/testPutCheckSum")
83+
assert.Nil(err1)
84+
assert.Equal("testthis", string(content))
85+
86+
}
87+
88+
func TestGetKernModChkSum(t *testing.T) {
89+
assert := assert.New(t)
90+
mockUtilFileExists(true)
91+
KernModChecksumFile := "/etc/zypp/chksum-kernmoddata.txt"
92+
mockLocalOsReadfile(t, KernModChecksumFile , "TestThis")
93+
94+
tstChkSum := getKernModChecksum(KernModChecksumFile)
95+
assert.Equal("TestThis", tstChkSum)
96+
}
97+
98+
func TestGetKernModChkSumNoFile(t *testing.T) {
99+
assert := assert.New(t)
100+
mockUtilFileExists(false)
101+
102+
tstChkSum := getKernModChecksum("/etc/zypp/chksum-kernmoddata.txt")
103+
assert.Equal("", tstChkSum)
104+
}

internal/collectors/pci.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package collectors
2+
3+
import (
4+
"crypto/sha256"
5+
"encoding/hex"
6+
"os"
7+
"github.com/SUSE/connect-ng/internal/util"
8+
)
9+
10+
//var InfoOnly bool // indicated that -i/--info was used
11+
const PCIChecksumFile = "/etc/zypp/chksum-pcidata.txt"
12+
13+
type PciProfile struct {
14+
PCIProfileID string // sha256 of PCIProfile
15+
PCIProfile string // output from "lspci -s .0"
16+
}
17+
type PCI struct{}
18+
19+
func getPCIChecksum (chkSumFilePath string) (string) {
20+
// before system registration chkSumFilePath won't exist
21+
// in that case just return emtpy string
22+
if !util.FileExists(chkSumFilePath) {
23+
return ""
24+
}
25+
26+
// read value in from checksum file
27+
content, err := localOsReadfile(chkSumFilePath)
28+
// if read fails return empty string, should not happed
29+
if err != nil {
30+
return ""
31+
}
32+
return string(content)
33+
}
34+
35+
36+
func putPCIChecksum (chkSumFilePath string, value string) (error) {
37+
perms := os.FileMode(0644)
38+
err := os.WriteFile(chkSumFilePath, []byte(value), perms)
39+
return err
40+
}
41+
42+
43+
func (pci PCI) run(arch string) (Result, error) {
44+
output, err := util.Execute([]string{"lspci", "-s", ".0"}, nil)
45+
// Some systems may not have lspci command, so
46+
// if the execute fails,
47+
// send nil to SCC to indicate that PCI data was unavailable.
48+
if err != nil {
49+
return Result{"PCIData": nil}, err
50+
}
51+
stringOutput := string(output)
52+
// get sha256 has a pci data
53+
mkSha256 := sha256.New()
54+
mkSha256.Write([]byte(output))
55+
sha256 := mkSha256.Sum(nil)
56+
hwIndex := hex.EncodeToString(sha256)
57+
58+
oldHwIndex := getPCIChecksum (PCIChecksumFile)
59+
if oldHwIndex != hwIndex {
60+
if util.UpdateLargeDataIDs {
61+
util.Debug.Print("updating: ", PCIChecksumFile)
62+
err = putPCIChecksum (PCIChecksumFile, hwIndex)
63+
if err != nil {
64+
return Result{"PCIData": nil}, err
65+
}
66+
}
67+
var dataBlob PciProfile
68+
dataBlob.PCIProfileID = hwIndex
69+
dataBlob.PCIProfile = stringOutput
70+
return Result{"PCIData": dataBlob}, nil
71+
} else {
72+
var dataBlob PciProfile
73+
dataBlob.PCIProfileID = hwIndex
74+
return Result{"PCIData": dataBlob}, nil
75+
}
76+
}

internal/collectors/pci_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package collectors
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
12+
func TestPCIRunSuccess(t *testing.T) {
13+
assert := assert.New(t)
14+
15+
var dataBlob PciProfile
16+
dataBlob.PCIProfileID = "de5b2af84042cbe8298be6864362a99bae3e087fea4efcdad9e33f58fa323185"
17+
dataBlob.PCIProfile = "00:00.0 Host bridge: Advanced Micro Devices, Inc. [AMD] Family 17h-19h PCIe Root Complex (rev 01)\n00:01.0 Host bridge: Advanced Micro Devices, Inc. [AMD] Family 17h-19h PCIe Dummy Host Bridge (rev 01)\n00:02.0 Host bridge: Advanced Micro Devices, Inc. [AMD] Family 17h-19h PCIe Dummy Host Bridge (rev 01)\n00:03.0 Host bridge: Advanced Micro Devices, Inc. [AMD] Family 17h-19h PCIe Dummy Host Bridge (rev 01)\n00:04.0 Host bridge: Advanced Micro Devices, Inc. [AMD] Family 17h-19h PCIe Dummy Host Bridge (rev 01)\n00:08.0 Host bridge: Advanced Micro Devices, Inc. [AMD] Family 17h-19h PCIe Dummy Host Bridge (rev 01)\n00:14.0 SMBus: Advanced Micro Devices, Inc. [AMD] FCH SMBus Controller (rev 71)\n00:18.0 Host bridge: Advanced Micro Devices, Inc. [AMD] Rembrandt Data Fabric: Device 18h; Function 0\n01:00.0 Non-Volatile memory controller: Micron/Crucial Technology Device 5426 (rev 01)\n02:00.0 Network controller: Intel Corporation Wi-Fi 6 AX200 (rev 1a)\n03:00.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL8111/8168/8211/8411 PCI Express Gigabit Ethernet Controller (rev 15)\n04:00.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL8111/8168/8211/8411 PCI Express Gigabit Ethernet Controller (rev 15)\n05:00.0 VGA compatible controller: Advanced Micro Devices, Inc. [AMD/ATI] Rembrandt [Radeon 680M] (rev c7)\n06:00.0 USB controller: Advanced Micro Devices, Inc. [AMD] Rembrandt USB4 XHCI controller #8"
18+
19+
mockUtilExecute(dataBlob.PCIProfile, nil)
20+
expected := Result{"PCIData": dataBlob}
21+
22+
collector := PCI{}
23+
result, err := collector.run(ARCHITECTURE_X86_64)
24+
25+
assert.Equal(expected, result)
26+
assert.Nil(err)
27+
}
28+
29+
func TestPCIRunFail(t *testing.T) {
30+
assert := assert.New(t)
31+
32+
mockUtilExecute("", fmt.Errorf("forced error"))
33+
expected := Result{"PCIData": nil}
34+
35+
collector := PCI{}
36+
result, err := collector.run(ARCHITECTURE_X86_64)
37+
38+
assert.Equal(expected, result)
39+
assert.ErrorContains(err, "forced error")
40+
}
41+
42+
43+
func TestPutPCIChkSum(t *testing.T) {
44+
assert := assert.New(t)
45+
46+
err := putPCIChecksum("/tmp/testPutCheckSum", "testthis")
47+
assert.Nil(err)
48+
content, err1 := os.ReadFile("/tmp/testPutCheckSum")
49+
assert.Nil(err1)
50+
assert.Equal("testthis", string(content))
51+
52+
}
53+
54+
func TestGetPCIChkSum(t *testing.T) {
55+
assert := assert.New(t)
56+
mockUtilFileExists(true)
57+
mockLocalOsReadfile(t, "/etc/zypp/chksum-pcidata.txt", "TestThis")
58+
59+
tstChkSum := getPCIChecksum("/etc/zypp/chksum-pcidata.txt")
60+
assert.Equal("TestThis", tstChkSum)
61+
}
62+
63+
func TestGetPCIChkSumNoFile(t *testing.T) {
64+
assert := assert.New(t)
65+
mockUtilFileExists(false)
66+
67+
tstChkSum := getPCIChecksum("/etc/zypp/chksum-pcidata.txt")
68+
assert.Equal("", tstChkSum)
69+
}

internal/connect/api.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,8 @@ var mandatoryCollectors = []collectors.Collector{
271271
collectors.CloudProvider{},
272272
collectors.Architecture{},
273273
collectors.ContainerRuntime{},
274+
collectors.PCI{},
275+
collectors.LSMOD{},
274276

275277
// Optional collectors
276278
collectors.Uname{},

internal/util/system.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const (
2727
)
2828

2929
var systemEcho bool
30+
var UpdateLargeDataIDs bool // set if -i/--info was used
3031

3132
// SetSystemEcho toggles piping of executed command's outputs to stdout/stderr
3233
// returns true if it was enabled before, false otherwise

0 commit comments

Comments
 (0)