mirror of
https://github.com/compute-blade-community/compute-blade-agent.git
synced 2026-04-21 17:45:43 +02:00
feat(bladectl)!: add more bladectl commands (#91)
This PR introduces a comprehensive set of new subcommands to bladectl, expanding its capabilities for querying and managing compute blade state. It also includes an internal refactor to simplify interface management across the gRPC API.
* `get`
* `fan`: Returns current fan speed.
* `identify`: Indicates whether the identify mode is active.
* `stealth`: Shows if stealth mode is currently enabled.
* `status`: Prints a full blade status report.
* `temperature`: Retrieves current SoC temperature.
* `critical`: Shows whether critical mode is active.
* `power`: Reports the current power source (e.g., PoE+ or USB).
* `set`
* `stealth`: Enables stealth mode.
* `remove`
* `stealth`: Disables stealth mode.
* `describe`
* `fan`: Outputs the current fan curve configuration.
* `monitor`: plot some charts about the state of the compute-blade-agent
* **gRPC API refactor**: The gRPC service definitions previously located in `internal/api` have been folded into `internal/agent`. This eliminates redundant interface declarations and ensures that all ComputeBladeAgent implementations are directly compatible with the gRPC API.
This reduces duplication and improves long-term maintainability and clarity of the interface contract.
```bash
bladectl set fan --percent 90 --blade 1 --blade 2
bladectl unset identify --blade 1 --blade 2 --blade 3 --blade 4
bladectl set stealth --blade 1 --blade 2 --blade 3 --blade 4
bladectl get status --blade 1 --blade 2 --blade 3 --blade 4
┌───────┬─────────────┬────────────────────┬───────────────┬──────────────┬──────────┬───────────────┬──────────────┐
│ BLADE │ TEMPERATURE │ FAN SPEED OVERRIDE │ FAN SPEED │ STEALTH MODE │ IDENTIFY │ CRITICAL MODE │ POWER STATUS │
├───────┼─────────────┼────────────────────┼───────────────┼──────────────┼──────────┼───────────────┼──────────────┤
│ 1 │ 50°C │ 90% │ 5825 RPM(90%) │ Active │ Off │ Off │ poe+ │
│ 2 │ 48°C │ 90% │ 5825 RPM(90%) │ Active │ Off │ Off │ poe+ │
│ 3 │ 49°C │ Not set │ 4643 RPM(56%) │ Active │ Off │ Off │ poe+ │
│ 4 │ 49°C │ Not set │ 4774 RPM(58%) │ Active │ Off │ Off │ poe+ │
└───────┴─────────────┴────────────────────┴───────────────┴──────────────┴──────────┴───────────────┴──────────────┘
bladectl rm stealth --blade 1 --blade 2 --blade 3 --blade 4
bladectl rm fan --blade 1 --blade 2 --blade 3 --blade 4
bladectl get status --blade 1 --blade 2 --blade 3 --blade 4
┌───────┬─────────────┬────────────────────┬───────────────┬──────────────┬──────────┬───────────────┬──────────────┐
│ BLADE │ TEMPERATURE │ FAN SPEED OVERRIDE │ FAN SPEED │ STEALTH MODE │ IDENTIFY │ CRITICAL MODE │ POWER STATUS │
├───────┼─────────────┼────────────────────┼───────────────┼──────────────┼──────────┼───────────────┼──────────────┤
│ 1 │ 51°C │ Not set │ 5177 RPM(66%) │ Off │ Off │ Off │ poe+ │
│ 2 │ 49°C │ Not set │ 5177 RPM(58%) │ Off │ Off │ Off │ poe+ │
│ 3 │ 50°C │ Not set │ 4659 RPM(60%) │ Off │ Off │ Off │ poe+ │
│ 4 │ 48°C │ Not set │ 4659 RPM(54%) │ Off │ Off │ Off │ poe+ │
└───────┴─────────────┴────────────────────┴───────────────┴──────────────┴──────────┴───────────────┴──────────────┘
```
when having multiple compute-blades in your bladeconfig:
```yaml
blades:
- name: 1
blade:
server: blade-pi1:8081
cert:
certificate-authority-data: <redacted>
client-certificate-data: <redacted>
client-key-data: <redacted>
- name: 2
blade:
server: blade-pi2:8081
cert:
certificate-authority-data: <redacted>
client-certificate-data: <redacted>
client-key-data: <redacted>
- name: 3
blade:
server: blade-pi3:8081
cert:
certificate-authority-data: <redacted>
client-certificate-data: <redacted>
client-key-data: <redacted>
- name: 4
blade:
server: blade-pi4:8081
cert:
certificate-authority-data: <redacted>
client-certificate-data: <redacted>
client-key-data: <redacted>
- name: 4
blade:
server: blade-pi4:8081
cert:
certificate-authority-data: <redacted>
client-certificate-data: <redacted>
client-key-data: <redacted>
current-blade: 1
```
Fixes #4, #9 (partially), should help with #5
* test: improve unit-testing
* fix: pin github.com/warthog618/gpiod
---------
Co-authored-by: Cedric Kienzler <cedric@specht-labs.de>
This commit is contained in:
committed by
Cedric Kienzler
parent
7ec49ce05c
commit
781ded8e43
72
pkg/util/clock_test.go
Normal file
72
pkg/util/clock_test.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package util_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/compute-blade-community/compute-blade-agent/pkg/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestRealClock_Now ensures that RealClock.Now() returns a time close to the actual time.
|
||||
func TestRealClock_Now(t *testing.T) {
|
||||
rc := util.RealClock{}
|
||||
before := time.Now()
|
||||
got := rc.Now()
|
||||
after := time.Now()
|
||||
|
||||
if got.Before(before) || got.After(after) {
|
||||
t.Errorf("RealClock.Now() = %v, want between %v and %v", got, before, after)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRealClock_After ensures that RealClock.After() returns a channel that sends after the given duration.
|
||||
func TestRealClock_After(t *testing.T) {
|
||||
rc := util.RealClock{}
|
||||
delay := 50 * time.Millisecond
|
||||
|
||||
start := time.Now()
|
||||
ch := rc.After(delay)
|
||||
<-ch
|
||||
elapsed := time.Since(start)
|
||||
|
||||
if elapsed < delay {
|
||||
t.Errorf("RealClock.After(%v) triggered too early after %v", delay, elapsed)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMockClock_Now tests that MockClock.Now() returns the expected time and records the call.
|
||||
func TestMockClock_Now(t *testing.T) {
|
||||
mockClock := new(util.MockClock)
|
||||
expectedTime := time.Date(2025, time.June, 6, 12, 0, 0, 0, time.UTC)
|
||||
|
||||
mockClock.On("Now").Return(expectedTime)
|
||||
|
||||
actualTime := mockClock.Now()
|
||||
assert.Equal(t, expectedTime, actualTime)
|
||||
mockClock.AssertCalled(t, "Now")
|
||||
mockClock.AssertExpectations(t)
|
||||
}
|
||||
|
||||
// TestMockClock_After tests that MockClock.After() returns the expected channel and records the call.
|
||||
func TestMockClock_After(t *testing.T) {
|
||||
mockClock := new(util.MockClock)
|
||||
duration := 100 * time.Millisecond
|
||||
expectedChan := make(chan time.Time, 1)
|
||||
expectedTime := time.Now().Add(duration)
|
||||
expectedChan <- expectedTime
|
||||
|
||||
mockClock.On("After", duration).Return(expectedChan)
|
||||
|
||||
resultChan := mockClock.After(duration)
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
assert.WithinDuration(t, expectedTime, result, time.Second)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timeout waiting for result from MockClock.After")
|
||||
}
|
||||
|
||||
mockClock.AssertCalled(t, "After", duration)
|
||||
mockClock.AssertExpectations(t)
|
||||
}
|
||||
25
pkg/util/file_exist_test.go
Normal file
25
pkg/util/file_exist_test.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package util_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/compute-blade-community/compute-blade-agent/pkg/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFileExists(t *testing.T) {
|
||||
// Create a temporary file
|
||||
tmpFile, err := os.CreateTemp("", "fileexists-test")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// It should exist
|
||||
assert.True(t, util.FileExists(tmpFile.Name()), "Expected file to exist")
|
||||
|
||||
// Close and remove the file
|
||||
assert.NoError(t, tmpFile.Close())
|
||||
assert.NoError(t, os.Remove(tmpFile.Name()))
|
||||
|
||||
// It should not exist anymore
|
||||
assert.False(t, util.FileExists(tmpFile.Name()), "Expected file not to exist")
|
||||
}
|
||||
18
pkg/util/host_ips_test.go
Normal file
18
pkg/util/host_ips_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package util_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/compute-blade-community/compute-blade-agent/pkg/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetHostIPs_ReturnsNonLoopbackIPs(t *testing.T) {
|
||||
ips, err := util.GetHostIPs()
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, ip := range ips {
|
||||
assert.False(t, ip.IsLoopback(), "Should not return loopback IPs")
|
||||
assert.False(t, ip.IsUnspecified(), "Should not return unspecified IPs")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user