mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-02-18 00:17:39 +01:00
feat: add PBS/PMG stubs to test harness and implement HTTP config fetch
Resolves two remaining TODOs from codebase audit.
## 1. PBS/PMG Test Harness Stubs
**Location:** internal/monitoring/harness_integration.go:149-151
**Changes:**
- Added PBS client stub registration: `monitor.pbsClients[inst.Name] = &pbs.Client{}`
- Added PMG client stub registration: `monitor.pmgClients[inst.Name] = &pmg.Client{}`
- Added imports for pkg/pbs and pkg/pmg
**Purpose:**
Enables integration test scenarios to include PBS and PMG instance types
alongside existing PVE support. Stubs allow scheduler to register and
execute tasks for these instance types during integration testing.
**Testing:**
✅ TestAdaptiveSchedulerIntegration passes (55.5s)
✅ Integration test harness now supports all three instance types
## 2. HTTP Config URL Fetch
**Location:** cmd/pulse/config.go:226-261
**Problem:**
`PULSE_INIT_CONFIG_URL` was recognized but not implemented, returning
"URL import not yet implemented" error.
**Implementation:**
- URL validation (http/https schemes only)
- HTTP client with 15 second timeout
- Status code validation (2xx required)
- Empty response detection
- Base64 decoding with fallback to raw data
- Matches existing env-var behavior for `PULSE_INIT_CONFIG_DATA`
**Security:**
- Both HTTP and HTTPS supported (HTTPS recommended for production)
- URL scheme validation prevents file:// or other protocols
- Timeout prevents hanging on unresponsive servers
**Usage:**
```bash
export PULSE_INIT_CONFIG_URL="https://config-server/encrypted-config"
export PULSE_INIT_CONFIG_PASSPHRASE="secret"
pulse config auto-import
```
**Testing:**
✅ Code compiles cleanly
✅ Follows same pattern as existing PULSE_INIT_CONFIG_DATA handling
## Impact
- Completes integration test infrastructure for all instance types
- Enables automated config distribution via HTTP(S) for container deployments
- Removes last TODOs from codebase (no TODO/FIXME remaining in Go files)
This commit is contained in:
@@ -5,9 +5,12 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -223,8 +226,39 @@ var configAutoImportCmd = &cobra.Command{
|
||||
|
||||
// Get data from URL or direct data
|
||||
if configURL != "" {
|
||||
// TODO: Implement HTTP fetch for config URL
|
||||
return fmt.Errorf("URL import not yet implemented")
|
||||
parsedURL, err := url.Parse(configURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid PULSE_INIT_CONFIG_URL: %w", err)
|
||||
}
|
||||
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
|
||||
return fmt.Errorf("unsupported URL scheme %q for PULSE_INIT_CONFIG_URL", parsedURL.Scheme)
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 15 * time.Second}
|
||||
resp, err := client.Get(configURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch configuration from URL: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("failed to fetch configuration from URL: %s", resp.Status)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read configuration response: %w", err)
|
||||
}
|
||||
if len(body) == 0 {
|
||||
return fmt.Errorf("configuration response from URL was empty")
|
||||
}
|
||||
|
||||
trimmed := strings.TrimSpace(string(body))
|
||||
if decoded, err := base64.StdEncoding.DecodeString(trimmed); err == nil {
|
||||
encryptedData = string(decoded)
|
||||
} else {
|
||||
encryptedData = string(body)
|
||||
}
|
||||
} else if configData != "" {
|
||||
// Decode base64 if needed
|
||||
if decoded, err := base64.StdEncoding.DecodeString(configData); err == nil {
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||
"github.com/rcourtman/pulse-go-rewrite/pkg/pbs"
|
||||
"github.com/rcourtman/pulse-go-rewrite/pkg/pmg"
|
||||
"github.com/rcourtman/pulse-go-rewrite/pkg/proxmox"
|
||||
)
|
||||
|
||||
@@ -80,29 +82,29 @@ type ResourceStats struct {
|
||||
|
||||
// HarnessReport is returned after a harness run completes.
|
||||
type HarnessReport struct {
|
||||
Scenario HarnessScenario
|
||||
PerInstanceStats map[string]InstanceStats
|
||||
QueueStats QueueStats
|
||||
StalenessStats StalenessStats
|
||||
ResourceStats ResourceStats
|
||||
Health SchedulerHealthResponse
|
||||
MaxStaleness time.Duration
|
||||
RuntimeSamples []runtimeSnapshot
|
||||
Scenario HarnessScenario
|
||||
PerInstanceStats map[string]InstanceStats
|
||||
QueueStats QueueStats
|
||||
StalenessStats StalenessStats
|
||||
ResourceStats ResourceStats
|
||||
Health SchedulerHealthResponse
|
||||
MaxStaleness time.Duration
|
||||
RuntimeSamples []runtimeSnapshot
|
||||
}
|
||||
|
||||
// Harness orchestrates the integration run.
|
||||
type Harness struct {
|
||||
Monitor *Monitor
|
||||
Executor *fakeExecutor
|
||||
cancel context.CancelFunc
|
||||
scenario HarnessScenario
|
||||
dataPath string
|
||||
queueMax int
|
||||
queueSum int
|
||||
queueSamples int
|
||||
maxStaleness time.Duration
|
||||
sampleEvery time.Duration
|
||||
runtimeSamples []runtimeSnapshot
|
||||
Monitor *Monitor
|
||||
Executor *fakeExecutor
|
||||
cancel context.CancelFunc
|
||||
scenario HarnessScenario
|
||||
dataPath string
|
||||
queueMax int
|
||||
queueSum int
|
||||
queueSamples int
|
||||
maxStaleness time.Duration
|
||||
sampleEvery time.Duration
|
||||
runtimeSamples []runtimeSnapshot
|
||||
lastRuntimeSample time.Time
|
||||
}
|
||||
|
||||
@@ -146,9 +148,9 @@ func NewHarness(scenario HarnessScenario) *Harness {
|
||||
case "pve":
|
||||
monitor.pveClients[inst.Name] = noopPVEClient{}
|
||||
case "pbs":
|
||||
// TODO: add PBS stub when needed.
|
||||
monitor.pbsClients[inst.Name] = &pbs.Client{}
|
||||
case "pmg":
|
||||
// TODO: add PMG stub when needed.
|
||||
monitor.pmgClients[inst.Name] = &pmg.Client{}
|
||||
default:
|
||||
// Unsupported types are ignored for now.
|
||||
}
|
||||
@@ -251,12 +253,12 @@ loop:
|
||||
GCCountStart: startSample.NumGC,
|
||||
GCCountEnd: endSample.NumGC,
|
||||
},
|
||||
Health: health,
|
||||
MaxStaleness: h.maxStaleness,
|
||||
Health: health,
|
||||
MaxStaleness: h.maxStaleness,
|
||||
RuntimeSamples: runtimeSamplesCopy,
|
||||
}
|
||||
|
||||
return report
|
||||
return report
|
||||
}
|
||||
|
||||
func (h *Harness) schedule(now time.Time) {
|
||||
@@ -386,9 +388,11 @@ func (noopPVEClient) GetVMs(ctx context.Context, node string) ([]proxmox.VM, err
|
||||
func (noopPVEClient) GetContainers(ctx context.Context, node string) ([]proxmox.Container, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (noopPVEClient) GetStorage(ctx context.Context, node string) ([]proxmox.Storage, error) { return nil, nil }
|
||||
func (noopPVEClient) GetAllStorage(ctx context.Context) ([]proxmox.Storage, error) { return nil, nil }
|
||||
func (noopPVEClient) GetBackupTasks(ctx context.Context) ([]proxmox.Task, error) { return nil, nil }
|
||||
func (noopPVEClient) GetStorage(ctx context.Context, node string) ([]proxmox.Storage, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (noopPVEClient) GetAllStorage(ctx context.Context) ([]proxmox.Storage, error) { return nil, nil }
|
||||
func (noopPVEClient) GetBackupTasks(ctx context.Context) ([]proxmox.Task, error) { return nil, nil }
|
||||
func (noopPVEClient) GetStorageContent(ctx context.Context, node, storage string) ([]proxmox.StorageContent, error) {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -423,6 +427,8 @@ func (noopPVEClient) GetZFSPoolStatus(ctx context.Context, node string) ([]proxm
|
||||
func (noopPVEClient) GetZFSPoolsWithDetails(ctx context.Context, node string) ([]proxmox.ZFSPoolInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (noopPVEClient) GetDisks(ctx context.Context, node string) ([]proxmox.Disk, error) { return nil, nil }
|
||||
func (noopPVEClient) GetCephStatus(ctx context.Context) (*proxmox.CephStatus, error) { return nil, nil }
|
||||
func (noopPVEClient) GetCephDF(ctx context.Context) (*proxmox.CephDF, error) { return nil, nil }
|
||||
func (noopPVEClient) GetDisks(ctx context.Context, node string) ([]proxmox.Disk, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (noopPVEClient) GetCephStatus(ctx context.Context) (*proxmox.CephStatus, error) { return nil, nil }
|
||||
func (noopPVEClient) GetCephDF(ctx context.Context) (*proxmox.CephDF, error) { return nil, nil }
|
||||
|
||||
Reference in New Issue
Block a user