Skip to content

Commit b8e8271

Browse files
committed
Add collector to get pci and lsmod data
1 parent d8b5d65 commit b8e8271

File tree

9 files changed

+318
-0
lines changed

9 files changed

+318
-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
@@ -118,6 +118,11 @@ func main() {
118118
flag.BoolVar(&info, "i", false, "")
119119

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

internal/collectors/lsmod.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package collectors
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"crypto/sha256"
7+
"encoding/hex"
8+
"sort"
9+
"strings"
10+
11+
"github.com/SUSE/connect-ng/internal/util"
12+
)
13+
14+
type LSMOD struct{}
15+
16+
const KernModChecksumFile = "/etc/zypp/chksum-kernmoddata.txt"
17+
const lsmodTag = "lsmod_data"
18+
19+
func (lsmod LSMOD) run(arch string) (Result, error) {
20+
modInfo, err := util.Execute([]string{"lsmod"}, nil)
21+
// Should not happen, but if command fails
22+
// send nil to SCC to indicate that module data was unavailable.
23+
if err != nil {
24+
return Result{lsmodTag: nil}, err
25+
}
26+
scanner := bufio.NewScanner(bytes.NewReader(modInfo))
27+
28+
// Iterate through each line get module name
29+
modList := make(map[string]bool)
30+
for scanner.Scan() {
31+
module := strings.Fields(scanner.Text())[0]
32+
// skip "Mudule" from the hearder line
33+
if module != "Module" {
34+
modList[module] = true // update module list
35+
}
36+
}
37+
// Check for any errors during scanning
38+
if err := scanner.Err(); err != nil {
39+
return Result{lsmodTag: nil}, err
40+
}
41+
42+
// run through mudual list removing duplicates
43+
var sortedMods []string
44+
for module := range modList {
45+
sortedMods = append(sortedMods, module)
46+
}
47+
48+
// sort the module list, , since the order does not matter
49+
// and may change over system reboots.
50+
sort.Strings(sortedMods)
51+
52+
// create new line seperated list of sorted modules
53+
output := strings.Join(sortedMods, "\n")
54+
55+
byteOutput := []byte(output)
56+
// get sha256 has a pci data
57+
mkSha256 := sha256.New()
58+
mkSha256.Write([]byte(byteOutput))
59+
sha256 := mkSha256.Sum(nil)
60+
modProfileId := hex.EncodeToString(sha256)
61+
62+
oldmodProfileId := util.GetChecksum(KernModChecksumFile)
63+
if oldmodProfileId != modProfileId {
64+
if util.UpdateLargeDataIDs {
65+
util.Debug.Print("updating: ", KernModChecksumFile)
66+
err = util.PutChecksum(KernModChecksumFile, modProfileId)
67+
if err != nil {
68+
return Result{lsmodTag: nil}, err
69+
}
70+
}
71+
var dataBlob util.Profile
72+
dataBlob.ProfileID = modProfileId
73+
dataBlob.ProfileData = sortedMods
74+
return Result{lsmodTag: dataBlob}, nil
75+
} else {
76+
var dataBlob util.Profile
77+
dataBlob.ProfileID = modProfileId
78+
return Result{lsmodTag: dataBlob}, nil
79+
}
80+
}

internal/collectors/lsmod_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package collectors
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/SUSE/connect-ng/internal/util"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestLSMODRunSuccess(t *testing.T) {
12+
assert := assert.New(t)
13+
14+
var dataBlob util.Profile
15+
dataBlob.ProfileID = "a27b6fd9514de9b56319922c0af28783ceec4d36230e16a32d35b060fa733c33"
16+
dataBlob.ProfileData = []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"}
17+
18+
kernModTestData := `rc_core 73728 1 cec
19+
snd_soc_acpi 16384 3 snd_sof_amd_acp,snd_acp_config,snd_pci_ps
20+
i2c_piix4 32768 0
21+
mc 81920 5 videodev,snd_usb_audio,videobuf2_v4l2,uvcvideo,videobuf2_common
22+
k10temp 16384 0
23+
i2c_algo_bit 16384 1 amdgpu
24+
i2c_smbus 20480 1 i2c_piix4
25+
soundcore 16384 1 snd
26+
ccp 155648 1 kvm_amd
27+
snd_pci_acp3x 16384 0
28+
input_leds 12288 0
29+
joydev 32768 0
30+
acpi_tad 20480 0
31+
mac_hid 12288 0
32+
sch_fq_codel 24576 4
33+
msr 12288 0
34+
parport_pc 53248 0
35+
ppdev 24576 0
36+
lp 28672 0
37+
parport 73728 3 parport_pc,lp,ppdev
38+
efi_pstore 12288 0
39+
nfnetlink 20480 5 nft_compat,nf_tables,ip_set
40+
dmi_sysfs 24576 0
41+
ip_tables 32768 0
42+
x_tables 65536 9 xt_conntrack,nft_compat,xt_tcpudp,xt_addrtype,xt_CHECKSUM,xt_set,ipt_REJECT,ip_tables,xt_MASQUERADE
43+
autofs4 57344 2
44+
hid_generic 12288 0
45+
usbhid 77824 0
46+
nvme 61440 2
47+
psmouse 217088 0
48+
r8169 126976 0
49+
nvme_core 225280 3 nvme
50+
realtek 49152 2
51+
`
52+
53+
mockUtilExecute(kernModTestData, nil)
54+
expected := Result{"lsmod_data": dataBlob}
55+
56+
collector := LSMOD{}
57+
result, err := collector.run(ARCHITECTURE_X86_64)
58+
59+
assert.Equal(expected, result)
60+
assert.Nil(err)
61+
}
62+
63+
func TestLSMODRunFail(t *testing.T) {
64+
assert := assert.New(t)
65+
66+
mockUtilExecute("", fmt.Errorf("forced error"))
67+
expected := Result{"lsmod_data": nil}
68+
69+
collector := LSMOD{}
70+
result, err := collector.run(ARCHITECTURE_X86_64)
71+
72+
assert.Equal(expected, result)
73+
assert.ErrorContains(err, "forced error")
74+
}

