diff --git a/controllers/allowed_image.go b/controllers/allowed_image.go index 22e443a..cfb5c2d 100644 --- a/controllers/allowed_image.go +++ b/controllers/allowed_image.go @@ -3,6 +3,7 @@ package controllers import ( "encoding/json" "slices" + "strconv" oclib "cloud.o-forge.io/core/oc-lib" beego "github.com/beego/beego/v2/server/web" @@ -28,11 +29,15 @@ func isAdmin(groups []string) bool { // @Title GetAll // @Description Retourne toutes les images autorisées à persister sur ce peer +// @Param offset query string false +// @Param limit query string false // @Success 200 {object} []allowed_image.AllowedImage // @router / [get] func (o *AllowedImageController) GetAll() { + offset, _ := strconv.Atoi(o.Ctx.Input.Query("offset")) + limit, _ := strconv.Atoi(o.Ctx.Input.Query("limit")) user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request) - res := oclib.NewRequest(oclib.LibDataEnum(oclib.ALLOWED_IMAGE), user, peerID, groups, nil).LoadAll(false) + res := oclib.NewRequest(oclib.LibDataEnum(oclib.ALLOWED_IMAGE), user, peerID, groups, nil).LoadAll(false, int64(offset), int64(limit)) o.Data["json"] = res o.ServeJSON() } diff --git a/controllers/datacenter.go b/controllers/datacenter.go index 7c2b3a2..5d9f9ea 100644 --- a/controllers/datacenter.go +++ b/controllers/datacenter.go @@ -1,12 +1,15 @@ package controllers import ( + "fmt" "net/http" "oc-datacenter/infrastructure/monitor" + "strconv" "time" oclib "cloud.o-forge.io/core/oc-lib" "cloud.o-forge.io/core/oc-lib/dbs" + "cloud.o-forge.io/core/oc-lib/models/utils" beego "github.com/beego/beego/v2/server/web" "github.com/gorilla/websocket" ) @@ -16,74 +19,143 @@ type DatacenterController struct { beego.Controller } +func resourceTypeEnum(t string, special bool) []oclib.LibDataEnum { + e := []oclib.LibDataEnum{} + if special && t == "resource" { + return e + } + if t == "storage" || t == "live" { + e = append(e, oclib.LibDataEnum(oclib.LIVE_STORAGE)) + } + if t == "datacenter" || t == "live" { + e = append(e, oclib.LibDataEnum(oclib.LIVE_DATACENTER)) + } + return e +} + +func (o *DatacenterController) collection(special bool) []oclib.LibDataEnum { + // Extrait le type depuis le segment d'URL après "resource" + // URL forme: /oc/resource/{type}/... + typ := o.Ctx.Input.Param(":type") + return resourceTypeEnum(typ, special) +} + +// @Title Search +// @Description search datacenter +// @Param type path string true "the type you want to get" +// @Param search path string true "the word search you want to get" +// @Param is_draft query string false "draft wished" +// @Param offset query string false +// @Param limit query string false +// @Success 200 {workspace} models.workspace +// @router /:type/search/:search [get] +func (o *DatacenterController) Search() { + /* + * This is a sample of how to use the search function + * The search function is used to search for data in the database + * The search function takes in a filter and a data type + * The filter is a struct that contains the search parameters + * The data type is an enum that specifies the type of data to search for + * The search function returns a list of data that matches the filter + * The data is then returned as a json object + */ + // store and return Id or post with UUID + user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request) + offset, _ := strconv.Atoi(o.Ctx.Input.Query("offset")) + limit, _ := strconv.Atoi(o.Ctx.Input.Query("limit")) + search := o.Ctx.Input.Param(":search") + if search == "*" { + search = "" + } + isDraft := o.Ctx.Input.Query("is_draft") + m := map[string][]utils.ShallowDBObject{} + for _, col := range o.collection(false) { + if m[col.String()] == nil { + m[col.String()] = []utils.ShallowDBObject{} + } + s := oclib.NewRequest(col, user, peerID, groups, nil).Search(&dbs.Filters{ + Or: map[string][]dbs.Filter{ + // "abstractlive.abstractobject.creator_id": {{Operator: dbs.EQUAL.String(), Value: peerID}}, + "abstractlive.abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}}, + }, + }, "", isDraft == "true", int64(offset), int64(limit)) + fmt.Println(s) + m[col.String()] = append(m[col.String()], s.Data...) + } + o.Data["json"] = map[string]interface{}{ + "data": m, + "code": 200, + "err": nil, + } + o.ServeJSON() +} + // @Title GetAll // @Description find booking by id +// @Param type path string true "the word type you want to get" // @Param is_draft query string false "draft wished" +// @Param offset query string false +// @Param limit query string false // @Success 200 {booking} models.booking // @router / [get] func (o *DatacenterController) GetAll() { user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request) isDraft := o.Ctx.Input.Query("is_draft") - storages := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_STORAGE), user, peerID, groups, nil).Search(&dbs.Filters{ - Or: map[string][]dbs.Filter{ - "abstractinstanciatedresource.abstractresource.abstractobject.creator_id": {{Operator: dbs.EQUAL.String(), Value: peerID}}, - }, - }, "", isDraft == "true") - computes := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_DATACENTER), user, peerID, groups, nil).Search(&dbs.Filters{ - Or: map[string][]dbs.Filter{ - "abstractinstanciatedresource.abstractresource.abstractobject.creator_id": {{Operator: dbs.EQUAL.String(), Value: peerID}}, - }, - }, "", isDraft == "true") - storages.Data = append(storages.Data, computes.Data...) - if storages.Err != "" { - storages.Err += " - " + computes.Err + offset, _ := strconv.Atoi(o.Ctx.Input.Query("offset")) + limit, _ := strconv.Atoi(o.Ctx.Input.Query("limit")) + m := map[string][]utils.ShallowDBObject{} + for _, col := range o.collection(false) { + if m[col.String()] == nil { + m[col.String()] = []utils.ShallowDBObject{} + } + s := oclib.NewRequest(oclib.LibDataEnum(col), user, peerID, groups, nil).LoadAll(isDraft == "true", int64(offset), int64(limit)) + fmt.Println(s) + m[col.String()] = append(m[col.String()], s.Data...) + } + fmt.Println(m) + o.Data["json"] = map[string]interface{}{ + "data": m, + "code": 200, + "err": nil, } - o.Data["json"] = storages o.ServeJSON() } // @Title Get // @Description find booking by id // @Param id path string true "the id you want to get" +// @Param type path string true "the word type you want to get" // @Param is_draft query string false "draft wished" // @Success 200 {booking} models.booking -// @router /:id [get] +// @router /:type/:id [get] func (o *DatacenterController) Get() { user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request) - isDraft := o.Ctx.Input.Query("is_draft") id := o.Ctx.Input.Param(":id") - storages := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_STORAGE), user, peerID, groups, nil).Search(&dbs.Filters{ - Or: map[string][]dbs.Filter{ - "abstractinstanciatedresource.abstractresource.abstractobject.id": {{Operator: dbs.EQUAL.String(), Value: id}}, - "abstractinstanciatedresource.abstractresource.abstractobject.creator_id": {{Operator: dbs.EQUAL.String(), Value: peerID}}, - }, - }, "", isDraft == "true") - if len(storages.Data) == 0 { - computes := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_DATACENTER), user, peerID, groups, nil).Search(&dbs.Filters{ - Or: map[string][]dbs.Filter{ - "abstractinstanciatedresource.abstractresource.abstractobject.id": {{Operator: dbs.EQUAL.String(), Value: id}}, - "abstractinstanciatedresource.abstractresource.abstractobject.creator_id": {{Operator: dbs.EQUAL.String(), Value: peerID}}, - }, - }, "", isDraft == "true") - if len(computes.Data) == 0 { - o.Data["json"] = map[string]interface{}{ - "data": nil, - "code": computes.Code, - "err": computes.Err, - } - } else { - o.Data["json"] = map[string]interface{}{ - "data": computes.Data[0], - "code": computes.Code, - "err": computes.Err, - } + for _, col := range o.collection(false) { + data := oclib.NewRequest(col, user, peerID, groups, nil).LoadOne(id) + o.Data["json"] = data + if data.Data != nil { + break } + } + o.ServeJSON() +} - } else { - o.Data["json"] = map[string]interface{}{ - "data": storages.Data[0], - "code": storages.Code, - "err": storages.Err, +// @Title Delete +// @Description find booking by id +// @Param id path string true "the id you want to get" +// @Param type path string true "the word type you want to get" +// @Param is_draft query string false "draft wished" +// @Success 200 {booking} models.booking +// @router /:type/:id [delete] +func (o *DatacenterController) Delete() { + user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request) + id := o.Ctx.Input.Param(":id") + for _, col := range o.collection(false) { + data := oclib.NewRequest(col, user, peerID, groups, nil).DeleteOne(id) + o.Data["json"] = data + if data.Data != nil { + break } } o.ServeJSON() @@ -97,7 +169,7 @@ var upgrader = websocket.Upgrader{ // @Description find booking by id // @Param id path string true "the id you want to get" // @Success 200 {booking} models.booking -// @router /:id [get] +// @router /logs/:id [get] func (o *DatacenterController) Log() { // user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request) id := o.Ctx.Input.Param(":id") diff --git a/go.mod b/go.mod index e31d6ef..928d97e 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( ) require ( - cloud.o-forge.io/core/oc-lib v0.0.0-20260325092016-4580200e8057 // indirect + cloud.o-forge.io/core/oc-lib v0.0.0-20260408134044-284533ad1d7b // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/biter777/countries v1.7.5 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect diff --git a/go.sum b/go.sum index c436ecf..cc86af7 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,10 @@ cloud.o-forge.io/core/oc-lib v0.0.0-20260324114937-6d0c78946e8b h1:y0rppyzGIQTIy cloud.o-forge.io/core/oc-lib v0.0.0-20260324114937-6d0c78946e8b/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA= cloud.o-forge.io/core/oc-lib v0.0.0-20260325092016-4580200e8057 h1:pR+lZzcCWZ0kke2r2xXa7OpdbLpPW3gZSWZ8gGHh274= cloud.o-forge.io/core/oc-lib v0.0.0-20260325092016-4580200e8057/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA= +cloud.o-forge.io/core/oc-lib v0.0.0-20260407090927-6fe91eda875d h1:54Vl14gurwAkmZEaWZKUM5eDZfB7MF/fzWjibWLQljE= +cloud.o-forge.io/core/oc-lib v0.0.0-20260407090927-6fe91eda875d/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA= +cloud.o-forge.io/core/oc-lib v0.0.0-20260408134044-284533ad1d7b h1:mOU+tc87/KEQgFmw1RcQ9E9Rbz8Q2jLOh5Cpu6po9Ww= +cloud.o-forge.io/core/oc-lib v0.0.0-20260408134044-284533ad1d7b/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= diff --git a/infrastructure/admiralty/admiralty.go b/infrastructure/admiralty/admiralty.go index 4224418..bcd6f9b 100644 --- a/infrastructure/admiralty/admiralty.go +++ b/infrastructure/admiralty/admiralty.go @@ -274,7 +274,7 @@ func provisionPVCsForTarget(ctx context.Context, targetNS string, sourceExecutio "executions_id": {{Operator: dbs.EQUAL.String(), Value: sourceExecutionsID}}, "resource_type": {{Operator: dbs.EQUAL.String(), Value: tools.LIVE_STORAGE.EnumIndex()}}, }, - }, "", false) + }, "", false, 0, 1000) if res.Err != "" || len(res.Data) == 0 { return @@ -424,7 +424,7 @@ func (s *AdmiraltySetter) TeardownIfRemote(exec *workflow_execution.WorkflowExec "executions_id": {{Operator: dbs.EQUAL.String(), Value: exec.ExecutionsID}}, "resource_type": {{Operator: dbs.EQUAL.String(), Value: tools.COMPUTE_RESOURCE.EnumIndex()}}, }, - }, "", false) + }, "", false, 0, 1000) if res.Err != "" || len(res.Data) == 0 { return diff --git a/infrastructure/allowed_image_bootstrap.go b/infrastructure/allowed_image_bootstrap.go index f27d4f7..a167737 100644 --- a/infrastructure/allowed_image_bootstrap.go +++ b/infrastructure/allowed_image_bootstrap.go @@ -29,7 +29,7 @@ func BootstrapAllowedImages() { And: map[string][]dbs.Filter{ "image": {{Operator: dbs.EQUAL.String(), Value: img.Image}}, }, - }, "", false) + }, "", false, 0, 1) if existing.Err != "" || len(existing.Data) > 0 { continue // déjà présente ou erreur de recherche : on passe } diff --git a/infrastructure/booking_watchdog.go b/infrastructure/booking_watchdog.go deleted file mode 100644 index e54e84c..0000000 --- a/infrastructure/booking_watchdog.go +++ /dev/null @@ -1,112 +0,0 @@ -package infrastructure - -import ( - "encoding/json" - "fmt" - "sync" - "time" - - oclib "cloud.o-forge.io/core/oc-lib" - "cloud.o-forge.io/core/oc-lib/dbs" - bookingmodel "cloud.o-forge.io/core/oc-lib/models/booking" - "cloud.o-forge.io/core/oc-lib/models/common/enum" - "cloud.o-forge.io/core/oc-lib/tools" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -// processedBookings tracks booking IDs already handled this process lifetime. -var processedBookings sync.Map - -// closingStates is the set of terminal booking states. -var ClosingStates = map[enum.BookingStatus]bool{ - enum.FAILURE: true, - enum.SUCCESS: true, - enum.FORGOTTEN: true, - enum.CANCELLED: true, -} - -// WatchBookings is a safety-net fallback for when oc-monitord fails to launch. -// It detects bookings that are past expected_start_date by at least 1 minute and -// are still in a non-terminal state. Instead of writing to the database directly, -// it emits WORKFLOW_STEP_DONE_EVENT with State=FAILURE on NATS so that oc-scheduler -// handles the state transition — keeping a single source of truth for booking state. -// -// Must be launched in a goroutine from main. -func WatchBookings() { - logger := oclib.GetLogger() - logger.Info().Msg("BookingWatchdog: started") - ticker := time.NewTicker(time.Minute) - defer ticker.Stop() - for range ticker.C { - if err := scanStaleBookings(); err != nil { - logger.Error().Msg("BookingWatchdog: " + err.Error()) - } - } -} - -// scanStaleBookings queries all bookings whose ExpectedStartDate passed more than -// 1 minute ago. Non-terminal ones get a WORKFLOW_STEP_DONE_EVENT FAILURE emitted -// on NATS so oc-scheduler closes them. -func scanStaleBookings() error { - myself, err := oclib.GetMySelf() - if err != nil { - return fmt.Errorf("could not resolve local peer: %w", err) - } - peerID := myself.GetID() - - deadline := time.Now().UTC().Add(-time.Minute) - res := oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), "", peerID, []string{}, nil). - Search(&dbs.Filters{ - And: map[string][]dbs.Filter{ - "expected_start_date": {{ - Operator: dbs.LTE.String(), - Value: primitive.NewDateTimeFromTime(deadline), - }}, - }, - }, "", false) - - if res.Err != "" { - return fmt.Errorf("stale booking search failed: %s", res.Err) - } - - for _, dbo := range res.Data { - b, ok := dbo.(*bookingmodel.Booking) - if !ok { - continue - } - go emitWatchdogFailure(b) - } - return nil -} - -// emitWatchdogFailure publishes a WORKFLOW_STEP_DONE_EVENT FAILURE for a stale -// booking. oc-scheduler is the single authority for booking state transitions. -func emitWatchdogFailure(b *bookingmodel.Booking) { - logger := oclib.GetLogger() - - if _, done := processedBookings.Load(b.GetID()); done { - return - } - if ClosingStates[b.State] { - processedBookings.Store(b.GetID(), struct{}{}) - return - } - - now := time.Now().UTC() - payload, err := json.Marshal(tools.WorkflowLifecycleEvent{ - BookingID: b.GetID(), - State: enum.FAILURE.EnumIndex(), - RealEnd: &now, - }) - if err != nil { - return - } - tools.NewNATSCaller().SetNATSPub(tools.WORKFLOW_STEP_DONE_EVENT, tools.NATSResponse{ - FromApp: "oc-datacenter", - Method: int(tools.WORKFLOW_STEP_DONE_EVENT), - Payload: payload, - }) - - logger.Info().Msgf("BookingWatchdog: booking %s stale → emitting FAILURE", b.GetID()) - processedBookings.Store(b.GetID(), struct{}{}) -} diff --git a/infrastructure/kubernetes/kubernetes.go b/infrastructure/kubernetes/kubernetes.go index e39d6b5..be27c7d 100644 --- a/infrastructure/kubernetes/kubernetes.go +++ b/infrastructure/kubernetes/kubernetes.go @@ -152,7 +152,7 @@ func (s *KubernetesService) filterNonAllowed(images []string) []string { And: map[string][]dbs.Filter{ "image": {{Operator: dbs.EQUAL.String(), Value: name}}, }, - }, "", false) + }, "", false, 0, 1000) if len(res.Data) == 0 { toRemove = append(toRemove, img) diff --git a/infrastructure/monitor/monitor.go b/infrastructure/monitor/monitor.go index 87be824..b13b069 100644 --- a/infrastructure/monitor/monitor.go +++ b/infrastructure/monitor/monitor.go @@ -57,7 +57,7 @@ func Call(book *booking.Booking, "source": {{Operator: dbs.EQUAL.String(), Value: instance.Source}}, "abstractlive.resources_id": {{Operator: dbs.EQUAL.String(), Value: computeRes.GetID()}}, }, - }, "", false) + }, "", false, 0, 1000) if res.Err != "" { continue } diff --git a/infrastructure/nats/nats.go b/infrastructure/nats/nats.go index 3ecd2d7..009b8c3 100644 --- a/infrastructure/nats/nats.go +++ b/infrastructure/nats/nats.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "oc-datacenter/infrastructure" "oc-datacenter/infrastructure/admiralty" "oc-datacenter/infrastructure/kubernetes" "oc-datacenter/infrastructure/kubernetes/models" @@ -237,7 +238,7 @@ func ListenNATS() { if err := json.Unmarshal(resp.Payload, &evt); err != nil || evt.ExecutionsID == "" { return } - go kubernetes.NewKubernetesService(evt.ExecutionsID).TeardownForExecution(evt.ExecutionID) + go infrastructure.TeardownForExecution(evt.ExecutionID, evt.ExecutionsID) }, // ─── REMOVE_RESOURCE ──────────────────────────────────────────────────────── diff --git a/infrastructure/storage/minio_setter.go b/infrastructure/storage/minio_setter.go index a5381c7..01a7e7f 100644 --- a/infrastructure/storage/minio_setter.go +++ b/infrastructure/storage/minio_setter.go @@ -298,7 +298,7 @@ func (m *MinioSetter) TeardownAsSource(ctx context.Context, event MinioDeleteEve // loadMinioURL searches through all live storages accessible by peerID to find // the one that references MinioID, and returns its endpoint URL. func (m *MinioSetter) loadMinioURL(peerID string) (string, error) { - res := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_STORAGE), "", peerID, []string{}, nil).LoadAll(false) + res := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_STORAGE), "", peerID, []string{}, nil).LoadAll(false, 0, 10000) if res.Err != "" { return "", fmt.Errorf("loadMinioURL: failed to load live storages: %s", res.Err) } @@ -324,7 +324,7 @@ func (m *MinioSetter) TeardownForExecution(ctx context.Context, localPeerID stri "executions_id": {{Operator: dbs.EQUAL.String(), Value: m.ExecutionsID}}, "resource_type": {{Operator: dbs.EQUAL.String(), Value: tools.LIVE_STORAGE.EnumIndex()}}, }, - }, "", false) + }, "", false, 0, 10000) if res.Err != "" || len(res.Data) == 0 { return diff --git a/infrastructure/storage/pvc_setter.go b/infrastructure/storage/pvc_setter.go index 521ef7c..e9ea3b2 100644 --- a/infrastructure/storage/pvc_setter.go +++ b/infrastructure/storage/pvc_setter.go @@ -160,7 +160,7 @@ func (p *PVCSetter) TeardownAsSource(ctx context.Context, event PVCDeleteEvent) // ResolveStorageName returns the live storage name for a given storageID, or "" if not found. func ResolveStorageName(storageID, peerID string) string { - res := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_STORAGE), "", peerID, []string{}, nil).LoadAll(false) + res := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_STORAGE), "", peerID, []string{}, nil).LoadAll(false, 0, 10000) if res.Err != "" { return "" } @@ -175,7 +175,7 @@ func ResolveStorageName(storageID, peerID string) string { // loadStorageSize looks up the SizeGB for this storage in live storages. func (p *PVCSetter) loadStorageSize(peerID string) (string, error) { - res := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_STORAGE), "", peerID, []string{}, nil).LoadAll(false) + res := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_STORAGE), "", peerID, []string{}, nil).LoadAll(false, 0, 10000) if res.Err != "" { return "", fmt.Errorf("loadStorageSize: %s", res.Err) } @@ -199,7 +199,7 @@ func (p *PVCSetter) TeardownForExecution(ctx context.Context, localPeerID string "executions_id": {{Operator: dbs.EQUAL.String(), Value: p.ExecutionsID}}, "resource_type": {{Operator: dbs.EQUAL.String(), Value: tools.LIVE_STORAGE.EnumIndex()}}, }, - }, "", false) + }, "", false, 0, 10000) if res.Err != "" || len(res.Data) == 0 { return diff --git a/infrastructure/kubernetes/watchdog.go b/infrastructure/watchdog.go similarity index 72% rename from infrastructure/kubernetes/watchdog.go rename to infrastructure/watchdog.go index 9bd7d79..479a723 100644 --- a/infrastructure/kubernetes/watchdog.go +++ b/infrastructure/watchdog.go @@ -1,27 +1,126 @@ -package kubernetes +package infrastructure import ( "context" + "encoding/json" "fmt" + "oc-datacenter/conf" + "oc-datacenter/infrastructure/admiralty" + "oc-datacenter/infrastructure/kubernetes" + "oc-datacenter/infrastructure/storage" "regexp" "strings" + "sync" "time" - "oc-datacenter/conf" - "oc-datacenter/infrastructure" - "oc-datacenter/infrastructure/admiralty" - "oc-datacenter/infrastructure/storage" - oclib "cloud.o-forge.io/core/oc-lib" "cloud.o-forge.io/core/oc-lib/dbs" bookingmodel "cloud.o-forge.io/core/oc-lib/models/booking" + "cloud.o-forge.io/core/oc-lib/models/common/enum" "cloud.o-forge.io/core/oc-lib/models/workflow_execution" "cloud.o-forge.io/core/oc-lib/tools" + "go.mongodb.org/mongo-driver/bson/primitive" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// uuidNsPattern matches Kubernetes namespace names that are execution UUIDs. +// processedBookings tracks booking IDs already handled this process lifetime. +var processedBookings sync.Map + +// closingStates is the set of terminal booking states. +var ClosingStates = map[enum.BookingStatus]bool{ + enum.FAILURE: true, + enum.SUCCESS: true, + enum.FORGOTTEN: true, + enum.CANCELLED: true, +} + +// WatchBookings is a safety-net fallback for when oc-monitord fails to launch. +// It detects bookings that are past expected_start_date by at least 1 minute and +// are still in a non-terminal state. Instead of writing to the database directly, +// it emits WORKFLOW_STEP_DONE_EVENT with State=FAILURE on NATS so that oc-scheduler +// handles the state transition — keeping a single source of truth for booking state. +// +// Must be launched in a goroutine from main. +func WatchBookings() { + logger := oclib.GetLogger() + logger.Info().Msg("BookingWatchdog: started") + ticker := time.NewTicker(time.Minute) + defer ticker.Stop() + for range ticker.C { + if err := scanStaleBookings(); err != nil { + logger.Error().Msg("BookingWatchdog: " + err.Error()) + } + } +} + +// scanStaleBookings queries all bookings whose ExpectedStartDate passed more than +// 1 minute ago. Non-terminal ones get a WORKFLOW_STEP_DONE_EVENT FAILURE emitted +// on NATS so oc-scheduler closes them. +func scanStaleBookings() error { + myself, err := oclib.GetMySelf() + if err != nil { + return fmt.Errorf("could not resolve local peer: %w", err) + } + peerID := myself.GetID() + + deadline := time.Now().UTC().Add(-time.Minute) + res := oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), "", peerID, []string{}, nil). + Search(&dbs.Filters{ + And: map[string][]dbs.Filter{ + "expected_start_date": {{ + Operator: dbs.LTE.String(), + Value: primitive.NewDateTimeFromTime(deadline), + }}, + }, + }, "", false, 0, 10000) + + if res.Err != "" { + return fmt.Errorf("stale booking search failed: %s", res.Err) + } + + for _, dbo := range res.Data { + b, ok := dbo.(*bookingmodel.Booking) + if !ok { + continue + } + go emitWatchdogFailure(b) + } + return nil +} + +// emitWatchdogFailure publishes a WORKFLOW_STEP_DONE_EVENT FAILURE for a stale +// booking. oc-scheduler is the single authority for booking state transitions. +func emitWatchdogFailure(b *bookingmodel.Booking) { + logger := oclib.GetLogger() + + if _, done := processedBookings.Load(b.GetID()); done { + return + } + if ClosingStates[b.State] { + processedBookings.Store(b.GetID(), struct{}{}) + return + } + + now := time.Now().UTC() + payload, err := json.Marshal(tools.WorkflowLifecycleEvent{ + BookingID: b.GetID(), + State: enum.FAILURE.EnumIndex(), + RealEnd: &now, + }) + if err != nil { + return + } + tools.NewNATSCaller().SetNATSPub(tools.WORKFLOW_STEP_DONE_EVENT, tools.NATSResponse{ + FromApp: "oc-datacenter", + Method: int(tools.WORKFLOW_STEP_DONE_EVENT), + Payload: payload, + }) + + logger.Info().Msgf("BookingWatchdog: booking %s stale → emitting FAILURE", b.GetID()) + processedBookings.Store(b.GetID(), struct{}{}) +} + var uuidNsPattern = regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`) // WatchInfra is a safety-net watchdog that periodically scans Kubernetes for @@ -30,22 +129,22 @@ var uuidNsPattern = regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0- // missed due to oc-monitord or oc-datacenter crash/restart). // // Must be launched in a goroutine from main. -func (s *KubernetesService) Watch() { +func Watch() { logger := oclib.GetLogger() logger.Info().Msg("InfraWatchdog: started") ticker := time.NewTicker(5 * time.Minute) defer ticker.Stop() for range ticker.C { - if err := s.scanOrphaned(); err != nil { + if err := scanOrphaned(); err != nil { logger.Error().Msg("InfraWatchdog: " + err.Error()) } - if err := s.scanOrphanedMinio(); err != nil { + if err := scanOrphanedMinio(); err != nil { logger.Error().Msg("InfraWatchdog(minio): " + err.Error()) } - if err := s.scanOrphanedAdmiraltyNodes(); err != nil { + if err := scanOrphanedAdmiraltyNodes(); err != nil { logger.Error().Msg("InfraWatchdog(admiralty-nodes): " + err.Error()) } - if err := s.scanOrphanedPVC(); err != nil { + if err := scanOrphanedPVC(); err != nil { logger.Error().Msg("InfraWatchdog(pvc): " + err.Error()) } } @@ -54,7 +153,7 @@ func (s *KubernetesService) Watch() { // scanOrphanedInfra lists all UUID-named Kubernetes namespaces, looks up their // WorkflowExecution in the DB, and triggers teardown for any that are in a // terminal state. Namespaces already in Terminating phase are skipped. -func (s *KubernetesService) scanOrphaned() error { +func scanOrphaned() error { logger := oclib.GetLogger() serv, err := tools.NewKubernetesService( @@ -98,7 +197,7 @@ func (s *KubernetesService) scanOrphaned() error { logger.Info().Msgf("InfraWatchdog: orphaned infra detected for execution %s (state=%v) → teardown", executionsID, exec.State) - go s.TeardownForExecution(exec.GetID()) + go TeardownForExecution(exec.GetID(), exec.ExecutionsID) } return nil } @@ -107,7 +206,7 @@ func (s *KubernetesService) scanOrphaned() error { // terminal state and triggers Minio teardown for each unique executionsID found. // This covers the case where the Kubernetes namespace is already gone (manual // deletion, prior partial teardown) but Minio SA and bucket were never revoked. -func (s *KubernetesService) scanOrphanedMinio() error { +func scanOrphanedMinio() error { logger := oclib.GetLogger() myself, err := oclib.GetMySelf() @@ -121,7 +220,7 @@ func (s *KubernetesService) scanOrphanedMinio() error { And: map[string][]dbs.Filter{ "resource_type": {{Operator: dbs.EQUAL.String(), Value: tools.LIVE_STORAGE.EnumIndex()}}, }, - }, "", false) + }, "", false, 0, 10000) if res.Err != "" { return fmt.Errorf("failed to search LIVE_STORAGE bookings: %s", res.Err) @@ -175,7 +274,7 @@ func (s *KubernetesService) scanOrphanedMinio() error { // This covers the gap where the namespace is already gone (or Terminating) but // the virtual node was never cleaned up by the Admiralty controller — which can // happen when the node goes NotReady before the AdmiraltyTarget CRD is deleted. -func (s *KubernetesService) scanOrphanedAdmiraltyNodes() error { +func scanOrphanedAdmiraltyNodes() error { logger := oclib.GetLogger() serv, err := tools.NewKubernetesService( @@ -251,7 +350,7 @@ func (s *KubernetesService) scanOrphanedAdmiraltyNodes() error { // // A LIVE_STORAGE booking is treated as a local PVC only when ResolveStorageName // returns a non-empty name — the same guard used by teardownPVCForExecution. -func (s *KubernetesService) scanOrphanedPVC() error { +func scanOrphanedPVC() error { logger := oclib.GetLogger() myself, err := oclib.GetMySelf() @@ -265,7 +364,7 @@ func (s *KubernetesService) scanOrphanedPVC() error { And: map[string][]dbs.Filter{ "resource_type": {{Operator: dbs.EQUAL.String(), Value: tools.LIVE_STORAGE.EnumIndex()}}, }, - }, "", false) + }, "", false, 0, 10000) if res.Err != "" { return fmt.Errorf("failed to search LIVE_STORAGE bookings: %s", res.Err) @@ -314,7 +413,7 @@ func findTerminalExecution(executionsID string, peerID string) *workflow_executi And: map[string][]dbs.Filter{ "executions_id": {{Operator: dbs.EQUAL.String(), Value: executionsID}}, }, - }, "", false) + }, "", false, 0, 10000) if res.Err != "" || len(res.Data) == 0 { return nil @@ -325,7 +424,7 @@ func findTerminalExecution(executionsID string, peerID string) *workflow_executi return nil } - if !infrastructure.ClosingStates[exec.State] { + if !ClosingStates[exec.State] { return nil } return exec @@ -334,7 +433,7 @@ func findTerminalExecution(executionsID string, peerID string) *workflow_executi // teardownInfraForExecution handles infrastructure cleanup when a workflow terminates. // oc-datacenter is responsible only for infra here — booking/execution state // is managed by oc-scheduler. -func (s *KubernetesService) TeardownForExecution(executionID string) { +func TeardownForExecution(executionID string, executionsID string) { logger := oclib.GetLogger() myself, err := oclib.GetMySelf() @@ -352,8 +451,8 @@ func (s *KubernetesService) TeardownForExecution(executionID string) { exec := res.(*workflow_execution.WorkflowExecution) ctx := context.Background() - admiralty.NewAdmiraltySetter(s.ExecutionsID).TeardownIfRemote(exec, selfPeerID) - storage.NewMinioSetter(s.ExecutionsID, "").TeardownForExecution(ctx, selfPeerID) - storage.NewPVCSetter(s.ExecutionsID, "").TeardownForExecution(ctx, selfPeerID) - s.CleanupImages(ctx) + admiralty.NewAdmiraltySetter(executionsID).TeardownIfRemote(exec, selfPeerID) + storage.NewMinioSetter(executionsID, "").TeardownForExecution(ctx, selfPeerID) + storage.NewPVCSetter(executionsID, "").TeardownForExecution(ctx, selfPeerID) + kubernetes.NewKubernetesService(executionsID).CleanupImages(ctx) } diff --git a/main.go b/main.go index fed919d..02d6b61 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "oc-datacenter/conf" "oc-datacenter/infrastructure" + "oc-datacenter/infrastructure/nats" _ "oc-datacenter/routers" oclib "cloud.o-forge.io/core/oc-lib" @@ -30,9 +31,9 @@ func main() { infrastructure.BootstrapAllowedImages() - go infrastructure.ListenNATS() + go nats.ListenNATS() go infrastructure.WatchBookings() - go infrastructure.WatchInfra() + go infrastructure.Watch() beego.Run() } diff --git a/oc-datacenter b/oc-datacenter deleted file mode 100755 index b9d7c63..0000000 Binary files a/oc-datacenter and /dev/null differ diff --git a/routers/commentsRouter.go b/routers/commentsRouter.go index ff8ea3f..da00c12 100644 --- a/routers/commentsRouter.go +++ b/routers/commentsRouter.go @@ -7,43 +7,7 @@ import ( func init() { - beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"], - beego.ControllerComments{ - Method: "GetKubeSecret", - Router: `/secret/:execution/:peer`, - AllowHTTPMethods: []string{"get"}, - MethodParams: param.Make(), - Filters: nil, - Params: nil}) - - beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"], - beego.ControllerComments{ - Method: "GetAllTargets", - Router: `/targets`, - AllowHTTPMethods: []string{"get"}, - MethodParams: param.Make(), - Filters: nil, - Params: nil}) - - beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"], - beego.ControllerComments{ - Method: "GetOneTarget", - Router: `/targets/:execution`, - AllowHTTPMethods: []string{"get"}, - MethodParams: param.Make(), - Filters: nil, - Params: nil}) - - beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:AdmiraltyController"], - beego.ControllerComments{ - Method: "DeleteAdmiraltySession", - Router: `/targets/:execution`, - AllowHTTPMethods: []string{"delete"}, - MethodParams: param.Make(), - Filters: nil, - Params: nil}) - - beego.GlobalControllerRouter["oc-datacenter/controllers:BookingController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:BookingController"], + beego.GlobalControllerRouter["oc-datacenter/controllers:AllowedImageController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:AllowedImageController"], beego.ControllerComments{ Method: "GetAll", Router: `/`, @@ -52,7 +16,7 @@ func init() { Filters: nil, Params: nil}) - beego.GlobalControllerRouter["oc-datacenter/controllers:BookingController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:BookingController"], + beego.GlobalControllerRouter["oc-datacenter/controllers:AllowedImageController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:AllowedImageController"], beego.ControllerComments{ Method: "Post", Router: `/`, @@ -61,7 +25,7 @@ func init() { Filters: nil, Params: nil}) - beego.GlobalControllerRouter["oc-datacenter/controllers:BookingController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:BookingController"], + beego.GlobalControllerRouter["oc-datacenter/controllers:AllowedImageController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:AllowedImageController"], beego.ControllerComments{ Method: "Get", Router: `/:id`, @@ -70,65 +34,11 @@ func init() { Filters: nil, Params: nil}) - beego.GlobalControllerRouter["oc-datacenter/controllers:BookingController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:BookingController"], + beego.GlobalControllerRouter["oc-datacenter/controllers:AllowedImageController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:AllowedImageController"], beego.ControllerComments{ - Method: "Log", + Method: "Delete", Router: `/:id`, - AllowHTTPMethods: []string{"get"}, - MethodParams: param.Make(), - Filters: nil, - Params: nil}) - - beego.GlobalControllerRouter["oc-datacenter/controllers:BookingController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:BookingController"], - beego.ControllerComments{ - Method: "Put", - Router: `/:id`, - AllowHTTPMethods: []string{"put"}, - MethodParams: param.Make(), - Filters: nil, - Params: nil}) - - beego.GlobalControllerRouter["oc-datacenter/controllers:BookingController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:BookingController"], - beego.ControllerComments{ - Method: "Check", - Router: `/check/:id/:start_date/:end_date`, - AllowHTTPMethods: []string{"get"}, - MethodParams: param.Make(), - Filters: nil, - Params: nil}) - - beego.GlobalControllerRouter["oc-datacenter/controllers:BookingController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:BookingController"], - beego.ControllerComments{ - Method: "ExtendForExecution", - Router: `/extend/:resource_id/from_execution/:execution_id/to/:duration`, - AllowHTTPMethods: []string{"post"}, - MethodParams: param.Make(), - Filters: nil, - Params: nil}) - - beego.GlobalControllerRouter["oc-datacenter/controllers:BookingController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:BookingController"], - beego.ControllerComments{ - Method: "ExtendForNamespace", - Router: `/extend/:resource_id/from_namespace/:namespace/to/:duration`, - AllowHTTPMethods: []string{"post"}, - MethodParams: param.Make(), - Filters: nil, - Params: nil}) - - beego.GlobalControllerRouter["oc-datacenter/controllers:BookingController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:BookingController"], - beego.ControllerComments{ - Method: "Search", - Router: `/search/:start_date/:end_date`, - AllowHTTPMethods: []string{"get"}, - MethodParams: param.Make(), - Filters: nil, - Params: nil}) - - beego.GlobalControllerRouter["oc-datacenter/controllers:BookingController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:BookingController"], - beego.ControllerComments{ - Method: "ExecutionSearch", - Router: `/search/execution/:id`, - AllowHTTPMethods: []string{"get"}, + AllowHTTPMethods: []string{"delete"}, MethodParams: param.Make(), Filters: nil, Params: nil}) @@ -145,17 +55,35 @@ func init() { beego.GlobalControllerRouter["oc-datacenter/controllers:DatacenterController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:DatacenterController"], beego.ControllerComments{ Method: "Get", - Router: `/:id`, + Router: `/:type/:id`, AllowHTTPMethods: []string{"get"}, MethodParams: param.Make(), Filters: nil, Params: nil}) - beego.GlobalControllerRouter["oc-datacenter/controllers:MinioController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:MinioController"], + beego.GlobalControllerRouter["oc-datacenter/controllers:DatacenterController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:DatacenterController"], beego.ControllerComments{ - Method: "CreateServiceAccount", - Router: `/serviceaccount/:minioId/:executions`, - AllowHTTPMethods: []string{"post"}, + Method: "Delete", + Router: `/:type/:id`, + AllowHTTPMethods: []string{"delete"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + + beego.GlobalControllerRouter["oc-datacenter/controllers:DatacenterController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:DatacenterController"], + beego.ControllerComments{ + Method: "Search", + Router: `/:type/search/:search`, + AllowHTTPMethods: []string{"get"}, + MethodParams: param.Make(), + Filters: nil, + Params: nil}) + + beego.GlobalControllerRouter["oc-datacenter/controllers:DatacenterController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:DatacenterController"], + beego.ControllerComments{ + Method: "Log", + Router: `/logs/:id`, + AllowHTTPMethods: []string{"get"}, MethodParams: param.Make(), Filters: nil, Params: nil}) @@ -169,15 +97,6 @@ func init() { Filters: nil, Params: nil}) - beego.GlobalControllerRouter["oc-datacenter/controllers:VectorController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:VectorController"], - beego.ControllerComments{ - Method: "Receive", - Router: `/`, - AllowHTTPMethods: []string{"post"}, - MethodParams: param.Make(), - Filters: nil, - Params: nil}) - beego.GlobalControllerRouter["oc-datacenter/controllers:VersionController"] = append(beego.GlobalControllerRouter["oc-datacenter/controllers:VersionController"], beego.ControllerComments{ Method: "GetAll", diff --git a/routers/router.go b/routers/router.go index 9119a39..5e047ad 100644 --- a/routers/router.go +++ b/routers/router.go @@ -14,11 +14,10 @@ import ( ) func init() { - ns := beego.NewNamespace("/oc/", + ns := beego.NewNamespace("/oc", beego.NSInclude( &controllers.DatacenterController{}, ), - beego.NSNamespace("/session", beego.NSInclude( &controllers.SessionController{}, diff --git a/swagger/index.html b/swagger/index.html index 2a9d4e2..9df41b1 100644 --- a/swagger/index.html +++ b/swagger/index.html @@ -39,7 +39,7 @@ window.onload = function() { // Begin Swagger UI call region const ui = SwaggerUIBundle({ - url: "https://petstore.swagger.io/v2/swagger.json", + url: "swagger.json", dom_id: '#swagger-ui', deepLinking: true, presets: [ diff --git a/swagger/swagger.json b/swagger/swagger.json index bf38760..41fee25 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -13,7 +13,7 @@ "url": "https://www.gnu.org/licenses/agpl-3.0.html" } }, - "basePath": "/oc/", + "basePath": "/oc", "paths": { "/": { "get": { @@ -23,11 +23,30 @@ "description": "find booking by id\n\u003cbr\u003e", "operationId": "DatacenterController.GetAll", "parameters": [ + { + "in": "path", + "name": "type", + "description": "the word type you want to get", + "required": true, + "type": "string" + }, { "in": "query", "name": "is_draft", "description": "draft wished", "type": "string" + }, + { + "in": "query", + "name": "offset", + "description": "false", + "type": "string" + }, + { + "in": "query", + "name": "limit", + "description": "false", + "type": "string" } ], "responses": { @@ -37,222 +56,24 @@ } } }, - "/admiralty/kubeconfig/{execution}": { + "/allowed-image/": { "get": { "tags": [ - "admiralty" + "allowed-image" ], - "parameters": [ - { - "in": "path", - "name": "execution", - "description": "execution id of the workflow", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "" - } - } - } - }, - "/admiralty/node/{execution}": { - "get": { - "tags": [ - "admiralty" - ], - "parameters": [ - { - "in": "path", - "name": "execution", - "description": "execution id of the workflow", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "" - } - } - } - }, - "/admiralty/secret/{execution}": { - "get": { - "tags": [ - "admiralty" - ], - "parameters": [ - { - "in": "path", - "name": "execution", - "description": "execution id of the workflow", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "" - } - } - }, - "post": { - "tags": [ - "admiralty" - ], - "parameters": [ - { - "in": "path", - "name": "execution", - "description": "execution id of the workflow", - "required": true, - "type": "string" - }, - { - "in": "body", - "name": "kubeconfig", - "description": "Kubeconfig to use when creating secret", - "required": true, - "schema": { - "$ref": "#/definitions/controllers.RemoteKubeconfig" - } - } - ], - "responses": { - "201": { - "description": "" - } - } - } - }, - "/admiralty/source/{execution}": { - "post": { - "tags": [ - "admiralty" - ], - "description": "Create an Admiralty Source on remote cluster\n\u003cbr\u003e", - "operationId": "AdmiraltyController.CreateSource", - "parameters": [ - { - "in": "path", - "name": "execution", - "description": "execution id of the workflow", - "required": true, - "type": "string" - } - ], - "responses": { - "201": { - "description": "" - } - } - } - }, - "/admiralty/target/{execution}": { - "post": { - "tags": [ - "admiralty" - ], - "description": "Create an Admiralty Target in the namespace associated to the executionID\n\u003cbr\u003e", - "operationId": "AdmiraltyController.CreateAdmiraltyTarget", - "parameters": [ - { - "in": "path", - "name": "execution", - "description": "execution id of the workflow", - "required": true, - "type": "string" - } - ], - "responses": { - "201": { - "description": "" - } - } - } - }, - "/admiralty/targets": { - "get": { - "tags": [ - "admiralty" - ], - "description": "find all Admiralty Target\n\u003cbr\u003e", - "operationId": "AdmiraltyController.GetAllTargets", - "responses": { - "200": { - "description": "" - } - } - } - }, - "/admiralty/targets/{execution}": { - "get": { - "tags": [ - "admiralty" - ], - "description": "find one Admiralty Target\n\u003cbr\u003e", - "operationId": "AdmiraltyController.GetOneTarget", - "parameters": [ - { - "in": "path", - "name": "id", - "description": "the name of the target to get", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "" - } - } - } - }, - "/booking/": { - "get": { - "tags": [ - "booking" - ], - "description": "find booking by id\n\u003cbr\u003e", - "operationId": "BookingController.GetAll", + "description": "Retourne toutes les images autorisées à persister sur ce peer\n\u003cbr\u003e", + "operationId": "AllowedImageController.GetAll", "parameters": [ { "in": "query", - "name": "is_draft", - "description": "draft wished", - "type": "string" - } - ], - "responses": { - "200": { - "description": "{booking} models.booking" - } - } - }, - "post": { - "tags": [ - "booking" - ], - "description": "create booking\n\u003cbr\u003e", - "operationId": "BookingController.Post.", - "parameters": [ - { - "in": "body", - "name": "booking", - "description": "the booking you want to post", - "required": true, - "schema": { - "type": "string" - }, + "name": "offset", + "description": "false", "type": "string" }, { "in": "query", - "name": "is_draft", - "description": "draft wished", + "name": "limit", + "description": "false", "type": "string" } ], @@ -260,44 +81,54 @@ "200": { "description": "", "schema": { - "$ref": "#/definitions/models.object" + "type": "array", + "items": { + "$ref": "#/definitions/allowed_image.AllowedImage" + } + } + } + } + }, + "post": { + "tags": [ + "allowed-image" + ], + "description": "Ajoute une image à la liste des images autorisées (peer admin uniquement)\n\u003cbr\u003e", + "operationId": "AllowedImageController.Post", + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Image à autoriser", + "required": true, + "schema": { + "$ref": "#/definitions/allowed_image.AllowedImage" + } + } + ], + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/allowed_image.AllowedImage" } } } } }, - "/booking/check/{id}/{start_date}/{end_date}": { + "/allowed-image/{id}": { "get": { "tags": [ - "booking" + "allowed-image" ], - "description": "check booking\n\u003cbr\u003e", - "operationId": "BookingController.Check", + "description": "Retourne une image autorisée par son ID\n\u003cbr\u003e", + "operationId": "AllowedImageController.Get", "parameters": [ { "in": "path", "name": "id", - "description": "id of the datacenter", - "type": "string" - }, - { - "in": "path", - "name": "start_date", - "description": "2006-01-02T15:04:05", - "type": "string", - "default": "the booking start date" - }, - { - "in": "path", - "name": "end_date", - "description": "2006-01-02T15:04:05", - "type": "string", - "default": "the booking end date" - }, - { - "in": "query", - "name": "is_draft", - "description": "draft wished", + "description": "ID de l'image autorisée", + "required": true, "type": "string" } ], @@ -305,84 +136,43 @@ "200": { "description": "", "schema": { - "$ref": "#/definitions/models.object" + "$ref": "#/definitions/allowed_image.AllowedImage" + } + } + } + }, + "delete": { + "tags": [ + "allowed-image" + ], + "description": "Supprime une image de la liste des images autorisées (peer admin uniquement, entrées bootstrap non supprimables)\n\u003cbr\u003e", + "operationId": "AllowedImageController.Delete", + "parameters": [ + { + "in": "path", + "name": "id", + "description": "ID de l'image autorisée", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/allowed_image.AllowedImage" } } } } }, - "/booking/search/execution/{id}": { + "/logs/{id}": { "get": { "tags": [ - "booking" - ], - "description": "search bookings by execution\n\u003cbr\u003e", - "operationId": "BookingController.Search", - "parameters": [ - { - "in": "path", - "name": "id", - "description": "id execution", - "required": true, - "type": "string" - }, - { - "in": "query", - "name": "is_draft", - "description": "draft wished", - "type": "string" - } - ], - "responses": { - "200": { - "description": "{workspace} models.workspace" - } - } - } - }, - "/booking/search/{start_date}/{end_date}": { - "get": { - "tags": [ - "booking" - ], - "description": "search bookings\n\u003cbr\u003e", - "operationId": "BookingController.Search", - "parameters": [ - { - "in": "path", - "name": "start_date", - "description": "the word search you want to get", - "required": true, - "type": "string" - }, - { - "in": "path", - "name": "end_date", - "description": "the word search you want to get", - "required": true, - "type": "string" - }, - { - "in": "query", - "name": "is_draft", - "description": "draft wished", - "type": "string" - } - ], - "responses": { - "200": { - "description": "{workspace} models.workspace" - } - } - } - }, - "/booking/{id}": { - "get": { - "tags": [ - "booking" + "oc-datacenter/controllersDatacenterController" ], "description": "find booking by id\n\u003cbr\u003e", - "operationId": "BookingController.Get", + "operationId": "DatacenterController.Log", "parameters": [ { "in": "path", @@ -397,36 +187,6 @@ "description": "{booking} models.booking" } } - }, - "put": { - "tags": [ - "booking" - ], - "description": "create computes\n\u003cbr\u003e", - "operationId": "BookingController.Update", - "parameters": [ - { - "in": "path", - "name": "id", - "description": "the compute id you want to get", - "required": true, - "type": "string" - }, - { - "in": "body", - "name": "body", - "description": "The compute content", - "required": true, - "schema": { - "$ref": "#/definitions/models.compute" - } - } - ], - "responses": { - "200": { - "description": "{compute} models.compute" - } - } } }, "/session/token/{id}/{duration}": { @@ -485,7 +245,55 @@ } } }, - "/{id}": { + "/{type}/search/{search}": { + "get": { + "tags": [ + "oc-datacenter/controllersDatacenterController" + ], + "description": "search datacenter\n\u003cbr\u003e", + "operationId": "DatacenterController.Search", + "parameters": [ + { + "in": "path", + "name": "type", + "description": "the type you want to get", + "required": true, + "type": "string" + }, + { + "in": "path", + "name": "search", + "description": "the word search you want to get", + "required": true, + "type": "string" + }, + { + "in": "query", + "name": "is_draft", + "description": "draft wished", + "type": "string" + }, + { + "in": "query", + "name": "offset", + "description": "false", + "type": "string" + }, + { + "in": "query", + "name": "limit", + "description": "false", + "type": "string" + } + ], + "responses": { + "200": { + "description": "{workspace} models.workspace" + } + } + } + }, + "/{type}/{id}": { "get": { "tags": [ "oc-datacenter/controllersDatacenterController" @@ -500,6 +308,47 @@ "required": true, "type": "string" }, + { + "in": "path", + "name": "type", + "description": "the word type you want to get", + "required": true, + "type": "string" + }, + { + "in": "query", + "name": "is_draft", + "description": "draft wished", + "type": "string" + } + ], + "responses": { + "200": { + "description": "{booking} models.booking" + } + } + }, + "delete": { + "tags": [ + "oc-datacenter/controllersDatacenterController" + ], + "description": "find booking by id\n\u003cbr\u003e", + "operationId": "DatacenterController.Delete", + "parameters": [ + { + "in": "path", + "name": "id", + "description": "the id you want to get", + "required": true, + "type": "string" + }, + { + "in": "path", + "name": "type", + "description": "the word type you want to get", + "required": true, + "type": "string" + }, { "in": "query", "name": "is_draft", @@ -516,21 +365,8 @@ } }, "definitions": { - "controllers.RemoteKubeconfig": { - "title": "RemoteKubeconfig", - "type": "object", - "properties": { - "Data": { - "type": "string" - } - } - }, - "models.compute": { - "title": "compute", - "type": "object" - }, - "models.object": { - "title": "object", + "allowed_image.AllowedImage": { + "title": "AllowedImage", "type": "object" } }, @@ -539,17 +375,13 @@ "name": "oc-datacenter/controllersDatacenterController", "description": "Operations about workspace\n" }, - { - "name": "booking", - "description": "Operations about workspace\n" - }, { "name": "version", "description": "VersionController operations for Version\n" }, { - "name": "admiralty", - "description": "Operations about the admiralty objects of the datacenter\n" + "name": "allowed-image", + "description": "AllowedImageController gère la liste locale des images autorisées à persister\nsur ce peer après l'exécution d'un workflow.\n\nGET /allowed-image/ → tous les utilisateurs authentifiés\nGET /allowed-image/:id → tous les utilisateurs authentifiés\nPOST /allowed-image/ → peer admin uniquement\nDELETE /allowed-image/:id → peer admin uniquement (bloqué si IsDefault)\n" } ] } \ No newline at end of file diff --git a/swagger/swagger.yml b/swagger/swagger.yml index 43a4fac..edb5ead 100644 --- a/swagger/swagger.yml +++ b/swagger/swagger.yml @@ -10,7 +10,7 @@ info: license: name: AGPL url: https://www.gnu.org/licenses/agpl-3.0.html -basePath: /oc/ +basePath: /oc paths: /: get: @@ -21,14 +21,27 @@ paths:
operationId: DatacenterController.GetAll parameters: + - in: path + name: type + description: the word type you want to get + required: true + type: string - in: query name: is_draft description: draft wished type: string + - in: query + name: offset + description: "false" + type: string + - in: query + name: limit + description: "false" + type: string responses: "200": description: '{booking} models.booking' - /{id}: + /{type}/{id}: get: tags: - oc-datacenter/controllersDatacenterController @@ -42,6 +55,11 @@ paths: description: the id you want to get required: true type: string + - in: path + name: type + description: the word type you want to get + required: true + type: string - in: query name: is_draft description: draft wished @@ -49,134 +67,24 @@ paths: responses: "200": description: '{booking} models.booking' - /admiralty/kubeconfig/{execution}: - get: + delete: tags: - - admiralty - parameters: - - in: path - name: execution - description: execution id of the workflow - required: true - type: string - responses: - "200": - description: "" - /admiralty/node/{execution}: - get: - tags: - - admiralty - parameters: - - in: path - name: execution - description: execution id of the workflow - required: true - type: string - responses: - "200": - description: "" - /admiralty/secret/{execution}: - get: - tags: - - admiralty - parameters: - - in: path - name: execution - description: execution id of the workflow - required: true - type: string - responses: - "200": - description: "" - post: - tags: - - admiralty - parameters: - - in: path - name: execution - description: execution id of the workflow - required: true - type: string - - in: body - name: kubeconfig - description: Kubeconfig to use when creating secret - required: true - schema: - $ref: '#/definitions/controllers.RemoteKubeconfig' - responses: - "201": - description: "" - /admiralty/source/{execution}: - post: - tags: - - admiralty - description: |- - Create an Admiralty Source on remote cluster -
- operationId: AdmiraltyController.CreateSource - parameters: - - in: path - name: execution - description: execution id of the workflow - required: true - type: string - responses: - "201": - description: "" - /admiralty/target/{execution}: - post: - tags: - - admiralty - description: |- - Create an Admiralty Target in the namespace associated to the executionID -
- operationId: AdmiraltyController.CreateAdmiraltyTarget - parameters: - - in: path - name: execution - description: execution id of the workflow - required: true - type: string - responses: - "201": - description: "" - /admiralty/targets: - get: - tags: - - admiralty - description: |- - find all Admiralty Target -
- operationId: AdmiraltyController.GetAllTargets - responses: - "200": - description: "" - /admiralty/targets/{execution}: - get: - tags: - - admiralty - description: |- - find one Admiralty Target -
- operationId: AdmiraltyController.GetOneTarget - parameters: - - in: path - name: id - description: the name of the target to get - required: true - type: string - responses: - "200": - description: "" - /booking/: - get: - tags: - - booking + - oc-datacenter/controllersDatacenterController description: |- find booking by id
- operationId: BookingController.GetAll + operationId: DatacenterController.Delete parameters: + - in: path + name: id + description: the id you want to get + required: true + type: string + - in: path + name: type + description: the word type you want to get + required: true + type: string - in: query name: is_draft description: draft wished @@ -184,38 +92,128 @@ paths: responses: "200": description: '{booking} models.booking' - post: + /{type}/search/{search}: + get: tags: - - booking + - oc-datacenter/controllersDatacenterController description: |- - create booking + search datacenter
- operationId: BookingController.Post. + operationId: DatacenterController.Search parameters: - - in: body - name: booking - description: the booking you want to post + - in: path + name: type + description: the type you want to get + required: true + type: string + - in: path + name: search + description: the word search you want to get required: true - schema: - type: string type: string - in: query name: is_draft description: draft wished type: string + - in: query + name: offset + description: "false" + type: string + - in: query + name: limit + description: "false" + type: string + responses: + "200": + description: '{workspace} models.workspace' + /allowed-image/: + get: + tags: + - allowed-image + description: |- + Retourne toutes les images autorisées à persister sur ce peer +
+ operationId: AllowedImageController.GetAll + parameters: + - in: query + name: offset + description: "false" + type: string + - in: query + name: limit + description: "false" + type: string responses: "200": description: "" schema: - $ref: '#/definitions/models.object' - /booking/{id}: + type: array + items: + $ref: '#/definitions/allowed_image.AllowedImage' + post: + tags: + - allowed-image + description: |- + Ajoute une image à la liste des images autorisées (peer admin uniquement) +
+ operationId: AllowedImageController.Post + parameters: + - in: body + name: body + description: Image à autoriser + required: true + schema: + $ref: '#/definitions/allowed_image.AllowedImage' + responses: + "200": + description: "" + schema: + $ref: '#/definitions/allowed_image.AllowedImage' + /allowed-image/{id}: get: tags: - - booking + - allowed-image + description: |- + Retourne une image autorisée par son ID +
+ operationId: AllowedImageController.Get + parameters: + - in: path + name: id + description: ID de l'image autorisée + required: true + type: string + responses: + "200": + description: "" + schema: + $ref: '#/definitions/allowed_image.AllowedImage' + delete: + tags: + - allowed-image + description: |- + Supprime une image de la liste des images autorisées (peer admin uniquement, entrées bootstrap non supprimables) +
+ operationId: AllowedImageController.Delete + parameters: + - in: path + name: id + description: ID de l'image autorisée + required: true + type: string + responses: + "200": + description: "" + schema: + $ref: '#/definitions/allowed_image.AllowedImage' + /logs/{id}: + get: + tags: + - oc-datacenter/controllersDatacenterController description: |- find booking by id
- operationId: BookingController.Get + operationId: DatacenterController.Log parameters: - in: path name: id @@ -225,107 +223,6 @@ paths: responses: "200": description: '{booking} models.booking' - put: - tags: - - booking - description: |- - create computes -
- operationId: BookingController.Update - parameters: - - in: path - name: id - description: the compute id you want to get - required: true - type: string - - in: body - name: body - description: The compute content - required: true - schema: - $ref: '#/definitions/models.compute' - responses: - "200": - description: '{compute} models.compute' - /booking/check/{id}/{start_date}/{end_date}: - get: - tags: - - booking - description: |- - check booking -
- operationId: BookingController.Check - parameters: - - in: path - name: id - description: id of the datacenter - type: string - - in: path - name: start_date - description: 2006-01-02T15:04:05 - type: string - default: the booking start date - - in: path - name: end_date - description: 2006-01-02T15:04:05 - type: string - default: the booking end date - - in: query - name: is_draft - description: draft wished - type: string - responses: - "200": - description: "" - schema: - $ref: '#/definitions/models.object' - /booking/search/{start_date}/{end_date}: - get: - tags: - - booking - description: |- - search bookings -
- operationId: BookingController.Search - parameters: - - in: path - name: start_date - description: the word search you want to get - required: true - type: string - - in: path - name: end_date - description: the word search you want to get - required: true - type: string - - in: query - name: is_draft - description: draft wished - type: string - responses: - "200": - description: '{workspace} models.workspace' - /booking/search/execution/{id}: - get: - tags: - - booking - description: |- - search bookings by execution -
- operationId: BookingController.Search - parameters: - - in: path - name: id - description: id execution - required: true - type: string - - in: query - name: is_draft - description: draft wished - type: string - responses: - "200": - description: '{workspace} models.workspace' /session/token/{id}/{duration}: get: tags: @@ -369,28 +266,22 @@ paths: "200": description: "" definitions: - controllers.RemoteKubeconfig: - title: RemoteKubeconfig - type: object - properties: - Data: - type: string - models.compute: - title: compute - type: object - models.object: - title: object + allowed_image.AllowedImage: + title: AllowedImage type: object tags: - name: oc-datacenter/controllersDatacenterController description: | Operations about workspace -- name: booking - description: | - Operations about workspace - name: version description: | VersionController operations for Version -- name: admiralty +- name: allowed-image description: | - Operations about the admiralty objects of the datacenter + AllowedImageController gère la liste locale des images autorisées à persister + sur ce peer après l'exécution d'un workflow. + + GET /allowed-image/ → tous les utilisateurs authentifiés + GET /allowed-image/:id → tous les utilisateurs authentifiés + POST /allowed-image/ → peer admin uniquement + DELETE /allowed-image/:id → peer admin uniquement (bloqué si IsDefault)