Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC for the new Collector interface #219

Merged
merged 7 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,21 @@

[![build result](https://build.opensuse.org/projects/systemsmanagement:SCC/packages/suseconnect-ng/badge.svg?type=default)](https://build.opensuse.org/package/show/systemsmanagement:SCC/suseconnect-ng)


SUSEConnect-ng is a work-in-progress project to rewrite [SUSEConnect](https://github.com/SUSE/connect) in Golang.

SUSEConnect is a command line tool for connecting a client system to the SUSE Customer Center.
SUSEConnect is a Golang command line tool for connecting a client system to the SUSE Customer Center.
It will connect the system to your product subscriptions and enable the product repositories/services locally.

SUSEConnect-ng reduces the size of its runtime dependencies compared to the
replaced SUSEConnect.
replaced [Ruby SUSEConnect](https://github.com/SUSE/connect).

SUSEConnect-ng is distributed as RPM for all SUSE distributions and gets built in
the [openSUSE build service](https://build.opensuse.org/package/show/systemsmanagement:SCC/suseconnect-ng).

Please visit https://scc.suse.com to see and manage your subscriptions.

SUSEConnect communicates with SCC over this [REST API](https://github.com/SUSE/connect/blob/master/doc/SCC-API-%28Implemented%29.md).
SUSEConnect-ng communicates with SCC over this [REST API](https://github.com/SUSE/connect/blob/master/doc/SCC-API-%28Implemented%29.md).

### Build
Requires Go 1.16 for [embed](https://pkg.go.dev/embed).
Requires Go >= 1.16 for [embed](https://pkg.go.dev/embed).
felixsch marked this conversation as resolved.
Show resolved Hide resolved
```
make build
```
Expand All @@ -31,3 +28,8 @@ cd connect-ng
podman run --rm -v "$PWD":/usr/src/myapp -w /usr/src/myapp golang:1.16 make build
```
This will create a `out/suseconnect` binary on the host.

### Testing

Run the unit tests: `make test`
Run selected unit tests, eg: `go test ./internal/collectors/`
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
module github.com/SUSE/connect-ng

go 1.21

require github.com/stretchr/testify v1.9.0

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
53 changes: 53 additions & 0 deletions internal/collectors/collectors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
ngetahun marked this conversation as resolved.
Show resolved Hide resolved
How to use the collector framework:
In the business logic, we would handle the mandatory/optional metrics
For hw_infos : we can call this framework on mandatory collectors and send the data to scc
For tahoe : we can call this with mandatory collectors. And then specify option collectors too.

Example:

var MandatoryCollectors = []collectors.Collector{
collectors.CPU{},
collectors.Hostname{},
collectors.Architecture{},
collectors.Memory{},
collectors.UUID{},
}

result, error := collectors.CollectInformation("x86_64", MandatoryCollectors)
ngetahun marked this conversation as resolved.
Show resolved Hide resolved
*/
package collectors

import (
"maps"
)

type Result = map[string]interface{}
type Architecture = string

const (
ARCHITECTURE_X86_64 = "x86_64"
ARCHITECTURE_ARM64 = "aarch64"
ARCHITECTURE_POWER = "ppc64le"
ARCHITECTURE_Z = "s390x"
)

var NoResult = Result{}

type Collector interface {
run(arch Architecture) (Result, error)
}

func CollectInformation(architecture Architecture, collectors []Collector) (Result, error) {
obj := Result{}

for _, collector := range collectors {
res, err := collector.run(architecture)
if err != nil {
return NoResult, err
}
maps.Copy(obj, res)
}

return obj, nil
}
70 changes: 70 additions & 0 deletions internal/collectors/collectors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package collectors

import (
"errors"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

type FakeCollector struct {
mock.Mock
}

func (m *FakeCollector) run(arch Architecture) (Result, error) {
args := m.Called(arch)

return args.Get(0).(Result), args.Error(1)
}

func TestCollectInformationRunAllCollectors(t *testing.T) {
assert := assert.New(t)

resultCollector1 := Result{"metric1": "value1"}
resultCollector2 := Result{"metric2": "value2"}

collector1 := FakeCollector{}
collector2 := FakeCollector{}

expected := Result{
"metric1": "value1",
"metric2": "value2",
}

// set up expectations
collector1.On("run", ARCHITECTURE_X86_64).Return(resultCollector1, nil)
collector2.On("run", ARCHITECTURE_X86_64).Return(resultCollector2, nil)

collectors := []Collector{
&collector1,
&collector2,
}

result, err := CollectInformation(ARCHITECTURE_X86_64, collectors)
if assert.NoError(err) {
assert.Equal(result, expected)
}
}

func TestCollectInformationWithFailingCollector(t *testing.T) {
assert := assert.New(t)

resultCollector1 := Result{"metric1": "value1"}

collector1 := FakeCollector{}
collector2 := FakeCollector{}

// set up expectations
collector1.On("run", ARCHITECTURE_X86_64).Return(resultCollector1, nil)
collector2.On("run", ARCHITECTURE_X86_64).Return(NoResult, errors.New("I am error"))

collectors := []Collector{
&collector1,
&collector2,
}

result, err := CollectInformation(ARCHITECTURE_X86_64, collectors)
assert.Error(err)
assert.Equal(result, NoResult)
}
35 changes: 35 additions & 0 deletions internal/collectors/cpu.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package collectors

import (
"strconv"
)

type CPU struct {
}

func (cpu CPU) run(arch Architecture) (Result, error) {
switch arch {
case ARCHITECTURE_Z:
return cpuInfoZ()
default:
return cpu.cpuInfoDefault()
}
}

func (CPU) cpuInfoDefault() (Result, error) {
cpuInfo, err := lscpu()
if err != nil {
return NoResult, err
}

cpuCount, _ := strconv.Atoi(cpuInfo["CPU(s)"])
socket, _ := strconv.Atoi(cpuInfo["Socket(s)"])

return Result{"cpus": cpuCount, "sockets": socket}, nil
}

func cpuInfoZ() (Result, error) {
return NoResult, nil
}

// TODO: Set default values for cpu and socket counts to handle invalid output from lscpu(if any) and test it.
ngetahun marked this conversation as resolved.
Show resolved Hide resolved
35 changes: 35 additions & 0 deletions internal/collectors/cpu_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package collectors

import (
"strings"
"testing"

"github.com/SUSE/connect-ng/internal/util"
"github.com/stretchr/testify/assert"
)

func TestCPUCollectorRun(t *testing.T) {
assert := assert.New(t)
expected := Result{"cpus": 2, "sockets": 2}
testObj := CPU{}

mockLscpu(t, "collectors/lscpu_x86_64.txt")

res, err := testObj.run(ARCHITECTURE_X86_64)
if err != nil {
t.Errorf("Something went wrong: %s", err)
}

assert.Equal(expected, res, "Result mismatch")
}

func mockLscpu(t *testing.T, path string) {
util.Execute = func(cmd []string, validExitCodes []int) ([]byte, error) {
actualCmd := strings.Join(cmd, " ")
testData := util.ReadTestFile(path, t)

assert.Equal(t, "lscpu", actualCmd, "Wrong command called")

return testData, nil
}
}
15 changes: 15 additions & 0 deletions internal/collectors/hostname.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package collectors

import "os"

type Hostname struct {
}

func (Hostname) run(arch Architecture) (Result, error) {
name, err := os.Hostname()
if err != nil {
return NoResult, err
}

return Result{"hostname": name}, nil
}
20 changes: 20 additions & 0 deletions internal/collectors/hostname_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package collectors

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
)

func TestHostnameRun(t *testing.T) {
assert := assert.New(t)
hostName, _ := os.Hostname()
expected := Result{"hostname": hostName}
collector := Hostname{}

result, err := collector.run(ARCHITECTURE_X86_64)

assert.Equal(expected, result)
assert.Nil(err)
}
36 changes: 36 additions & 0 deletions internal/collectors/lscpu.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package collectors
mssola marked this conversation as resolved.
Show resolved Hide resolved

import (
"bufio"
"bytes"
"strings"

"github.com/SUSE/connect-ng/internal/util"
)

func lscpu() (map[string]string, error) {
output, err := util.Execute([]string{"lscpu"}, nil)
felixsch marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}

return lscpu2map(output), nil
}

func lscpu2map(b []byte) map[string]string {
m := make(map[string]string)
scanner := bufio.NewScanner(bytes.NewReader(b))

for scanner.Scan() {
line := scanner.Text()
parts := strings.SplitN(line, ":", 2)
if len(parts) != 2 {
continue
}

key, val := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
m[key] = val
}

return m
}
34 changes: 34 additions & 0 deletions testdata/collectors/lscpu_x86_64.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 42 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 2
On-line CPU(s) list: 0,1
Vendor ID: GenuineIntel
Model name: Intel Core Processor (Haswell, no TSX)
CPU family: 6
Model: 60
Thread(s) per core: 1
Core(s) per socket: 1
Socket(s): 2
Stepping: 1
BogoMIPS: 3196.29
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology cpuid tsc_known_freq pni pclmulqdq vmx ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm invpcid_single pti ssbd tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt arat md_clear
Virtualization: VT-x
Hypervisor vendor: KVM
Virtualization type: full
L1d cache: 64 KiB (2 instances)
L1i cache: 64 KiB (2 instances)
L2 cache: 8 MiB (2 instances)
L3 cache: 32 MiB (2 instances)
NUMA node(s): 1
NUMA node0 CPU(s): 0,1
Vulnerability Itlb multihit: KVM: Mitigation: VMX disabled
Vulnerability L1tf: Mitigation; PTE Inversion
Vulnerability Mds: Mitigation; Clear CPU buffers; SMT Host state unknown
Vulnerability Meltdown: Mitigation; PTI
Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl and seccomp
Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Vulnerability Spectre v2: Mitigation; Retpolines, STIBP disabled, RSB filling
Vulnerability Srbds: Unknown: Dependent on hypervisor status
Vulnerability Tsx async abort: Not affected
Loading