This commit is contained in:
@@ -2,6 +2,7 @@ package httpapp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
@@ -287,47 +288,167 @@ func (a *App) handleDeleteZVOL(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Snapshot Handlers
|
||||
func (a *App) handleListSnapshots(w http.ResponseWriter, r *http.Request) {
|
||||
snapshots := []models.Snapshot{} // Stub
|
||||
dataset := r.URL.Query().Get("dataset")
|
||||
snapshots, err := a.zfs.ListSnapshots(dataset)
|
||||
if err != nil {
|
||||
log.Printf("list snapshots error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, snapshots)
|
||||
}
|
||||
|
||||
func (a *App) handleCreateSnapshot(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented"})
|
||||
var req struct {
|
||||
Dataset string `json:"dataset"`
|
||||
Name string `json:"name"`
|
||||
Recursive bool `json:"recursive,omitempty"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
if req.Dataset == "" || req.Name == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "dataset and name are required"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := a.zfs.CreateSnapshot(req.Dataset, req.Name, req.Recursive); err != nil {
|
||||
log.Printf("create snapshot error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
snapshotName := fmt.Sprintf("%s@%s", req.Dataset, req.Name)
|
||||
snap, err := a.zfs.GetSnapshot(snapshotName)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusCreated, map[string]string{"message": "snapshot created", "name": snapshotName})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, snap)
|
||||
}
|
||||
|
||||
func (a *App) handleGetSnapshot(w http.ResponseWriter, r *http.Request) {
|
||||
name := pathParam(r, "/api/v1/snapshots/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "name": name})
|
||||
if name == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "snapshot name required"})
|
||||
return
|
||||
}
|
||||
|
||||
snap, err := a.zfs.GetSnapshot(name)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, snap)
|
||||
}
|
||||
|
||||
func (a *App) handleDeleteSnapshot(w http.ResponseWriter, r *http.Request) {
|
||||
name := pathParam(r, "/api/v1/snapshots/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "name": name})
|
||||
if name == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "snapshot name required"})
|
||||
return
|
||||
}
|
||||
|
||||
recursive := r.URL.Query().Get("recursive") == "true"
|
||||
|
||||
if err := a.zfs.DestroySnapshot(name, recursive); err != nil {
|
||||
log.Printf("destroy snapshot error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]string{"message": "snapshot destroyed", "name": name})
|
||||
}
|
||||
|
||||
// Snapshot Policy Handlers
|
||||
func (a *App) handleListSnapshotPolicies(w http.ResponseWriter, r *http.Request) {
|
||||
policies := []models.SnapshotPolicy{} // Stub
|
||||
dataset := r.URL.Query().Get("dataset")
|
||||
var policies []models.SnapshotPolicy
|
||||
if dataset != "" {
|
||||
policies = a.snapshotPolicy.ListForDataset(dataset)
|
||||
} else {
|
||||
policies = a.snapshotPolicy.List()
|
||||
}
|
||||
writeJSON(w, http.StatusOK, policies)
|
||||
}
|
||||
|
||||
func (a *App) handleCreateSnapshotPolicy(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented"})
|
||||
var policy models.SnapshotPolicy
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&policy); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
if policy.Dataset == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "dataset is required"})
|
||||
return
|
||||
}
|
||||
|
||||
a.snapshotPolicy.Set(&policy)
|
||||
writeJSON(w, http.StatusCreated, policy)
|
||||
}
|
||||
|
||||
func (a *App) handleGetSnapshotPolicy(w http.ResponseWriter, r *http.Request) {
|
||||
dataset := pathParam(r, "/api/v1/snapshot-policies/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "dataset": dataset})
|
||||
if dataset == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "dataset name required"})
|
||||
return
|
||||
}
|
||||
|
||||
policy, err := a.snapshotPolicy.Get(dataset)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if policy == nil {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": "policy not found"})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, policy)
|
||||
}
|
||||
|
||||
func (a *App) handleUpdateSnapshotPolicy(w http.ResponseWriter, r *http.Request) {
|
||||
dataset := pathParam(r, "/api/v1/snapshot-policies/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "dataset": dataset})
|
||||
if dataset == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "dataset name required"})
|
||||
return
|
||||
}
|
||||
|
||||
var policy models.SnapshotPolicy
|
||||
if err := json.NewDecoder(r.Body).Decode(&policy); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure dataset matches URL parameter
|
||||
policy.Dataset = dataset
|
||||
|
||||
a.snapshotPolicy.Set(&policy)
|
||||
writeJSON(w, http.StatusOK, policy)
|
||||
}
|
||||
|
||||
func (a *App) handleDeleteSnapshotPolicy(w http.ResponseWriter, r *http.Request) {
|
||||
dataset := pathParam(r, "/api/v1/snapshot-policies/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "dataset": dataset})
|
||||
if dataset == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "dataset name required"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := a.snapshotPolicy.Delete(dataset); err != nil {
|
||||
log.Printf("delete snapshot policy error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]string{"message": "policy deleted", "dataset": dataset})
|
||||
}
|
||||
|
||||
// SMB Share Handlers
|
||||
@@ -417,18 +538,41 @@ func (a *App) handleRemoveLUN(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Job Handlers
|
||||
func (a *App) handleListJobs(w http.ResponseWriter, r *http.Request) {
|
||||
jobs := []models.Job{} // Stub
|
||||
status := models.JobStatus(r.URL.Query().Get("status"))
|
||||
jobs := a.jobManager.List(status)
|
||||
writeJSON(w, http.StatusOK, jobs)
|
||||
}
|
||||
|
||||
func (a *App) handleGetJob(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/jobs/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "id": id})
|
||||
if id == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "job id required"})
|
||||
return
|
||||
}
|
||||
|
||||
job, err := a.jobManager.Get(id)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, job)
|
||||
}
|
||||
|
||||
func (a *App) handleCancelJob(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/jobs/")
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "id": id})
|
||||
if id == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "job id required"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := a.jobManager.Cancel(id); err != nil {
|
||||
log.Printf("cancel job error: %v", err)
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]string{"message": "job cancelled", "id": id})
|
||||
}
|
||||
|
||||
// Auth Handlers (stubs)
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"gitea.avt.data-center.id/othman.suseno/atlas/internal/job"
|
||||
"gitea.avt.data-center.id/othman.suseno/atlas/internal/snapshot"
|
||||
"gitea.avt.data-center.id/othman.suseno/atlas/internal/zfs"
|
||||
)
|
||||
|
||||
@@ -17,10 +19,13 @@ type Config struct {
|
||||
}
|
||||
|
||||
type App struct {
|
||||
cfg Config
|
||||
tmpl *template.Template
|
||||
mux *http.ServeMux
|
||||
zfs *zfs.Service
|
||||
cfg Config
|
||||
tmpl *template.Template
|
||||
mux *http.ServeMux
|
||||
zfs *zfs.Service
|
||||
snapshotPolicy *snapshot.PolicyStore
|
||||
jobManager *job.Manager
|
||||
scheduler *snapshot.Scheduler
|
||||
}
|
||||
|
||||
func New(cfg Config) (*App, error) {
|
||||
@@ -36,13 +41,24 @@ func New(cfg Config) (*App, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zfsService := zfs.New()
|
||||
policyStore := snapshot.NewPolicyStore()
|
||||
jobMgr := job.NewManager()
|
||||
scheduler := snapshot.NewScheduler(policyStore, zfsService, jobMgr)
|
||||
|
||||
a := &App{
|
||||
cfg: cfg,
|
||||
tmpl: tmpl,
|
||||
mux: http.NewServeMux(),
|
||||
zfs: zfs.New(),
|
||||
cfg: cfg,
|
||||
tmpl: tmpl,
|
||||
mux: http.NewServeMux(),
|
||||
zfs: zfsService,
|
||||
snapshotPolicy: policyStore,
|
||||
jobManager: jobMgr,
|
||||
scheduler: scheduler,
|
||||
}
|
||||
|
||||
// Start snapshot scheduler (runs every 15 minutes)
|
||||
scheduler.Start(15 * time.Minute)
|
||||
|
||||
a.routes()
|
||||
return a, nil
|
||||
}
|
||||
@@ -52,6 +68,13 @@ func (a *App) Router() http.Handler {
|
||||
return requestID(logging(a.mux))
|
||||
}
|
||||
|
||||
// StopScheduler stops the snapshot scheduler (for graceful shutdown)
|
||||
func (a *App) StopScheduler() {
|
||||
if a.scheduler != nil {
|
||||
a.scheduler.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// routes() is now in routes.go
|
||||
|
||||
func parseTemplates(dir string) (*template.Template, error) {
|
||||
|
||||
Reference in New Issue
Block a user