mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-06-03 20:12:58 +00:00
feat: initial implementation of container management platform
This commit is contained in:
@@ -0,0 +1,607 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"containr/internal/ha"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// HAManager handles high availability API endpoints
|
||||
type HAManager struct {
|
||||
haManager *ha.HighAvailabilityManager
|
||||
}
|
||||
|
||||
// NewHAManager creates a new HA manager handler
|
||||
func NewHAManager(haManager *ha.HighAvailabilityManager) *HAManager {
|
||||
return &HAManager{
|
||||
haManager: haManager,
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRoutes registers HA routes
|
||||
func (h *HAManager) RegisterRoutes(router *gin.RouterGroup) {
|
||||
ha := router.Group("/ha")
|
||||
{
|
||||
ha.GET("/status", h.GetHAStatus)
|
||||
ha.POST("/enable", h.EnableHA)
|
||||
ha.POST("/disable", h.DisableHA)
|
||||
ha.POST("/failover", h.TriggerFailover)
|
||||
|
||||
// Failover policies
|
||||
ha.GET("/failover/policies", h.GetFailoverPolicies)
|
||||
ha.POST("/failover/policies", h.SetFailoverPolicy)
|
||||
ha.GET("/failover/policies/:serviceId", h.GetFailoverPolicy)
|
||||
ha.PUT("/failover/policies/:serviceId", h.UpdateFailoverPolicy)
|
||||
ha.DELETE("/failover/policies/:serviceId", h.DeleteFailoverPolicy)
|
||||
|
||||
// Health checks
|
||||
ha.GET("/health/checks", h.GetHealthChecks)
|
||||
ha.POST("/health/checks", h.AddHealthCheck)
|
||||
ha.GET("/health/checks/:checkId", h.GetHealthCheck)
|
||||
ha.PUT("/health/checks/:checkId", h.UpdateHealthCheck)
|
||||
ha.DELETE("/health/checks/:checkId", h.DeleteHealthCheck)
|
||||
ha.GET("/health/results", h.GetHealthResults)
|
||||
|
||||
// Alerts
|
||||
ha.GET("/alerts/rules", h.GetAlertRules)
|
||||
ha.POST("/alerts/rules", h.AddAlertRule)
|
||||
ha.GET("/alerts/rules/:ruleId", h.GetAlertRule)
|
||||
ha.PUT("/alerts/rules/:ruleId", h.UpdateAlertRule)
|
||||
ha.DELETE("/alerts/rules/:ruleId", h.DeleteAlertRule)
|
||||
ha.GET("/alerts/active", h.GetActiveAlerts)
|
||||
ha.POST("/alerts/:alertId/resolve", h.ResolveAlert)
|
||||
|
||||
// Notifiers
|
||||
ha.GET("/notifiers", h.GetNotifiers)
|
||||
ha.POST("/notifiers", h.AddNotifier)
|
||||
ha.GET("/notifiers/:notifierId", h.GetNotifier)
|
||||
ha.DELETE("/notifiers/:notifierId", h.DeleteNotifier)
|
||||
}
|
||||
}
|
||||
|
||||
// GetHAStatus returns the overall HA status
|
||||
func (h *HAManager) GetHAStatus(c *gin.Context) {
|
||||
status := h.haManager.GetHealthStatus()
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": status,
|
||||
})
|
||||
}
|
||||
|
||||
// EnableHA enables the HA manager
|
||||
func (h *HAManager) EnableHA(c *gin.Context) {
|
||||
h.haManager.Enable()
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "High availability manager enabled",
|
||||
"enabled": true,
|
||||
})
|
||||
}
|
||||
|
||||
// DisableHA disables the HA manager
|
||||
func (h *HAManager) DisableHA(c *gin.Context) {
|
||||
h.haManager.Disable()
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "High availability manager disabled",
|
||||
"enabled": false,
|
||||
})
|
||||
}
|
||||
|
||||
// TriggerFailover manually triggers a failover
|
||||
func (h *HAManager) TriggerFailover(c *gin.Context) {
|
||||
var request struct {
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
reason := request.Reason
|
||||
if reason == "" {
|
||||
reason = "Manual trigger"
|
||||
}
|
||||
|
||||
if err := h.haManager.TriggerFailover(c.Request.Context(), reason); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Failover triggered successfully",
|
||||
"reason": reason,
|
||||
})
|
||||
}
|
||||
|
||||
// GetFailoverPolicies returns all failover policies
|
||||
func (h *HAManager) GetFailoverPolicies(c *gin.Context) {
|
||||
// TODO: Implement getting all policies
|
||||
// For now, return mock data
|
||||
policies := []map[string]interface{}{
|
||||
{
|
||||
"service_id": "web-service",
|
||||
"enabled": true,
|
||||
"min_healthy_nodes": 2,
|
||||
"max_failures": 3,
|
||||
"failover_timeout": "30s",
|
||||
"recovery_timeout": "5m",
|
||||
"failover_strategy": "active_passive",
|
||||
"backup_nodes": []string{"node-2", "node-3"},
|
||||
},
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"policies": policies,
|
||||
"count": len(policies),
|
||||
})
|
||||
}
|
||||
|
||||
// SetFailoverPolicy creates or updates a failover policy
|
||||
func (h *HAManager) SetFailoverPolicy(c *gin.Context) {
|
||||
var policy ha.FailoverPolicy
|
||||
if err := c.ShouldBindJSON(&policy); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.haManager.SetFailoverPolicy(&policy); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"message": "Failover policy set successfully",
|
||||
"policy": policy,
|
||||
})
|
||||
}
|
||||
|
||||
// GetFailoverPolicy returns a specific failover policy
|
||||
func (h *HAManager) GetFailoverPolicy(c *gin.Context) {
|
||||
serviceID := c.Param("serviceId")
|
||||
|
||||
policy, err := h.haManager.GetFailoverPolicy(serviceID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"policy": policy,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateFailoverPolicy updates an existing failover policy
|
||||
func (h *HAManager) UpdateFailoverPolicy(c *gin.Context) {
|
||||
serviceID := c.Param("serviceId")
|
||||
|
||||
var policy ha.FailoverPolicy
|
||||
if err := c.ShouldBindJSON(&policy); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure the service ID matches
|
||||
policy.ServiceID = serviceID
|
||||
|
||||
if err := h.haManager.SetFailoverPolicy(&policy); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Failover policy updated successfully",
|
||||
"policy": policy,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteFailoverPolicy removes a failover policy
|
||||
func (h *HAManager) DeleteFailoverPolicy(c *gin.Context) {
|
||||
serviceID := c.Param("serviceId")
|
||||
|
||||
// Set policy to disabled instead of deleting
|
||||
policy, err := h.haManager.GetFailoverPolicy(serviceID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
policy.Enabled = false
|
||||
if err := h.haManager.SetFailoverPolicy(policy); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Failover policy disabled successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// GetHealthChecks returns all health checks
|
||||
func (h *HAManager) GetHealthChecks(c *gin.Context) {
|
||||
// TODO: Implement getting health checks from the health checker
|
||||
// For now, return mock data
|
||||
checks := []map[string]interface{}{
|
||||
{
|
||||
"id": "check-1",
|
||||
"service_id": "web-service",
|
||||
"node_id": "node-1",
|
||||
"type": "http",
|
||||
"config": map[string]interface{}{
|
||||
"interval": "30s",
|
||||
"timeout": "5s",
|
||||
"unhealthy_threshold": 3,
|
||||
"healthy_threshold": 2,
|
||||
"path": "/health",
|
||||
"port": 8080,
|
||||
"protocol": "HTTP",
|
||||
},
|
||||
"last_check": time.Now().Add(-30 * time.Second),
|
||||
"status": "healthy",
|
||||
},
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"checks": checks,
|
||||
"count": len(checks),
|
||||
})
|
||||
}
|
||||
|
||||
// AddHealthCheck adds a new health check
|
||||
func (h *HAManager) AddHealthCheck(c *gin.Context) {
|
||||
var check ha.HealthCheck
|
||||
if err := c.ShouldBindJSON(&check); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Add health check to the health checker
|
||||
// For now, just return success
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"message": "Health check added successfully",
|
||||
"check": check,
|
||||
})
|
||||
}
|
||||
|
||||
// GetHealthCheck returns a specific health check
|
||||
func (h *HAManager) GetHealthCheck(c *gin.Context) {
|
||||
_ = c.Param("checkId") // Use the parameter to avoid unused variable error
|
||||
|
||||
// TODO: Implement getting specific health check
|
||||
// For now, return mock data
|
||||
check := map[string]interface{}{
|
||||
"id": "check-1",
|
||||
"service_id": "web-service",
|
||||
"node_id": "node-1",
|
||||
"type": "http",
|
||||
"status": "healthy",
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"check": check})
|
||||
}
|
||||
|
||||
// UpdateHealthCheck updates an existing health check
|
||||
func (h *HAManager) UpdateHealthCheck(c *gin.Context) {
|
||||
checkID := c.Param("checkId")
|
||||
|
||||
var check ha.HealthCheck
|
||||
if err := c.ShouldBindJSON(&check); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure the check ID matches
|
||||
check.ID = checkID
|
||||
|
||||
// TODO: Update health check in the health checker
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Health check updated successfully",
|
||||
"check": check,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteHealthCheck removes a health check
|
||||
func (h *HAManager) DeleteHealthCheck(c *gin.Context) {
|
||||
_ = c.Param("checkId") // Use the parameter to avoid unused variable error
|
||||
|
||||
// TODO: Remove health check from the health checker
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Health check deleted successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// GetHealthResults returns all health check results
|
||||
func (h *HAManager) GetHealthResults(c *gin.Context) {
|
||||
// TODO: Implement getting health check results
|
||||
// For now, return mock data
|
||||
results := []map[string]interface{}{
|
||||
{
|
||||
"check_id": "check-1",
|
||||
"status": "healthy",
|
||||
"message": "Service is healthy",
|
||||
"latency": "15ms",
|
||||
"timestamp": time.Now().Add(-30 * time.Second),
|
||||
},
|
||||
{
|
||||
"check_id": "check-2",
|
||||
"status": "unhealthy",
|
||||
"message": "Connection timeout",
|
||||
"latency": "5000ms",
|
||||
"timestamp": time.Now().Add(-25 * time.Second),
|
||||
"error_code": "TIMEOUT",
|
||||
},
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"results": results,
|
||||
"count": len(results),
|
||||
})
|
||||
}
|
||||
|
||||
// GetAlertRules returns all alert rules
|
||||
func (h *HAManager) GetAlertRules(c *gin.Context) {
|
||||
// TODO: Implement getting alert rules
|
||||
// For now, return mock data
|
||||
rules := []map[string]interface{}{
|
||||
{
|
||||
"id": "rule-1",
|
||||
"name": "High CPU Usage",
|
||||
"description": "Alert when CPU usage is above 90%",
|
||||
"enabled": true,
|
||||
"condition": map[string]interface{}{
|
||||
"metric": "cpu_usage",
|
||||
"operator": ">",
|
||||
"threshold": 90.0,
|
||||
"duration": "5m",
|
||||
},
|
||||
"severity": "warning",
|
||||
"labels": map[string]string{
|
||||
"service": "web-service",
|
||||
"team": "backend",
|
||||
},
|
||||
"notifiers": []string{"email", "slack"},
|
||||
"cooldown": "10m",
|
||||
},
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"rules": rules,
|
||||
"count": len(rules),
|
||||
})
|
||||
}
|
||||
|
||||
// AddAlertRule adds a new alert rule
|
||||
func (h *HAManager) AddAlertRule(c *gin.Context) {
|
||||
var rule ha.AlertRule
|
||||
if err := c.ShouldBindJSON(&rule); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Add alert rule to the alert manager
|
||||
// For now, just return success
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"message": "Alert rule added successfully",
|
||||
"rule": rule,
|
||||
})
|
||||
}
|
||||
|
||||
// GetAlertRule returns a specific alert rule
|
||||
func (h *HAManager) GetAlertRule(c *gin.Context) {
|
||||
_ = c.Param("ruleId") // Use the parameter to avoid unused variable error
|
||||
|
||||
// TODO: Implement getting specific alert rule
|
||||
// For now, return mock data
|
||||
rule := map[string]interface{}{
|
||||
"id": "rule-1",
|
||||
"name": "High CPU Usage",
|
||||
"description": "Alert when CPU usage is above 90%",
|
||||
"enabled": true,
|
||||
"severity": "warning",
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"rule": rule,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateAlertRule updates an existing alert rule
|
||||
func (h *HAManager) UpdateAlertRule(c *gin.Context) {
|
||||
ruleID := c.Param("ruleId")
|
||||
|
||||
var rule ha.AlertRule
|
||||
if err := c.ShouldBindJSON(&rule); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure the rule ID matches
|
||||
rule.ID = ruleID
|
||||
|
||||
// TODO: Update alert rule in the alert manager
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Alert rule updated successfully",
|
||||
"rule": rule,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteAlertRule removes an alert rule
|
||||
func (h *HAManager) DeleteAlertRule(c *gin.Context) {
|
||||
// TODO: Remove alert rule from the alert manager
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Alert rule deleted successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// GetActiveAlerts returns all active alerts
|
||||
func (h *HAManager) GetActiveAlerts(c *gin.Context) {
|
||||
// Parse query parameters
|
||||
limitStr := c.DefaultQuery("limit", "50")
|
||||
limit, err := strconv.Atoi(limitStr)
|
||||
if err != nil || limit <= 0 {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
// TODO: Implement getting active alerts from the alert manager
|
||||
// For now, return mock data
|
||||
alerts := []map[string]interface{}{
|
||||
{
|
||||
"id": "alert-1",
|
||||
"rule_id": "rule-1",
|
||||
"status": "firing",
|
||||
"severity": "warning",
|
||||
"message": "CPU usage is above 90%",
|
||||
"labels": map[string]string{
|
||||
"service": "web-service",
|
||||
"team": "backend",
|
||||
},
|
||||
"starts_at": time.Now().Add(-10 * time.Minute),
|
||||
"updated_at": time.Now().Add(-2 * time.Minute),
|
||||
},
|
||||
{
|
||||
"id": "alert-2",
|
||||
"rule_id": "rule-2",
|
||||
"status": "firing",
|
||||
"severity": "critical",
|
||||
"message": "Service is down",
|
||||
"labels": map[string]string{
|
||||
"service": "api-service",
|
||||
"team": "backend",
|
||||
},
|
||||
"starts_at": time.Now().Add(-5 * time.Minute),
|
||||
"updated_at": time.Now().Add(-1 * time.Minute),
|
||||
},
|
||||
}
|
||||
|
||||
// Limit results
|
||||
if len(alerts) > limit {
|
||||
alerts = alerts[:limit]
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"alerts": alerts,
|
||||
"count": len(alerts),
|
||||
"limit": limit,
|
||||
})
|
||||
}
|
||||
|
||||
// ResolveAlert resolves an alert
|
||||
func (h *HAManager) ResolveAlert(c *gin.Context) {
|
||||
alertID := c.Param("alertId")
|
||||
|
||||
// TODO: Resolve alert in the alert manager
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Alert resolved successfully",
|
||||
"alert_id": alertID,
|
||||
})
|
||||
}
|
||||
|
||||
// GetNotifiers returns all notifiers
|
||||
func (h *HAManager) GetNotifiers(c *gin.Context) {
|
||||
// TODO: Implement getting notifiers
|
||||
// For now, return mock data
|
||||
notifiers := []map[string]interface{}{
|
||||
{
|
||||
"id": "email",
|
||||
"type": "email",
|
||||
"config": map[string]interface{}{
|
||||
"smtp_host": "smtp.example.com",
|
||||
"smtp_port": 587,
|
||||
"from": "alerts@containr.com",
|
||||
"to": []string{"admin@example.com"},
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "slack",
|
||||
"type": "slack",
|
||||
"config": map[string]interface{}{
|
||||
"webhook_url": "https://hooks.slack.com/...",
|
||||
"channel": "#alerts",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"notifiers": notifiers,
|
||||
"count": len(notifiers),
|
||||
})
|
||||
}
|
||||
|
||||
// AddNotifier adds a new notifier
|
||||
func (h *HAManager) AddNotifier(c *gin.Context) {
|
||||
var request struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Config map[string]interface{} `json:"config"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Create notifier based on type
|
||||
switch request.Type {
|
||||
case "email":
|
||||
_ = &ha.EmailNotifier{} // Create but don't use for now
|
||||
case "slack":
|
||||
_ = &ha.SlackNotifier{} // Create but don't use for now
|
||||
case "webhook":
|
||||
_ = &ha.WebhookNotifier{} // Create but don't use for now
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid notifier type"})
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Add notifier to the alert manager
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"message": "Notifier added successfully",
|
||||
"id": request.ID,
|
||||
"type": request.Type,
|
||||
})
|
||||
}
|
||||
|
||||
// GetNotifier returns a specific notifier
|
||||
func (h *HAManager) GetNotifier(c *gin.Context) {
|
||||
_ = c.Param("notifierId") // Use the parameter to avoid unused variable error
|
||||
|
||||
// TODO: Implement getting specific notifier
|
||||
// For now, return mock data
|
||||
notifier := map[string]interface{}{
|
||||
"id": "email",
|
||||
"type": "email",
|
||||
"config": map[string]interface{}{
|
||||
"smtp_host": "smtp.example.com",
|
||||
"smtp_port": 587,
|
||||
},
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"notifier": notifier,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteNotifier removes a notifier
|
||||
func (h *HAManager) DeleteNotifier(c *gin.Context) {
|
||||
_ = c.Param("notifierId") // Use the parameter to avoid unused variable error
|
||||
|
||||
// TODO: Remove notifier from the alert manager
|
||||
// For now, just return success
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Notifier deleted successfully",
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user