network/pkg/gateway/admin_handlers.go
anonpenguin23 4d05ae696b
Add admin handlers for database creation and metadata management
- Introduced `admin_handlers.go` to handle database creation requests via HTTP, including validation and response handling.
- Implemented `db_metadata.go` to manage database metadata caching and synchronization with a pubsub subscriber.
- Updated `gateway.go` to initialize the metadata cache and start the metadata subscriber in the background.
- Added new route for database creation in `routes.go` to expose the new functionality.
- Enhanced cluster management to support system database auto-joining and improved metadata handling for database operations.
2025-10-16 10:56:59 +03:00

137 lines
4.0 KiB
Go

package gateway
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/DeBrosOfficial/network/pkg/logging"
"github.com/DeBrosOfficial/network/pkg/rqlite"
"go.uber.org/zap"
)
// CreateDatabaseRequest is the request body for database creation
type CreateDatabaseRequest struct {
Database string `json:"database"`
ReplicationFactor int `json:"replication_factor,omitempty"` // defaults to 3
}
// CreateDatabaseResponse is the response for database creation
type CreateDatabaseResponse struct {
Status string `json:"status"`
Database string `json:"database"`
Message string `json:"message,omitempty"`
Error string `json:"error,omitempty"`
}
// databaseCreateHandler handles database creation requests via pubsub
func (g *Gateway) databaseCreateHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req CreateDatabaseRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
g.respondJSON(w, http.StatusBadRequest, CreateDatabaseResponse{
Status: "error",
Error: "Invalid request body",
})
return
}
if req.Database == "" {
g.respondJSON(w, http.StatusBadRequest, CreateDatabaseResponse{
Status: "error",
Error: "database field is required",
})
return
}
// Default replication factor
if req.ReplicationFactor == 0 {
req.ReplicationFactor = 3
}
// Check if database already exists in metadata
if existing := g.dbMetaCache.Get(req.Database); existing != nil {
g.respondJSON(w, http.StatusConflict, CreateDatabaseResponse{
Status: "exists",
Database: req.Database,
Message: "Database already exists",
})
return
}
g.logger.ComponentInfo(logging.ComponentGeneral, "Creating database via gateway",
zap.String("database", req.Database),
zap.Int("replication_factor", req.ReplicationFactor))
// Publish DATABASE_CREATE_REQUEST via pubsub
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
// We need to get the node ID to act as requester
// For now, use a placeholder - the actual node will coordinate
createReq := rqlite.DatabaseCreateRequest{
DatabaseName: req.Database,
RequesterNodeID: "gateway", // Gateway is requesting on behalf of client
ReplicationFactor: req.ReplicationFactor,
}
msgData, err := rqlite.MarshalMetadataMessage(rqlite.MsgDatabaseCreateRequest, "gateway", createReq)
if err != nil {
g.logger.ComponentError(logging.ComponentGeneral, "Failed to marshal create request",
zap.Error(err))
g.respondJSON(w, http.StatusInternalServerError, CreateDatabaseResponse{
Status: "error",
Error: fmt.Sprintf("Failed to create database: %v", err),
})
return
}
// Publish to metadata topic
metadataTopic := "/debros/metadata/v1"
if err := g.client.PubSub().Publish(ctx, metadataTopic, msgData); err != nil {
g.logger.ComponentError(logging.ComponentGeneral, "Failed to publish create request",
zap.Error(err))
g.respondJSON(w, http.StatusInternalServerError, CreateDatabaseResponse{
Status: "error",
Error: fmt.Sprintf("Failed to publish create request: %v", err),
})
return
}
// Wait briefly for metadata sync (3 seconds)
waitCtx, waitCancel := context.WithTimeout(ctx, 3*time.Second)
defer waitCancel()
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-waitCtx.Done():
// Timeout - database creation is async, return accepted status
g.respondJSON(w, http.StatusAccepted, CreateDatabaseResponse{
Status: "accepted",
Database: req.Database,
Message: "Database creation initiated, it may take a few seconds to become available",
})
return
case <-ticker.C:
// Check if metadata arrived
if metadata := g.dbMetaCache.Get(req.Database); metadata != nil {
g.respondJSON(w, http.StatusOK, CreateDatabaseResponse{
Status: "created",
Database: req.Database,
Message: "Database created successfully",
})
return
}
}
}
}