Skip to content

Commit 4dc73c8

Browse files
committed
feat(agent): detect host CPU model and patch ClusterImpNodeProfile at startup
1 parent d271bb6 commit 4dc73c8

4 files changed

Lines changed: 113 additions & 0 deletions

File tree

internal/agent/cpumodel_linux.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//go:build linux
2+
3+
package agent
4+
5+
import (
6+
"context"
7+
"os"
8+
"strings"
9+
10+
apierrors "k8s.io/apimachinery/pkg/api/errors"
11+
"sigs.k8s.io/controller-runtime/pkg/client"
12+
logf "sigs.k8s.io/controller-runtime/pkg/log"
13+
14+
impv1alpha1 "github.com/syscode-labs/imp/api/v1alpha1"
15+
)
16+
17+
// detectAndPatchCPUModel reads /proc/cpuinfo, extracts the CPU model string,
18+
// and patches it onto the ClusterImpNodeProfile for this node.
19+
// This is best-effort — errors are logged at V(1) and do not affect agent startup.
20+
func detectAndPatchCPUModel(ctx context.Context, c client.Client, nodeName string) {
21+
log := logf.FromContext(ctx)
22+
data, err := os.ReadFile("/proc/cpuinfo")
23+
if err != nil {
24+
log.V(1).Info("could not read /proc/cpuinfo", "err", err)
25+
return
26+
}
27+
model := parseCPUModelFromProcInfo(string(data))
28+
if model == "" {
29+
log.V(1).Info("could not parse CPU model from /proc/cpuinfo")
30+
return
31+
}
32+
33+
profile := &impv1alpha1.ClusterImpNodeProfile{}
34+
err = c.Get(ctx, client.ObjectKey{Name: nodeName}, profile)
35+
if apierrors.IsNotFound(err) {
36+
log.V(1).Info("no ClusterImpNodeProfile for node, skipping CPU model patch", "node", nodeName)
37+
return
38+
}
39+
if err != nil {
40+
log.Error(err, "failed to get ClusterImpNodeProfile for CPU model patch", "node", nodeName)
41+
return
42+
}
43+
44+
if profile.Spec.CPUModel == model {
45+
return // already up to date
46+
}
47+
48+
base := profile.DeepCopy()
49+
profile.Spec.CPUModel = model
50+
if err := c.Patch(ctx, profile, client.MergeFrom(base)); err != nil {
51+
log.Error(err, "failed to patch CPUModel onto ClusterImpNodeProfile", "node", nodeName)
52+
return
53+
}
54+
log.Info("patched CPU model onto ClusterImpNodeProfile", "node", nodeName, "model", model)
55+
}
56+
57+
// parseCPUModelFromProcInfo extracts the "model name" line from /proc/cpuinfo content.
58+
func parseCPUModelFromProcInfo(content string) string {
59+
for _, line := range strings.Split(content, "\n") {
60+
if strings.HasPrefix(line, "model name") {
61+
parts := strings.SplitN(line, ":", 2)
62+
if len(parts) == 2 {
63+
return strings.TrimSpace(parts[1])
64+
}
65+
}
66+
}
67+
return ""
68+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//go:build linux
2+
3+
package agent
4+
5+
import (
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestParseCPUModel_standard(t *testing.T) {
12+
cpuinfo := "processor\t: 0\nvendor_id\t: GenuineIntel\nmodel name\t: Intel(R) Core(TM) i5-8500T CPU @ 2.10GHz\ncpu MHz\t\t: 2100.000\n"
13+
model := parseCPUModelFromProcInfo(cpuinfo)
14+
assert.Equal(t, "Intel(R) Core(TM) i5-8500T CPU @ 2.10GHz", model)
15+
}
16+
17+
func TestParseCPUModel_arm64(t *testing.T) {
18+
cpuinfo := "processor\t: 0\nFeatures\t: fp asimd\nCPU implementer\t: 0x41\nmodel name\t: ARM Cortex-A72\n"
19+
model := parseCPUModelFromProcInfo(cpuinfo)
20+
assert.Equal(t, "ARM Cortex-A72", model)
21+
}
22+
23+
func TestParseCPUModel_missing(t *testing.T) {
24+
model := parseCPUModelFromProcInfo("no model name here\nprocessor: 0\n")
25+
assert.Equal(t, "", model)
26+
}
27+
28+
func TestParseCPUModel_empty(t *testing.T) {
29+
model := parseCPUModelFromProcInfo("")
30+
assert.Equal(t, "", model)
31+
}

internal/agent/cpumodel_stub.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//go:build !linux
2+
3+
package agent
4+
5+
import (
6+
"context"
7+
8+
"sigs.k8s.io/controller-runtime/pkg/client"
9+
)
10+
11+
func detectAndPatchCPUModel(_ context.Context, _ client.Client, _ string) {} //nolint:unused

internal/agent/reconciler.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,9 @@ func (r *ImpVMReconciler) SetupWithManager(mgr ctrl.Manager) error {
211211
}
212212
}
213213

214+
// Detect and patch CPU model onto ClusterImpNodeProfile at startup (best-effort).
215+
go detectAndPatchCPUModel(context.Background(), r.Client, r.NodeName)
216+
214217
return ctrl.NewControllerManagedBy(mgr).
215218
For(&impdevv1alpha1.ImpVM{}).
216219
Named("agent-impvm").

0 commit comments

Comments
 (0)