feat: add rudimentary API & bladectl client

Signed-off-by: Matthias Riegler <matthias.riegler@ankorstore.com>
This commit is contained in:
Matthias Riegler
2023-07-25 23:53:27 +02:00
parent 3e7386b38c
commit 70892128bc
14 changed files with 1277 additions and 14 deletions

View File

@@ -3,6 +3,7 @@ package main
import (
"context"
"fmt"
"net"
"net/http"
"os"
"os/signal"
@@ -11,10 +12,12 @@ import (
"time"
"github.com/prometheus/client_golang/prometheus/promhttp"
bladeapiv1alpha1 "github.com/xvzf/computeblade-agent/api/bladeapi/v1alpha1"
"github.com/xvzf/computeblade-agent/internal/agent"
"github.com/xvzf/computeblade-agent/pkg/ledengine"
"github.com/xvzf/computeblade-agent/pkg/log"
"go.uber.org/zap"
"google.golang.org/grpc"
)
func main() {
@@ -29,7 +32,7 @@ func main() {
ctx, cancelCtx := context.WithCancelCause(baseCtx)
defer cancelCtx(context.Canceled)
agent, err := agent.NewComputeBladeAgent(agent.ComputeBladeAgentConfig{
computebladeAgent, err := agent.NewComputeBladeAgent(agent.ComputeBladeAgentConfig{
IdleLedColor: ledengine.LedColorGreen(0.05),
IdentifyLedColor: ledengine.LedColorPurple(0.05),
CriticalLedColor: ledengine.LedColorRed(0.3),
@@ -61,13 +64,41 @@ func main() {
wg.Add(1)
go func() {
defer wg.Done()
err := agent.Run(ctx)
err := computebladeAgent.Run(ctx)
if err != nil && err != context.Canceled {
log.FromContext(ctx).Error("Failed to run agent", zap.Error(err))
cancelCtx(err)
}
}()
// Setup GRPC server
// FIXME add logging middleware
grpcServer := grpc.NewServer()
bladeapiv1alpha1.RegisterBladeAgentServiceServer(grpcServer, agent.NewGrpcServiceFor(computebladeAgent))
wg.Add(1)
go func() {
defer wg.Done()
socketPath := "/tmp/computeblade-agent.sock"
grpcListen, err := net.Listen("unix", "/tmp/computeblade-agent.sock")
if err != nil {
log.FromContext(ctx).Error("Failed to create grpc listener", zap.Error(err))
cancelCtx(err)
return
}
log.FromContext(ctx).Info("Starting grpc server", zap.String("address", socketPath))
if err := grpcServer.Serve(grpcListen); err != nil && err != grpc.ErrServerStopped {
log.FromContext(ctx).Error("Failed to start grpc server", zap.Error(err))
cancelCtx(err)
}
}()
wg.Add(1)
go func() {
defer wg.Done()
<-ctx.Done()
log.FromContext(ctx).Info("Shutting down grpc server")
grpcServer.GracefulStop()
}()
// setup prometheus endpoint
promHandler := http.NewServeMux()
promHandler.Handle("/metrics", promhttp.Handler())

45
cmd/bladectl/cmd_fan.go Normal file
View File

@@ -0,0 +1,45 @@
package main
import (
"strconv"
"github.com/spf13/cobra"
bladeapiv1alpha1 "github.com/xvzf/computeblade-agent/api/bladeapi/v1alpha1"
)
func init() {
cmdFan.AddCommand(cmdFanSetPercent)
rootCmd.AddCommand(cmdFan)
}
var (
cmdFan = &cobra.Command{
Use: "fan",
Short: "Fan-related commands for the compute blade",
}
cmdFanSetPercent = &cobra.Command{
Use: "set-percent <percent>",
Example: "bladectl fan set-percent 50",
Short: "Set the fan speed in percent",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var err error
ctx := cmd.Context()
client := clientFromContext(ctx)
// convert string to int
percent, err := strconv.Atoi(args[0])
if err != nil {
return err
}
_, err = client.SetFanSpeed(ctx, &bladeapiv1alpha1.SetFanSpeedRequest{
Percent: int64(percent),
})
return err
},
}
)

View File

@@ -0,0 +1,58 @@
package main
import (
"github.com/spf13/cobra"
bladeapiv1alpha1 "github.com/xvzf/computeblade-agent/api/bladeapi/v1alpha1"
"google.golang.org/protobuf/types/known/emptypb"
)
func init() {
cmdIdentify.Flags().Bool("confirm", false, "confirm the identify state")
cmdIdentify.Flags().Bool("wait", false, "Wait for the identify state to be confirmed (e.g. by a physical button press)")
rootCmd.AddCommand(cmdIdentify)
}
var cmdIdentify = &cobra.Command{
Use: "identify",
Short: "interact with the compute-blade identity LED",
RunE: runIdentity,
}
func runIdentity(cmd *cobra.Command, _ []string) error {
var err error
ctx := cmd.Context()
client := clientFromContext(ctx)
// Get flags
confirm, err := cmd.Flags().GetBool("confirm")
if err != nil {
return err
}
wait, err := cmd.Flags().GetBool("wait")
if err != nil {
return err
}
// Check if we should wait for the identify state to be confirmed
event := bladeapiv1alpha1.Event_IDENTIFY
if confirm {
event = bladeapiv1alpha1.Event_IDENTIFY_CONFIRM
}
// Emit the event to the computeblade-agent
_, err = client.EmitEvent(ctx, &bladeapiv1alpha1.EmitEventRequest{Event: event})
if err != nil {
return err
}
// Check if we should wait for the identify state to be confirmed
if wait {
_, err := client.WaitForIdentifyConfirm(ctx, &emptypb.Empty{})
if err != nil {
return err
}
}
return nil
}

85
cmd/bladectl/main.go Normal file
View File

@@ -0,0 +1,85 @@
package main
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/spf13/cobra"
bladeapiv1alpha1 "github.com/xvzf/computeblade-agent/api/bladeapi/v1alpha1"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
type grpcClientContextKey int
const defaultGrpcClientContextKey grpcClientContextKey = 0
var (
grpcAddr string
timeout time.Duration
)
func init() {
rootCmd.PersistentFlags().
StringVar(&grpcAddr, "addr", "unix:///tmp/computeblade-agent.sock", "address of the computeblade-agent gRPC server")
rootCmd.PersistentFlags().DurationVar(&timeout, "timeout", time.Minute, "timeout for gRPC requests")
}
func clientIntoContext(ctx context.Context, client bladeapiv1alpha1.BladeAgentServiceClient) context.Context {
return context.WithValue(ctx, defaultGrpcClientContextKey, client)
}
func clientFromContext(ctx context.Context) (bladeapiv1alpha1.BladeAgentServiceClient) {
client, ok := ctx.Value(defaultGrpcClientContextKey).(bladeapiv1alpha1.BladeAgentServiceClient)
if !ok {
panic("grpc client not found in context")
}
return client
}
var rootCmd = &cobra.Command{
Use: "bladectl",
Short: "bladectl interacts with the computeblade-agent and allows you to manage hardware-features of your compute blade(s)",
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
origCtx := cmd.Context()
// setup signal handlers for SIGINT and SIGTERM
ctx, cancelCtx := context.WithTimeout(origCtx, timeout)
// setup signal handler channels
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
// Wait for context cancel or signal
select {
case <-ctx.Done():
case <-sigs:
// On signal, cancel context
cancelCtx()
}
}()
// FIXME handle conn teardown properly
// setup grpc client
conn, err := grpc.Dial(grpcAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return fmt.Errorf("failed to dial grpc server: %w", err)
}
client := bladeapiv1alpha1.NewBladeAgentServiceClient(conn)
cmd.SetContext(clientIntoContext(ctx, client))
return nil
},
}
func main() {
if err := rootCmd.Execute(); err != nil {
log.Fatal(err)
}
}