Files
compute-blade-agent/internal/agent/api.go
Cedric Kienzler 062e36e33a feat(bladectl): add server version information to output (#100)
add server version information to output
ensure the blade-name is always set

---------

Co-authored-by: Cedric Kienzler <cedric@specht-labs.de>
2025-06-06 22:14:29 +00:00

157 lines
5.5 KiB
Go

package internal_agent
import (
"context"
"crypto/tls"
bladeapiv1alpha1 "github.com/compute-blade-community/compute-blade-agent/api/bladeapi/v1alpha1"
"github.com/compute-blade-community/compute-blade-agent/pkg/fancontroller"
"github.com/compute-blade-community/compute-blade-agent/pkg/log"
grpczap "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging"
"github.com/sierrasoftworks/humane-errors-go"
"github.com/spechtlabs/go-otel-utils/otelzap"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/protobuf/types/known/emptypb"
)
// setupGrpcServer initializes and configures the gRPC server with authentication, logging, and server options.
func (a *computeBladeAgent) setupGrpcServer(ctx context.Context) error {
listenMode, err := ListenModeFromString(a.config.Listen.GrpcListenMode)
if err != nil {
return err
}
var grpcOpts []grpc.ServerOption
if listenMode == ModeTcp && a.config.Listen.GrpcAuthenticated {
tlsCfg, err := createServerTLSConfig(ctx)
if err != nil {
return err
}
grpcOpts = append(grpcOpts, grpc.Creds(credentials.NewTLS(tlsCfg)))
if err := EnsureAuthenticatedBladectlConfig(ctx, a.config.Listen.Grpc, listenMode); err != nil {
return err
}
} else {
if err := EnsureUnauthenticatedBladectlConfig(ctx, a.config.Listen.Grpc, listenMode); err != nil {
return err
}
}
logger := log.InterceptorLogger(otelzap.L())
grpcOpts = append(grpcOpts,
grpc.ChainUnaryInterceptor(grpczap.UnaryServerInterceptor(logger)),
grpc.ChainStreamInterceptor(grpczap.StreamServerInterceptor(logger)),
)
a.server = grpc.NewServer(grpcOpts...)
return nil
}
// createServerTLSConfig creates and returns a TLS configuration for a server, enforcing client authentication.
// It generates or loads the necessary certificates and certificate pools, logging fatal errors if certificate loading fails.
func createServerTLSConfig(ctx context.Context) (*tls.Config, error) {
cert, certPool, err := EnsureServerCertificate(ctx)
if err != nil {
log.FromContext(ctx).WithError(err).Fatal("failed to load server key pair")
}
return &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: certPool,
}, nil
}
// EmitEvent dispatches an event to the event handler
func (a *computeBladeAgent) EmitEvent(ctx context.Context, req *bladeapiv1alpha1.EmitEventRequest) (*emptypb.Empty, error) {
event, err := fromProto(req.GetEvent())
if err != nil {
return nil, err
}
select {
case a.eventChan <- event:
return &emptypb.Empty{}, nil
case <-ctx.Done():
return &emptypb.Empty{}, ctx.Err()
}
}
// SetFanSpeed sets the fan speed
func (a *computeBladeAgent) SetFanSpeed(_ context.Context, req *bladeapiv1alpha1.SetFanSpeedRequest) (*emptypb.Empty, error) {
if a.state.CriticalActive() {
return &emptypb.Empty{}, humane.New("cannot set fan speed while the blade is in a critical state", "improve cooling on your blade before attempting to overwrite the fan speed")
}
a.fanController.Override(&fancontroller.FanOverrideOpts{Percent: uint8(req.GetPercent())})
return &emptypb.Empty{}, nil
}
// SetFanSpeedAuto sets the fan speed to automatic mode
func (a *computeBladeAgent) SetFanSpeedAuto(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
a.fanController.Override(nil)
return &emptypb.Empty{}, nil
}
// SetStealthMode enables/disables the stealth mode
func (a *computeBladeAgent) SetStealthMode(_ context.Context, req *bladeapiv1alpha1.StealthModeRequest) (*emptypb.Empty, error) {
if a.state.CriticalActive() {
return &emptypb.Empty{}, humane.New("cannot set stealth mode while the blade is in a critical state", "improve cooling on your blade before attempting to enable stealth mode again")
}
return &emptypb.Empty{}, a.blade.SetStealthMode(req.GetEnable())
}
// GetStatus aggregates the status of the blade
func (a *computeBladeAgent) GetStatus(_ context.Context, _ *emptypb.Empty) (*bladeapiv1alpha1.StatusResponse, error) {
rpm, err := a.blade.GetFanRPM()
if err != nil {
return nil, err
}
temp, err := a.blade.GetTemperature()
if err != nil {
return nil, err
}
powerStatus, err := a.blade.GetPowerStatus()
if err != nil {
return nil, err
}
steps := a.fanController.Steps()
fanCurveSteps := make([]*bladeapiv1alpha1.FanCurveStep, len(steps))
for idx, step := range steps {
fanCurveSteps[idx] = &bladeapiv1alpha1.FanCurveStep{
Temperature: int64(step.Temperature),
Percent: uint32(step.Percent),
}
}
versionInfo := &bladeapiv1alpha1.VersionInfo{
Version: a.agentInfo.Version,
Commit: a.agentInfo.Commit,
Date: a.agentInfo.BuildTime.Unix(),
}
return &bladeapiv1alpha1.StatusResponse{
StealthMode: a.blade.StealthModeActive(),
IdentifyActive: a.state.IdentifyActive(),
CriticalActive: a.state.CriticalActive(),
Temperature: int64(temp),
FanRpm: int64(rpm),
FanPercent: uint32(a.fanController.GetFanSpeedPercent(temp)),
FanSpeedAutomatic: a.fanController.IsAutomaticSpeed(),
PowerStatus: bladeapiv1alpha1.PowerStatus(powerStatus),
FanCurveSteps: fanCurveSteps,
CriticalTemperatureThreshold: int64(a.config.CriticalTemperatureThreshold),
Version: versionInfo,
}, nil
}
// WaitForIdentifyConfirm blocks until the identify confirmation process is completed or an error occurs.
func (a *computeBladeAgent) WaitForIdentifyConfirm(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
return &emptypb.Empty{}, a.state.WaitForIdentifyConfirm(ctx)
}