Skip to content

Commit

Permalink
feat: add cpu/ram info as resource
Browse files Browse the repository at this point in the history
Expose processor and memory information from SMBIOS as Talos resources.

Output from QEMU:

```bash
❯ talosctl -n 10.5.0.2 get cpu
NODE       NAMESPACE   TYPE        ID      VERSION   MANUFACTURER   MODEL        CORES   THREADS
10.5.0.2   hardware    Processor   CPU-0   1         QEMU           pc-q35-6.2   4       1

❯ talosctl -n 10.5.0.2 get ram
NODE       NAMESPACE   TYPE     ID       VERSION   MANUFACTURER   MODEL   SIZE
10.5.0.2   hardware    Memory   DIMM-0   1         QEMU                   2048

❯ talosctl -n 10.5.0.2 get cpu CPU-0 -o yaml
node: 10.5.0.2
metadata:
    namespace: hardware
    type: Processors.hardware.talos.dev
    id: CPU-0
    version: 1
    owner: hardware.SystemInfoController
    phase: running
    created: 2022-05-19T13:58:12Z
    updated: 2022-05-19T13:58:12Z
spec:
    socket: CPU 0
    manufacturer: QEMU
    productName: pc-q35-6.2
    maxSpeed: 2000
    bootSpeed: 2000
    status: 65
    coreCount: 4
    coreEnabled: 4
    threadCount: 1

❯ talosctl -n 10.5.0.2 get ram DIMM-0 -o yaml
node: 10.5.0.2
metadata:
    namespace: hardware
    type: Memories.hardware.talos.dev
    id: DIMM-0
    version: 1
    owner: hardware.SystemInfoController
    phase: running
    created: 2022-05-19T13:58:12Z
    updated: 2022-05-19T13:58:12Z
spec:
    size: 2048
    deviceLocator: DIMM 0
    bankLocator: ""
    speed: 0
    manufacturer: QEMU
```

Signed-off-by: Noel Georgi <[email protected]>
  • Loading branch information
frezbo committed May 19, 2022
1 parent c3f2903 commit 20286c9
Show file tree
Hide file tree
Showing 17 changed files with 705 additions and 1 deletion.
6 changes: 6 additions & 0 deletions internal/app/machined/pkg/adapters/hardware/hardware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package hardware implements adapters wrapping resources/hardware to provide additional functionality.
package hardware
54 changes: 54 additions & 0 deletions internal/app/machined/pkg/adapters/hardware/memory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package hardware

import (
"github.com/talos-systems/go-smbios/smbios"

"github.com/talos-systems/talos/pkg/machinery/resources/hardware"
)

// Memory adapter provider conversion from smbios.SMBIOS.
//
//nolint:revive,golint
func Memory(m *hardware.Memory) memory {
return memory{
Memory: m,
}
}

type memory struct {
*hardware.Memory
}

// Update current processor info.
func (m memory) Update(memory *smbios.MemoryDevice) {
translateProcessorInfo := func(in *smbios.MemoryDevice) hardware.MemorySpec {
var memorySpec hardware.MemorySpec

if in.Size != 0 && in.Size != 0xFFFF {
var size uint32

if in.Size == 0x7FFF {
size = uint32(in.ExtendedSize)
} else {
size = uint32(in.Size)
}

memorySpec.AssetTag = in.AssetTag
memorySpec.BankLocator = in.BankLocator
memorySpec.DeviceLocator = in.DeviceLocator
memorySpec.Manufacturer = in.Manufacturer
memorySpec.ProductName = in.PartNumber
memorySpec.SerialNumber = in.SerialNumber
memorySpec.Size = size
memorySpec.Speed = uint32(in.Speed)
}

return memorySpec
}

*m.Memory.TypedSpec() = translateProcessorInfo(memory)
}
50 changes: 50 additions & 0 deletions internal/app/machined/pkg/adapters/hardware/processor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package hardware

import (
"github.com/talos-systems/go-smbios/smbios"

"github.com/talos-systems/talos/pkg/machinery/resources/hardware"
)

// Processor adapter provider conversion from smbios.SMBIOS.
//
//nolint:revive,golint
func Processor(p *hardware.Processor) processor {
return processor{
Processor: p,
}
}

type processor struct {
*hardware.Processor
}

// Update current processor info.
func (p processor) Update(processor *smbios.ProcessorInformation) {
translateProcessorInfo := func(in *smbios.ProcessorInformation) hardware.ProcessorSpec {
var processorSpec hardware.ProcessorSpec

if in.Status.SocketPopulated() {
processorSpec.Socket = in.SocketDesignation
processorSpec.Manufacturer = in.ProcessorManufacturer
processorSpec.ProductName = in.ProcessorVersion
processorSpec.MaxSpeed = uint32(in.MaxSpeed)
processorSpec.BootSpeed = uint32(in.CurrentSpeed)
processorSpec.Status = uint32(in.Status)
processorSpec.SerialNumber = in.SerialNumber
processorSpec.AssetTag = in.AssetTag
processorSpec.PartNumber = in.PartNumber
processorSpec.CoreCount = uint32(in.CoreCount)
processorSpec.CoreEnabled = uint32(in.CoreEnabled)
processorSpec.ThreadCount = uint32(in.ThreadCount)
}

return processorSpec
}

*p.Processor.TypedSpec() = translateProcessorInfo(processor)
}
5 changes: 5 additions & 0 deletions internal/app/machined/pkg/controllers/hardware/hardware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package hardware
112 changes: 112 additions & 0 deletions internal/app/machined/pkg/controllers/hardware/hardware_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package hardware_test

