- Updated NodeDiscover to accept an optional subnet parameter, with auto-detection of local networks if none is provided. - Removed support for IP list format in NodeDiscover request body. - Implemented discovery cancellation functionality with NodeDiscoveryCancel endpoint. - Improved error handling and response messages for better clarity. feat(cluster): Add operation tracking for cluster bootstrap process - Integrated operations manager into cluster manager for tracking bootstrap progress. - Refactored Bootstrap method to run asynchronously with detailed progress updates. - Added methods to wait for various bootstrap steps (etcd health, VIP assignment, control plane readiness, etc.). fix(discovery): Optimize node discovery process and improve maintenance mode detection - Enhanced node discovery to run in parallel with a semaphore to limit concurrent scans. - Updated probeNode to detect maintenance mode more reliably. - Added functions to expand CIDR notation into individual IP addresses and retrieve local network interfaces. refactor(node): Update node manager to handle instance-specific configurations - Modified NewManager to accept instanceName for tailored talosconfig usage. - Improved hardware detection logic to handle maintenance mode scenarios. feat(operations): Implement detailed bootstrap progress tracking - Introduced BootstrapProgress struct to track and report the status of bootstrap operations. - Updated operation management to include bootstrap-specific details. fix(tools): Improve talosctl command execution with context and error handling - Added context with timeout to talosctl commands to prevent hanging on unreachable nodes. - Enhanced error handling for version retrieval in maintenance mode.
320 lines
9.2 KiB
Go
320 lines
9.2 KiB
Go
package v1
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
"github.com/wild-cloud/wild-central/daemon/internal/cluster"
|
|
"github.com/wild-cloud/wild-central/daemon/internal/operations"
|
|
)
|
|
|
|
// ClusterGenerateConfig generates cluster configuration
|
|
func (api *API) ClusterGenerateConfig(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
instanceName := vars["name"]
|
|
|
|
// Validate instance exists
|
|
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
|
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
|
return
|
|
}
|
|
|
|
// Read cluster configuration from instance config
|
|
configPath := api.instance.GetInstanceConfigPath(instanceName)
|
|
|
|
// Get cluster.name
|
|
clusterName, err := api.config.GetConfigValue(configPath, "cluster.name")
|
|
if err != nil || clusterName == "" {
|
|
respondError(w, http.StatusBadRequest, "cluster.name not set in config")
|
|
return
|
|
}
|
|
|
|
// Get cluster.nodes.control.vip
|
|
vip, err := api.config.GetConfigValue(configPath, "cluster.nodes.control.vip")
|
|
if err != nil || vip == "" {
|
|
respondError(w, http.StatusBadRequest, "cluster.nodes.control.vip not set in config")
|
|
return
|
|
}
|
|
|
|
// Get cluster.nodes.talos.version (optional, defaults to v1.11.0)
|
|
version, err := api.config.GetConfigValue(configPath, "cluster.nodes.talos.version")
|
|
if err != nil || version == "" {
|
|
version = "v1.11.0"
|
|
}
|
|
|
|
// Create cluster config
|
|
clusterConfig := cluster.ClusterConfig{
|
|
ClusterName: clusterName,
|
|
VIP: vip,
|
|
Version: version,
|
|
}
|
|
|
|
// Generate configuration
|
|
clusterMgr := cluster.NewManager(api.dataDir, api.opsMgr)
|
|
if err := clusterMgr.GenerateConfig(instanceName, &clusterConfig); err != nil {
|
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to generate config: %v", err))
|
|
return
|
|
}
|
|
|
|
respondJSON(w, http.StatusOK, map[string]string{
|
|
"message": "Cluster configuration generated successfully",
|
|
})
|
|
}
|
|
|
|
// ClusterBootstrap bootstraps the cluster
|
|
func (api *API) ClusterBootstrap(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
instanceName := vars["name"]
|
|
|
|
// Validate instance exists
|
|
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
|
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
|
return
|
|
}
|
|
|
|
// Parse request
|
|
var req struct {
|
|
Node string `json:"node"`
|
|
}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
respondError(w, http.StatusBadRequest, "Invalid request body")
|
|
return
|
|
}
|
|
|
|
if req.Node == "" {
|
|
respondError(w, http.StatusBadRequest, "node is required")
|
|
return
|
|
}
|
|
|
|
// Bootstrap with progress tracking
|
|
clusterMgr := cluster.NewManager(api.dataDir, api.opsMgr)
|
|
opID, err := clusterMgr.Bootstrap(instanceName, req.Node)
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to start bootstrap: %v", err))
|
|
return
|
|
}
|
|
|
|
respondJSON(w, http.StatusAccepted, map[string]string{
|
|
"operation_id": opID,
|
|
"message": "Bootstrap initiated",
|
|
})
|
|
}
|
|
|
|
// ClusterConfigureEndpoints configures talosconfig endpoints to use VIP
|
|
func (api *API) ClusterConfigureEndpoints(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
instanceName := vars["name"]
|
|
|
|
// Validate instance exists
|
|
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
|
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
|
return
|
|
}
|
|
|
|
// Parse request
|
|
var req struct {
|
|
IncludeNodes bool `json:"include_nodes"`
|
|
}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
// Default to false if no body provided
|
|
req.IncludeNodes = false
|
|
}
|
|
|
|
// Configure endpoints
|
|
clusterMgr := cluster.NewManager(api.dataDir, api.opsMgr)
|
|
if err := clusterMgr.ConfigureEndpoints(instanceName, req.IncludeNodes); err != nil {
|
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to configure endpoints: %v", err))
|
|
return
|
|
}
|
|
|
|
respondJSON(w, http.StatusOK, map[string]string{
|
|
"message": "Endpoints configured successfully",
|
|
})
|
|
}
|
|
|
|
// ClusterGetStatus returns cluster status
|
|
func (api *API) ClusterGetStatus(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
instanceName := vars["name"]
|
|
|
|
// Validate instance exists
|
|
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
|
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
|
return
|
|
}
|
|
|
|
// Get status
|
|
clusterMgr := cluster.NewManager(api.dataDir, api.opsMgr)
|
|
status, err := clusterMgr.GetStatus(instanceName)
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get status: %v", err))
|
|
return
|
|
}
|
|
|
|
respondJSON(w, http.StatusOK, status)
|
|
}
|
|
|
|
// ClusterHealth returns cluster health checks
|
|
func (api *API) ClusterHealth(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
instanceName := vars["name"]
|
|
|
|
// Validate instance exists
|
|
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
|
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
|
return
|
|
}
|
|
|
|
// Get health checks
|
|
clusterMgr := cluster.NewManager(api.dataDir, api.opsMgr)
|
|
checks, err := clusterMgr.Health(instanceName)
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get health: %v", err))
|
|
return
|
|
}
|
|
|
|
// Determine overall status
|
|
overallStatus := "healthy"
|
|
for _, check := range checks {
|
|
if check.Status == "failing" {
|
|
overallStatus = "unhealthy"
|
|
break
|
|
} else if check.Status == "warning" && overallStatus == "healthy" {
|
|
overallStatus = "degraded"
|
|
}
|
|
}
|
|
|
|
respondJSON(w, http.StatusOK, map[string]interface{}{
|
|
"status": overallStatus,
|
|
"checks": checks,
|
|
})
|
|
}
|
|
|
|
// ClusterGetKubeconfig returns the kubeconfig
|
|
func (api *API) ClusterGetKubeconfig(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
instanceName := vars["name"]
|
|
|
|
// Validate instance exists
|
|
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
|
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
|
return
|
|
}
|
|
|
|
// Get kubeconfig
|
|
clusterMgr := cluster.NewManager(api.dataDir, api.opsMgr)
|
|
kubeconfig, err := clusterMgr.GetKubeconfig(instanceName)
|
|
if err != nil {
|
|
respondError(w, http.StatusNotFound, fmt.Sprintf("Kubeconfig not found: %v", err))
|
|
return
|
|
}
|
|
|
|
respondJSON(w, http.StatusOK, map[string]string{
|
|
"kubeconfig": kubeconfig,
|
|
})
|
|
}
|
|
|
|
// ClusterGenerateKubeconfig regenerates the kubeconfig from the cluster
|
|
func (api *API) ClusterGenerateKubeconfig(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
instanceName := vars["name"]
|
|
|
|
// Validate instance exists
|
|
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
|
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
|
return
|
|
}
|
|
|
|
// Regenerate kubeconfig from cluster
|
|
clusterMgr := cluster.NewManager(api.dataDir, api.opsMgr)
|
|
if err := clusterMgr.RegenerateKubeconfig(instanceName); err != nil {
|
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to generate kubeconfig: %v", err))
|
|
return
|
|
}
|
|
|
|
respondJSON(w, http.StatusOK, map[string]string{
|
|
"message": "Kubeconfig regenerated successfully",
|
|
})
|
|
}
|
|
|
|
// ClusterGetTalosconfig returns the talosconfig
|
|
func (api *API) ClusterGetTalosconfig(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
instanceName := vars["name"]
|
|
|
|
// Validate instance exists
|
|
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
|
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
|
return
|
|
}
|
|
|
|
// Get talosconfig
|
|
clusterMgr := cluster.NewManager(api.dataDir, api.opsMgr)
|
|
talosconfig, err := clusterMgr.GetTalosconfig(instanceName)
|
|
if err != nil {
|
|
respondError(w, http.StatusNotFound, fmt.Sprintf("Talosconfig not found: %v", err))
|
|
return
|
|
}
|
|
|
|
respondJSON(w, http.StatusOK, map[string]string{
|
|
"talosconfig": talosconfig,
|
|
})
|
|
}
|
|
|
|
// ClusterReset resets the cluster
|
|
func (api *API) ClusterReset(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
instanceName := vars["name"]
|
|
|
|
// Validate instance exists
|
|
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
|
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
|
return
|
|
}
|
|
|
|
// Parse request
|
|
var req struct {
|
|
Confirm bool `json:"confirm"`
|
|
}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
respondError(w, http.StatusBadRequest, "Invalid request body")
|
|
return
|
|
}
|
|
|
|
if !req.Confirm {
|
|
respondError(w, http.StatusBadRequest, "Must confirm cluster reset")
|
|
return
|
|
}
|
|
|
|
// Start reset operation
|
|
opsMgr := operations.NewManager(api.dataDir)
|
|
opID, err := opsMgr.Start(instanceName, "reset", instanceName)
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to start operation: %v", err))
|
|
return
|
|
}
|
|
|
|
// Reset in background
|
|
go func() {
|
|
clusterMgr := cluster.NewManager(api.dataDir, api.opsMgr)
|
|
_ = opsMgr.UpdateStatus(instanceName, opID, "running")
|
|
|
|
if err := clusterMgr.Reset(instanceName, req.Confirm); err != nil {
|
|
_ = opsMgr.Update(instanceName, opID, "failed", err.Error(), 0)
|
|
} else {
|
|
_ = opsMgr.Update(instanceName, opID, "completed", "Cluster reset completed", 100)
|
|
}
|
|
}()
|
|
|
|
respondJSON(w, http.StatusAccepted, map[string]string{
|
|
"operation_id": opID,
|
|
"message": "Cluster reset initiated",
|
|
})
|
|
}
|