feat: share proxmox cluster client with worker
This commit is contained in:
@@ -11,7 +11,9 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"forgejo.digital-droplets.de/philschlo/proxui/platform/cluster"
|
||||
"forgejo.digital-droplets.de/philschlo/proxui/platform/config"
|
||||
"forgejo.digital-droplets.de/philschlo/proxui/platform/encryption"
|
||||
"forgejo.digital-droplets.de/philschlo/proxui/platform/logging"
|
||||
"github.com/hibiken/asynq"
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
@@ -28,6 +30,12 @@ func main() {
|
||||
}
|
||||
|
||||
logger := logging.New("worker", cfg.AppEnv, cfg.LogLevel)
|
||||
tokenCipher, err := encryption.NewFromBase64(cfg.MasterKeyBase64)
|
||||
if err != nil {
|
||||
logger.Error("failed to initialize cluster token encryption", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
db, err := openDatabase(cfg.DatabaseURL)
|
||||
if err != nil {
|
||||
logger.Error("failed to connect database", "error", err)
|
||||
@@ -43,7 +51,8 @@ func main() {
|
||||
Concurrency: cfg.WorkerConcurrency,
|
||||
})
|
||||
mux := asynq.NewServeMux()
|
||||
registerHandlers(mux, db, logger)
|
||||
clusterRepository := cluster.NewRepository(db, tokenCipher)
|
||||
registerHandlers(mux, db, clusterRepository, logger)
|
||||
|
||||
go func() {
|
||||
logger.Info("worker started", "concurrency", cfg.WorkerConcurrency, "redis_addr", cfg.RedisAddr)
|
||||
@@ -58,12 +67,12 @@ func main() {
|
||||
logger.Info("worker stopped")
|
||||
}
|
||||
|
||||
func registerHandlers(mux *asynq.ServeMux, db *sql.DB, logger *slog.Logger) {
|
||||
func registerHandlers(mux *asynq.ServeMux, db *sql.DB, clusters cluster.Repository, logger *slog.Logger) {
|
||||
mux.Handle(tasks.TypeDummy, tasks.NewDummyHandler(logger))
|
||||
mux.Handle(
|
||||
tasks.TypeProxmoxTaskPoll,
|
||||
tasks.NewProxmoxTaskPollHandler(
|
||||
tasks.UnconfiguredProxmoxTaskClientResolver{},
|
||||
tasks.NewDefaultClusterTaskClientResolver(clusters),
|
||||
tasks.NewSQLVMStatusStore(db),
|
||||
tasks.NewSQLAuditWriter(db),
|
||||
logger,
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
@@ -13,6 +14,8 @@ import (
|
||||
|
||||
const TypeProxmoxTaskPoll = "proxmox.task.poll"
|
||||
|
||||
var ErrClusterNotFound = errors.New("cluster not found")
|
||||
|
||||
type ProxmoxTaskPollPayload struct {
|
||||
ClusterID string `json:"cluster_id"`
|
||||
Node string `json:"node"`
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"forgejo.digital-droplets.de/philschlo/proxui/platform/cluster"
|
||||
"forgejo.digital-droplets.de/philschlo/proxui/platform/proxmox"
|
||||
)
|
||||
|
||||
type ClusterRepository interface {
|
||||
GetCluster(ctx context.Context, id string) (cluster.Cluster, bool, error)
|
||||
}
|
||||
|
||||
type ProxmoxTaskClientFactory func(cluster cluster.Cluster) (PlatformProxmoxTaskClient, error)
|
||||
|
||||
type PlatformProxmoxTaskClient interface {
|
||||
GetTaskStatus(ctx context.Context, node string, upid string) (proxmox.TaskStatus, error)
|
||||
}
|
||||
|
||||
type ClusterTaskClientResolver struct {
|
||||
clusters ClusterRepository
|
||||
factory ProxmoxTaskClientFactory
|
||||
}
|
||||
|
||||
func NewClusterTaskClientResolver(clusters ClusterRepository, factory ProxmoxTaskClientFactory) ClusterTaskClientResolver {
|
||||
return ClusterTaskClientResolver{
|
||||
clusters: clusters,
|
||||
factory: factory,
|
||||
}
|
||||
}
|
||||
|
||||
func NewDefaultClusterTaskClientResolver(clusters ClusterRepository) ClusterTaskClientResolver {
|
||||
return NewClusterTaskClientResolver(clusters, func(cluster cluster.Cluster) (PlatformProxmoxTaskClient, error) {
|
||||
return proxmox.NewClient(cluster)
|
||||
})
|
||||
}
|
||||
|
||||
func (r ClusterTaskClientResolver) ResolveTaskClient(ctx context.Context, clusterID string) (ProxmoxTaskClient, error) {
|
||||
cluster, found, err := r.clusters.GetCluster(ctx, clusterID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !found {
|
||||
return nil, ErrClusterNotFound
|
||||
}
|
||||
|
||||
client, err := r.factory(cluster)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return platformTaskClientAdapter{client: client}, nil
|
||||
}
|
||||
|
||||
type platformTaskClientAdapter struct {
|
||||
client PlatformProxmoxTaskClient
|
||||
}
|
||||
|
||||
func (a platformTaskClientAdapter) GetTaskStatus(ctx context.Context, node string, upid string) (ProxmoxTaskStatus, error) {
|
||||
status, err := a.client.GetTaskStatus(ctx, node, upid)
|
||||
if err != nil {
|
||||
return ProxmoxTaskStatus{}, err
|
||||
}
|
||||
|
||||
return ProxmoxTaskStatus{
|
||||
Status: status.Status,
|
||||
ExitStatus: status.ExitStatus,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"forgejo.digital-droplets.de/philschlo/proxui/platform/cluster"
|
||||
"forgejo.digital-droplets.de/philschlo/proxui/platform/proxmox"
|
||||
)
|
||||
|
||||
func TestClusterTaskClientResolverLoadsClusterAndAdaptsStatus(t *testing.T) {
|
||||
repository := &stubClusterRepository{
|
||||
cluster: cluster.Cluster{
|
||||
ID: "cluster-1",
|
||||
APIEndpoint: "https://pve.example.test:8006",
|
||||
TLSFingerprint: "fingerprint",
|
||||
TokenID: "root@pam!proxui",
|
||||
TokenSecret: "secret-token",
|
||||
},
|
||||
found: true,
|
||||
}
|
||||
factoryClient := &stubPlatformTaskClient{
|
||||
status: proxmox.TaskStatus{Status: "stopped", ExitStatus: "OK"},
|
||||
}
|
||||
resolver := NewClusterTaskClientResolver(repository, func(cluster cluster.Cluster) (PlatformProxmoxTaskClient, error) {
|
||||
if cluster.ID != "cluster-1" {
|
||||
t.Fatalf("cluster ID = %q, want cluster-1", cluster.ID)
|
||||
}
|
||||
return factoryClient, nil
|
||||
})
|
||||
|
||||
client, err := resolver.ResolveTaskClient(context.Background(), "cluster-1")
|
||||
if err != nil {
|
||||
t.Fatalf("ResolveTaskClient() error = %v", err)
|
||||
}
|
||||
|
||||
status, err := client.GetTaskStatus(context.Background(), "pve", "UPID:pve:1")
|
||||
if err != nil {
|
||||
t.Fatalf("GetTaskStatus() error = %v", err)
|
||||
}
|
||||
if status.Status != "stopped" || status.ExitStatus != "OK" {
|
||||
t.Fatalf("status = %+v, want stopped/OK", status)
|
||||
}
|
||||
if repository.id != "cluster-1" {
|
||||
t.Fatalf("repository id = %q, want cluster-1", repository.id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClusterTaskClientResolverReturnsClusterNotFound(t *testing.T) {
|
||||
resolver := NewClusterTaskClientResolver(&stubClusterRepository{}, nil)
|
||||
|
||||
if _, err := resolver.ResolveTaskClient(context.Background(), "missing"); !errors.Is(err, ErrClusterNotFound) {
|
||||
t.Fatalf("ResolveTaskClient() error = %v, want ErrClusterNotFound", err)
|
||||
}
|
||||
}
|
||||
|
||||
type stubClusterRepository struct {
|
||||
id string
|
||||
cluster cluster.Cluster
|
||||
found bool
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *stubClusterRepository) GetCluster(_ context.Context, id string) (cluster.Cluster, bool, error) {
|
||||
s.id = id
|
||||
return s.cluster, s.found, s.err
|
||||
}
|
||||
|
||||
type stubPlatformTaskClient struct {
|
||||
status proxmox.TaskStatus
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *stubPlatformTaskClient) GetTaskStatus(context.Context, string, string) (proxmox.TaskStatus, error) {
|
||||
return s.status, s.err
|
||||
}
|
||||
Reference in New Issue
Block a user