import (
"context"
"log"
"sync"
"time"

"github.com/cosi-project/runtime/pkg/controller/runtime"
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/state"
"github.com/cosi-project/runtime/pkg/state/impl/inmem"
"github.com/cosi-project/runtime/pkg/state/impl/namespaced"
"github.com/stretchr/testify/suite"
"github.com/talos-systems/go-retry/retry"

"github.com/talos-systems/talos/pkg/logging"
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
"github.com/talos-systems/talos/pkg/machinery/resources/config"
)

type HardwareSuite struct {
suite.Suite

state state.State

runtime *runtime.Runtime
wg sync.WaitGroup

ctx context.Context //nolint:containedctx
ctxCancel context.CancelFunc
}

func (suite *HardwareSuite) SetupTest() {
suite.ctx, suite.ctxCancel = context.WithTimeout(context.Background(), 3*time.Minute)

suite.state = state.WrapCore(namespaced.NewState(inmem.Build))

var err error

logger := logging.Wrap(log.Writer())

suite.runtime, err = runtime.NewRuntime(suite.state, logger)
suite.Require().NoError(err)
}

func (suite *HardwareSuite) startRuntime() {
suite.wg.Add(1)

go func() {
defer suite.wg.Done()

suite.Assert().NoError(suite.runtime.Run(suite.ctx))
}()
}

func (suite *HardwareSuite) assertResource(md resource.Metadata, check func(res resource.Resource) error) func() error {
return func() error {
r, err := suite.state.Get(suite.ctx, md)
if err != nil {
if state.IsNotFoundError(err) {
return retry.ExpectedError(err)
}

return err
}

return check(r)
}
}

func (suite *HardwareSuite) assertNoResource(md resource.Metadata) func() error {
return func() error {
_, err := suite.state.Get(suite.ctx, md)
if err == nil {
return retry.ExpectedErrorf("resource %s still exists", md)
}

if state.IsNotFoundError(err) {
return nil
}

return err
}
}

func (suite *HardwareSuite) TearDownTest() {
suite.T().Log("tear down")

suite.ctxCancel()

suite.wg.Wait()

// trigger updates in resources to stop watch loops
err := suite.state.Create(
context.Background(), config.NewMachineConfig(
&v1alpha1.Config{
ConfigVersion: "v1alpha1",
MachineConfig: &v1alpha1.MachineConfig{},
},
),
)
if state.IsConflictError(err) {
err = suite.state.Destroy(context.Background(), config.NewMachineConfig(nil).Metadata())
}

suite.Assert().NoError(err)
}
26 changes: 26 additions & 0 deletions internal/app/machined/pkg/controllers/hardware/smbios.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package hardware

import (
"sync"

"github.com/talos-systems/go-smbios/smbios"
)

var (
syncSMBIOS sync.Once
connSMBIOS *smbios.SMBIOS
errSMBIOS error
)

// GetSMBIOSInfo returns the SMBIOS info.
func GetSMBIOSInfo() (*smbios.SMBIOS, error) {
syncSMBIOS.Do(func() {
connSMBIOS, errSMBIOS = smbios.New()
})

return connSMBIOS, errSMBIOS
}
97 changes: 97 additions & 0 deletions internal/app/machined/pkg/controllers/hardware/system.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package hardware

import (
"context"
"fmt"
"strings"

"github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/resource"
"github.com/talos-systems/go-smbios/smbios"
"go.uber.org/zap"

hwadapter "github.com/talos-systems/talos/internal/app/machined/pkg/adapters/hardware"
"github.com/talos-systems/talos/pkg/machinery/resources/hardware"
)

// SystemInfoController populates CPU information of the underlying hardware.
type SystemInfoController struct {
SMBIOS *smbios.SMBIOS
}

// Name implements controller.Controller interface.
func (ctrl *SystemInfoController) Name() string {
return "hardware.SystemInfoController"
}

// Inputs implements controller.Controller interface.
func (ctrl *SystemInfoController) Inputs() []controller.Input {
return nil
}

// Outputs implements controller.Controller interface.
func (ctrl *SystemInfoController) Outputs() []controller.Output {
return []controller.Output{
{
Type: hardware.ProcessorType,
Kind: controller.OutputExclusive,
},
{
Type: hardware.MemoryType,
Kind: controller.OutputExclusive,
},
}
}

// Run implements controller.Controller interface.
//
//nolint:gocyclo
func (ctrl *SystemInfoController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
select {
case <-ctx.Done():
return nil
case <-r.EventCh():
}

// controller runs only once
if ctrl.SMBIOS == nil {
s, err := GetSMBIOSInfo()
if err != nil {
return err
}

ctrl.SMBIOS = s
}

for _, p := range ctrl.SMBIOS.ProcessorInformation {
// replaces `CPU 0` with `CPU-0`
id := strings.ReplaceAll(p.SocketDesignation, " ", "-")

if err := r.Modify(ctx, hardware.NewProcessorInfo(id), func(res resource.Resource) error {
hwadapter.Processor(res.(*hardware.Processor)).Update(&p)

return nil
}); err != nil {
return fmt.Errorf("error updating objects: %w", err)
}
}

for _, m := range ctrl.SMBIOS.MemoryDevices {
// replaces `SIMM 0` with `SIMM-0`
id := strings.ReplaceAll(m.DeviceLocator, " ", "-")

if err := r.Modify(ctx, hardware.NewMemoryInfo(id), func(res resource.Resource) error {
hwadapter.Memory(res.(*hardware.Memory)).Update(&m)

return nil
}); err != nil {
return fmt.Errorf("error updating objects: %w", err)
}
}

return nil
}
Loading

0 comments on commit 20286c9

Please sign in to comment.