internal/collectors/pci.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package collectors
2+
3+
import (
4+
"crypto/sha256"
5+
"encoding/hex"
6+
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+
const pciTag = "pci_data"
13+
14+
type PCI struct{}
15+
16+
func (pci PCI) run(arch string) (Result, error) {
17+
output, err := util.Execute([]string{"lspci", "-s", ".0"}, nil)
18+
// Some systems may not have lspci command, so
19+
// if the execute fails,
20+
// send nil to SCC to indicate that PCI data was unavailable.
21+
if err != nil {
22+
return Result{pciTag: nil}, err
23+
}
24+
stringOutput := string(output)
25+
// get sha256 has a pci data
26+
mkSha256 := sha256.New()
27+
mkSha256.Write([]byte(output))
28+
sha256 := mkSha256.Sum(nil)
29+
hwIndex := hex.EncodeToString(sha256)
30+
31+
oldHwIndex := util.GetChecksum(PCIChecksumFile)
32+
if oldHwIndex != hwIndex {
33+
if util.UpdateLargeDataIDs {
34+
util.Debug.Print("updating: ", PCIChecksumFile)
35+
err = util.PutChecksum(PCIChecksumFile, hwIndex)
36+
if err != nil {
37+
return Result{pciTag: nil}, err
38+
}
39+
}
40+
var dataBlob util.Profile
41+
dataBlob.ProfileID = hwIndex
42+
dataBlob.ProfileData = stringOutput
43+
return Result{pciTag: dataBlob}, nil
44+
} else {
45+
var dataBlob util.Profile
46+
dataBlob.ProfileID = hwIndex
47+
return Result{pciTag: dataBlob}, nil
48+
}
49+
}

internal/collectors/pci_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package collectors
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"testing"
7+
8+
"github.com/SUSE/connect-ng/internal/util"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestPCIRunSuccess(t *testing.T) {
13+
assert := assert.New(t)
14+
15+
var dataBlob util.Profile
16+
dataBlob.ProfileID = "de5b2af84042cbe8298be6864362a99bae3e087fea4efcdad9e33f58fa323185"
17+
dataBlob.ProfileData = "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.ProfileData.(string), nil)
20+
expected := Result{"pci_data": 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{"pci_data": 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+
func TestPutChkSum(t *testing.T) {
43+
assert := assert.New(t)
44+
45+
err := util.PutChecksum("/tmp/testPutCheckSum", "testthis")
46+
assert.Nil(err)
47+
content, err1 := os.ReadFile("/tmp/testPutCheckSum")
48+
assert.Nil(err1)
49+
assert.Equal("testthis", string(content))
50+
51+
}
52+
53+
func TestGetChkSum(t *testing.T) {
54+
assert := assert.New(t)
55+
mockUtilFileExists(true)
56+
tstChkSum := util.GetChecksum("/tmp/testPutCheckSum")
57+
assert.Equal("testthis", tstChkSum)
58+
}
59+
60+
func TestGetChkSumNoFile(t *testing.T) {
61+
assert := assert.New(t)
62+
mockUtilFileExists(false)
63+
64+
tstChkSum := util.GetChecksum("/etc/zypp/chksum-pcidata.txt")
65+
assert.Equal("", tstChkSum)
66+
}

internal/connect/collectors.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ var usedCollectors = []collectors.Collector{
1414
collectors.CloudProvider{},
1515
collectors.Architecture{},
1616
collectors.ContainerRuntime{},
17+
collectors.PCI{},
18+
collectors.LSMOD{},
1719

1820
// Optional collectors
1921
collectors.Uname{},

internal/util/profiles.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package util
2+
3+
import (
4+
"os"
5+
)
6+
7+
// Profile struct is for handling lage data blobs
8+
// ProfileID: an index to reference ProfileData, at the moment
9+
//
10+
// this will be a sha256 caluculated from ProfileData
11+
// since, ProfileData can be any type (string, array
12+
// of strings, etc) it may need processing before the
13+
// sha256 can be calcuated
14+
//
15+
// ProfileData: can be any data such array of strings, a single large string
16+
type Profile struct {
17+
ProfileID string // sha256 of ProfileData
18+
ProfileData any // data associated with ProfileI
19+
}
20+
21+
func GetChecksum(chkSumFilePath string) string {
22+
// before system registration chkSumFilePath won't exist
23+
// in that case just return emtpy string
24+
if !FileExists(chkSumFilePath) {
25+
return ""
26+
}
27+
// read value in from checksum file
28+
content, err := os.ReadFile(chkSumFilePath)
29+
// if read fails return empty string, should not happed
30+
if err != nil {
31+
return ""
32+
}
33+
return string(content)
34+
}
35+
36+
func PutChecksum(chkSumFilePath string, value string) error {
37+
perms := os.FileMode(0644)
38+
err := os.WriteFile(chkSumFilePath, []byte(value), perms)
39+
return err
40+
}

internal/util/system.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const (
3131
)
3232

3333
var systemEcho bool
34+
var UpdateLargeDataIDs bool // set if -i/--info was used
3435

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

0 commit comments

Comments
 (0)