Datacenter no more handle booking but is fully charged with Kube & minio allocate per NATS

This commit is contained in:
mr
2026-02-25 09:08:40 +01:00
parent 41750dc054
commit b45c795002
19 changed files with 1494 additions and 2109 deletions

View File

@@ -1,601 +0,0 @@
package controllers
import (
"encoding/base64"
"encoding/json"
"fmt"
"oc-datacenter/conf"
"oc-datacenter/infrastructure"
"oc-datacenter/models"
"slices"
"time"
oclib "cloud.o-forge.io/core/oc-lib"
beego "github.com/beego/beego/v2/server/web"
jwt "github.com/golang-jwt/jwt/v5"
"gopkg.in/yaml.v2"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
)
type KubeInfo struct {
Url *string
KubeCA *string
KubeCert *string
KubeKey *string
}
type RemoteKubeconfig struct {
Data *string
}
type KubeUser struct {
Name string
User struct {
Token string
}
}
type KubeconfigToken struct {
ApiVersion string `yaml:"apiVersion"`
Kind string `yaml:"kind"`
Preferences string `yaml:"preferences"`
CurrentContext string `yaml:"current-context"`
Clusters []struct {
Cluster struct {
CA string `yaml:"certificate-authority-data"`
Server string `yaml:"server"`
} `yaml:"cluster"`
Name string `yaml:"name"`
} `yaml:"clusters"`
Contexts []struct {
Context struct {
Cluster string `yaml:"cluster"`
User string `yaml:"user"`
} `yaml:"context"`
Name string `yaml:"name"`
} `yaml:"contexts"`
Users []struct {
Name string `yaml:"name"`
User struct {
Token string `yaml:"token"`
} `yaml:"user"`
} `yaml:"users"`
}
// Operations about the admiralty objects of the datacenter
type AdmiraltyController struct {
beego.Controller
}
// @Title GetAllTargets
// @Description find all Admiralty Target
// @Success 200
// @router /targets [get]
func (c *AdmiraltyController) GetAllTargets() {
serv, err := infrastructure.NewService()
if err != nil {
// change code to 500
HandleControllerErrors(c.Controller, 500, &err, nil)
// c.Ctx.Output.SetStatus(500)
// c.ServeJSON()
// c.Data["json"] = map[string]string{"error": err.Error()}
return
}
res, err := serv.GetTargets(c.Ctx.Request.Context())
c.Data["json"] = res
c.ServeJSON()
}
// @Title GetOneTarget
// @Description find one Admiralty Target
// @Param id path string true "the name of the target to get"
// @Success 200
// @router /targets/:execution [get]
func (c *AdmiraltyController) GetOneTarget() {
id := c.Ctx.Input.Param(":execution")
serv, err := infrastructure.NewService()
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
c.ServeJSON()
c.Data["json"] = map[string]string{"error": err.Error()}
return
}
res, err := serv.GetTargets(c.Ctx.Request.Context())
if err != nil {
c.Ctx.Output.SetStatus(500)
c.ServeJSON()
c.Data["json"] = map[string]string{"error": err.Error()}
return
}
id = "target-" + id
found := slices.Contains(res, id)
if !found {
c.Ctx.Output.SetStatus(404)
c.ServeJSON()
}
c.Data["json"] = id
c.ServeJSON()
}
// @Title DeleteAdmiraltySession
// @Description find one Admiralty Target
// @Param execution path string true "the name of the target to get"
// @Success 200
// @router /targets/:execution [delete]
func (c *AdmiraltyController) DeleteAdmiraltySession() {
id := c.Ctx.Input.Param(":execution")
serv, err := infrastructure.NewService()
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
c.ServeJSON()
c.Data["json"] = map[string]string{"error": err.Error()}
return
}
err = serv.DeleteNamespace(c.Ctx.Request.Context(), id)
if err != nil {
c.Ctx.Output.SetStatus(500)
c.ServeJSON()
c.Data["json"] = map[string]string{"error": err.Error()}
return
}
c.Data["json"] = id
c.ServeJSON()
}
// @Title CreateAdmiraltySource
// @Description Create an Admiralty Source on remote cluster
// @Param execution path string true "execution id of the workflow"
// @Success 201
// @router /source/:execution [post]
func (c *AdmiraltyController) CreateAdmiraltySource() {
execution := c.Ctx.Input.Param(":execution")
fmt.Println("execution :: ", execution)
fmt.Println("input :: ", c.Ctx.Input)
serv, err := infrastructure.NewKubernetesService()
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
c.ServeJSON()
c.Data["json"] = map[string]string{"error": err.Error()}
c.ServeJSON()
return
}
res, err := serv.CreateAdmiraltySource(c.Ctx.Request.Context(), execution)
if err != nil {
if apierrors.IsAlreadyExists(err) {
c.Ctx.Output.SetStatus(409)
c.Data["json"] = map[string]string{"info": "A source already exists for this namespace : " + execution}
c.ServeJSON()
return
}
// change code to 500
c.Ctx.Output.SetStatus(500)
c.Data["json"] = map[string]string{"error": err.Error()}
c.ServeJSON()
return
}
// TODO : Return a description of the created resource
var respData map[string]interface{}
err = json.Unmarshal(res, &respData)
if err != nil {
c.Ctx.Output.SetStatus(500)
c.ServeJSON()
c.Data["json"] = map[string]string{"error": err.Error()}
return
}
c.Ctx.Output.SetStatus(201)
c.Data["json"] = respData
c.ServeJSON()
}
// @Title CreateAdmiraltyTarget
// @Description Create an Admiralty Target in the namespace associated to the executionID
// @Param execution path string true "execution id of the workflow"
// @Param peer path string true "peerId of the peer the target points to"
// @Success 201
// @router /target/:execution/:peer [post]
func (c *AdmiraltyController) CreateAdmiraltyTarget() {
var data map[string]interface{}
execution := c.Ctx.Input.Param(":execution")
peerId := c.Ctx.Input.Param(":peer")
if execution == "" || peerId == "" {
c.Ctx.Output.SetStatus(400)
c.Data["json"] = map[string]string{"error": "parameters can be empty " + "execution: " + execution + " peer: " + peerId}
c.ServeJSON()
return
}
serv, err := infrastructure.NewService()
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
c.Data["json"] = map[string]string{"error": err.Error()}
c.ServeJSON()
return
}
resp, err := serv.CreateAdmiraltyTarget(c.Ctx.Request.Context(), execution, peerId)
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
c.Data["json"] = map[string]string{"error": err.Error()}
c.ServeJSON()
return
}
if resp == nil {
fmt.Println("Error while trying to create Admiralty target")
fmt.Println(resp)
fmt.Println(err)
c.Ctx.Output.SetStatus(401)
c.Data["json"] = map[string]string{"error": "Could not perform the action"}
c.ServeJSON()
return
}
err = json.Unmarshal(resp, &data)
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
c.ServeJSON()
c.Data["json"] = map[string]string{"error": err.Error()}
return
}
c.Ctx.Output.SetStatus(201)
c.Data["json"] = data
c.ServeJSON()
}
// @Title GetKubeSecret
// @Description Retrieve the secret created from a Kubeconfig that will be associated to an Admiralty Target
// @Param execution path string true "execution id of the workflow"
// @Param peer path string true "UUID of the peer to which the resource is linked"
// @Success 200
// @router /secret/:execution/:peer [get]
func (c *AdmiraltyController) GetKubeSecret() {
var data map[string]interface{}
execution := c.Ctx.Input.Param(":execution")
peerId := c.Ctx.Input.Param(":peer")
serv, err := infrastructure.NewService()
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
c.Data["json"] = map[string]string{"error": err.Error()}
c.ServeJSON()
return
}
resp, err := serv.GetKubeconfigSecret(c.Ctx.Request.Context(), execution, peerId)
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
c.Data["json"] = map[string]string{"error": err.Error()}
c.ServeJSON()
return
}
if resp == nil {
c.Ctx.Output.SetStatus(404)
c.ServeJSON()
return
}
err = json.Unmarshal(resp, &data)
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
c.ServeJSON()
c.Data["json"] = map[string]string{"error": err.Error()}
return
}
c.Data["json"] = data
c.ServeJSON()
}
// @Title CreateKubeSecret
// @Description Creat a secret from a Kubeconfig that will be associated to an Admiralty Target
// @Param execution path string true "execution id of the workflow"
// @Param peer path string true "UUID of the peer to which the resource is linked"
// @Param kubeconfig body controllers.RemoteKubeconfig true "Kubeconfig to use when creating secret"
// @Success 201
// @router /secret/:execution/:peer [post]
func (c *AdmiraltyController) CreateKubeSecret() {
var kubeconfig RemoteKubeconfig
var respData map[string]interface{}
data := c.Ctx.Input.CopyBody(100000)
err := json.Unmarshal(data, &kubeconfig)
if err != nil {
fmt.Println("Error when retrieving the data for kubeconfig from request")
fmt.Println(err)
c.Ctx.Output.SetStatus(500)
c.Data["json"] = map[string]string{"error": err.Error()}
c.ServeJSON()
return
}
execution := c.Ctx.Input.Param(":execution")
peerId := c.Ctx.Input.Param(":peer")
serv, err := infrastructure.NewService()
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
c.Data["json"] = map[string]string{"error": err.Error()}
c.ServeJSON()
return
}
resp, err := serv.CreateKubeconfigSecret(c.Ctx.Request.Context(), *kubeconfig.Data, execution, peerId)
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
c.Data["json"] = map[string]string{"error": err.Error()}
c.ServeJSON()
return
}
err = json.Unmarshal(resp, &respData)
c.Ctx.Output.SetStatus(201)
c.Data["json"] = respData
c.ServeJSON()
}
// @name GetAdmiraltyNodes
// @description Allows user to test if an admiralty connection has already been established : Target and valid Secret set up on the local host and Source set up on remote host
// @Param execution path string true "execution id of the workflow"
// @Param peer path string true "UUID of the peer to which the resource is linked"
// @Success 200
// @router /node/:execution/:peer [get]
func (c *AdmiraltyController) GetNodeReady() {
var secret v1.Secret
execution := c.Ctx.Input.Param(":execution")
peerId := c.Ctx.Input.Param(":peer")
serv, err := infrastructure.NewService()
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
c.Data["json"] = map[string]string{"error": err.Error()}
c.ServeJSON()
return
}
node, err := serv.GetOneNode(c.Ctx.Request.Context(), execution, peerId)
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
c.Data["json"] = map[string]string{"error": err.Error()}
c.ServeJSON()
return
}
if node == nil {
c.Ctx.Output.SetStatus(404)
c.Data["json"] = map[string]string{
"node": "the node for " + execution + " can't be found, make sure both target and source resources are set up on local and remote hosts",
}
c.ServeJSON()
return
}
resp, err := serv.GetKubeconfigSecret(c.Ctx.Request.Context(), execution, peerId)
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
c.Data["json"] = map[string]string{"error": err.Error()}
c.ServeJSON()
return
}
if resp == nil {
c.Ctx.Output.SetStatus(500)
c.Data["json"] = map[string]string{"error": "Nodes was up but the secret can't be found"}
c.ServeJSON()
return
}
// Extract JWT token RS265 encoded
var editedKubeconfig map[string]interface{}
json.Unmarshal(resp, &secret)
byteEditedKubeconfig := secret.Data["config"]
err = yaml.Unmarshal(byteEditedKubeconfig, &editedKubeconfig)
// err = json.Unmarshal(byteEditedKubeconfig,&editedKubeconfig)
if err != nil {
fmt.Println("Error while retrieving the kubeconfig from secret-", execution)
fmt.Println(err)
c.Ctx.Output.SetStatus(500)
c.Data["json"] = err
c.ServeJSON()
return
}
token, err := retrieveTokenFromKonfig(editedKubeconfig)
if err != nil {
fmt.Println("Error while trying to retrieve token for kubeconfing")
fmt.Println(err)
HandleControllerErrors(c.Controller, 500, &err, nil)
}
// Decode token
isExpired, err := isTokenExpired(token)
if err != nil {
fmt.Println("Error veryfing token's expiration")
c.Ctx.Output.SetStatus(500)
c.Data["json"] = err
c.ServeJSON()
}
if *isExpired {
c.Data["json"] = map[string]interface{}{
"token": "token in the secret is expired and must be regenerated",
"node": node,
}
c.Ctx.Output.SetStatus(410)
c.ServeJSON()
}
c.Data["json"] = map[string]interface{}{"node": node, "token": true}
c.ServeJSON()
}
func retrieveTokenFromKonfig(editedKubeconfig map[string]interface{}) (string, error) {
var kubeUsers []KubeUser
b, err := yaml.Marshal(editedKubeconfig["users"])
if err != nil {
fmt.Println("Error while retrieving the users attribute from the Kubeconfig")
fmt.Println(err)
return "", err
}
err = yaml.Unmarshal(b, &kubeUsers)
if err != nil {
fmt.Println("Error while unmarshalling users attribute from kubeconfig")
fmt.Println(err)
return "", nil
}
fmt.Println(kubeUsers)
token := kubeUsers[0].User.Token
return token, nil
}
func isTokenExpired(token string) (*bool, error) {
logger := oclib.GetLogger()
t, _, err := new(jwt.Parser).ParseUnverified(token, jwt.MapClaims{})
if err != nil {
fmt.Println("couldn't decode token")
return nil, err
}
expiration, err := t.Claims.GetExpirationTime()
if err != nil {
fmt.Println("Error while checking token's expiration time")
return nil, err
}
logger.Debug().Msg("Expiration date : " + expiration.UTC().Format("2006-01-02T15:04:05"))
logger.Debug().Msg(fmt.Sprint("Now : ", time.Now().Unix()))
logger.Debug().Msg(fmt.Sprint("Token : ", expiration.Unix()))
expired := expiration.Unix() < time.Now().Unix()
return &expired, nil
}
// @name Get Admiralty Kubeconfig
// @description Retrieve a kubeconfig from the host with the token to authenticate as the SA from the namespace identified with execution id
// @Param execution path string true "execution id of the workflow"
// @Success 200
// @router /kubeconfig/:execution [get]
func (c *AdmiraltyController) GetAdmiraltyKubeconfig() {
execution := c.Ctx.Input.Param(":execution")
serv, err := infrastructure.NewService()
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
c.Data["json"] = map[string]string{"error": err.Error()}
c.ServeJSON()
return
}
generatedToken, err := serv.GenerateToken(c.Ctx.Request.Context(), execution, 3600)
if err != nil {
fmt.Println("Couldn't generate a token for ns-", execution)
fmt.Println(err)
c.Ctx.Output.SetStatus(500)
c.Data["json"] = map[string]string{"error": err.Error()}
c.ServeJSON()
return
}
kubeconfig, err := NewHostKubeWithToken(generatedToken)
if err != nil {
fmt.Println("Could not retrieve the Kubeconfig edited with token")
fmt.Println(err)
c.Ctx.Output.SetStatus(500)
c.Data["json"] = map[string]string{"error": err.Error()}
c.ServeJSON()
return
}
b, err := json.Marshal(kubeconfig)
if err != nil {
fmt.Println("Error while marshalling kubeconfig")
c.Ctx.Output.SetStatus(500)
c.Data["json"] = map[string]string{"error": err.Error()}
c.ServeJSON()
}
encodedKubeconfig := base64.StdEncoding.EncodeToString(b)
c.Data["json"] = map[string]string{
"data": encodedKubeconfig,
}
json.NewEncoder(c.Ctx.ResponseWriter)
c.ServeJSON()
}
func NewHostKubeWithToken(token string) (*models.KubeConfigValue, error) {
if len(token) == 0 {
return nil, fmt.Errorf("you didn't provide a token to be inserted in the Kubeconfig")
}
encodedCA := base64.StdEncoding.EncodeToString([]byte(conf.GetConfig().KubeCA))
hostKube := models.KubeConfigValue{
APIVersion: "v1",
CurrentContext: "default",
Kind: "Config",
Preferences: struct{}{},
Clusters: []models.KubeconfigNamedCluster{
{
Name: "default",
Cluster: models.KubeconfigCluster{
Server: "https://" + conf.GetConfig().KubeHost + ":6443",
CertificateAuthorityData: encodedCA,
},
},
},
Contexts: []models.KubeconfigNamedContext{
{
Name: "default",
Context: models.KubeconfigContext{
Cluster: "default",
User: "default",
},
},
},
Users: []models.KubeconfigUser{
{
Name: "default",
User: models.KubeconfigUserKeyPair{
Token: token,
},
},
},
}
return &hostKube, nil
}

View File

@@ -1,500 +0,0 @@
package controllers
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"oc-datacenter/infrastructure"
"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/booking"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
beego "github.com/beego/beego/v2/server/web"
"github.com/gorilla/websocket"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// Operations about workspace
type BookingController struct {
beego.Controller
}
var BookingExample booking.Booking
// @Title Search
// @Description search bookings by execution
// @Param id path string true "id execution"
// @Param is_draft query string false "draft wished"
// @Success 200 {workspace} models.workspace
// @router /search/execution/:id [get]
func (o *BookingController) ExecutionSearch() {
/*
* 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)
id := o.Ctx.Input.Param(":id")
isDraft := o.Ctx.Input.Query("is_draft")
f := dbs.Filters{
Or: map[string][]dbs.Filter{ // filter by name if no filters are provided
"execution_id": {{Operator: dbs.EQUAL.String(), Value: id}},
},
}
o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).Search(&f, "", isDraft == "true")
o.ServeJSON()
}
// @Title Search
// @Description search bookings
// @Param start_date path string true "the word search you want to get"
// @Param end_date path string true "the word search you want to get"
// @Param is_draft query string false "draft wished"
// @Success 200 {workspace} models.workspace
// @router /search/:start_date/:end_date [get]
func (o *BookingController) 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)
start_date, _ := time.Parse("2006-01-02", o.Ctx.Input.Param(":start_date"))
end_date, _ := time.Parse("2006-01-02", o.Ctx.Input.Param(":end_date"))
isDraft := o.Ctx.Input.Query("is_draft")
sd := primitive.NewDateTimeFromTime(start_date)
ed := primitive.NewDateTimeFromTime(end_date)
f := dbs.Filters{
And: map[string][]dbs.Filter{
"execution_date": {{Operator: "gte", Value: sd}, {Operator: "lte", Value: ed}},
},
}
o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).Search(&f, "", isDraft == "true")
o.ServeJSON()
}
// @Title GetAll
// @Description find booking by id
// @Param is_draft query string false "draft wished"
// @Success 200 {booking} models.booking
// @router / [get]
func (o *BookingController) GetAll() {
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
isDraft := o.Ctx.Input.Query("is_draft")
o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).LoadAll(isDraft == "true")
o.ServeJSON()
}
// @Title Get
// @Description find booking by id
// @Param id path string true "the id you want to get"
// @Success 200 {booking} models.booking
// @router /:id [get]
func (o *BookingController) Get() {
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
id := o.Ctx.Input.Param(":id")
o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).LoadOne(id)
o.ServeJSON()
}
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true }, // allow all origins
}
// @Title Log
// @Description find booking by id
// @Param id path string true "the id you want to get"
// @Success 200 {booking} models.booking
// @router /:id [get]
func (o *BookingController) Log() {
// user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
id := o.Ctx.Input.Param(":id")
conn, err := upgrader.Upgrade(o.Ctx.ResponseWriter, o.Ctx.Request, nil)
if err != nil {
o.Ctx.WriteString("WebSocket upgrade failed: " + err.Error())
return
}
defer conn.Close()
monitors, err := monitor.NewMonitorService()
if err != nil {
o.Ctx.WriteString("Monitor service unavailable: " + err.Error())
return
}
ctx := monitor.StreamRegistry.Register(id)
monitors.Stream(ctx, id, 1*time.Second, conn)
}
// @Title Update
// @Description create computes
// @Param id path string true "the compute id you want to get"
// @Param body body models.compute true "The compute content"
// @Success 200 {compute} models.compute
// @router /:id [put]
func (o *BookingController) Put() {
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
// store and return Id or post with UUID
var res map[string]interface{}
id := o.Ctx.Input.Param(":id")
book := oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).LoadOne(id)
if book.Code != 200 {
o.Data["json"] = map[string]interface{}{
"data": nil,
"code": book.Code,
"error": book.Err,
}
o.ServeJSON()
return
}
booking := book.Data.(*booking.Booking)
if time.Now().After(booking.ExpectedStartDate) {
o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).UpdateOne(res, id)
} else {
o.Data["json"] = map[string]interface{}{
"data": nil,
"code": 409,
"error": "booking is not already started",
}
}
o.ServeJSON()
}
// @Title Check
// @Description check booking
// @Param id path string "id of the datacenter"
// @Param start_date path string "the booking start date" format "2006-01-02T15:04:05"
// @Param end_date path string "the booking end date" format "2006-01-02T15:04:05"
// @Param is_draft query string false "draft wished"
// @Success 200 {object} models.object
// @router /check/:id/:start_date/:end_date [get]
func (o *BookingController) Check() {
/*
* This function is used to check if a booking is available for a specific datacenter.
* It takes the following parameters:
* - id: the id of the datacenter
* - start_date: the start date of the booking/search/execution/:id
* - end_date: the end date of the booking
*/
id := o.Ctx.Input.Param(":id")
date, err := time.Parse("2006-01-02T15:04:05", o.Ctx.Input.Param(":start_date"))
date2, err2 := time.Parse("2006-01-02T15:04:05", o.Ctx.Input.Param(":end_date"))
if err != nil || err2 != nil {
o.Data["json"] = map[string]interface{}{
"data": map[string]interface{}{
"is_available": false,
},
"code": 400,
"error": errors.New("invalid date format"),
}
} else {
bks := &booking.Booking{} // create a new booking object
code := 200
err := ""
if isAvailable, err2 := bks.Check(id, date, &date2, 1); !isAvailable {
code = 409
err = "booking not available"
if err2 != nil {
err += " - " + err2.Error()
}
}
o.Data["json"] = map[string]interface{}{
"data": map[string]interface{}{
"is_available": true,
},
"code": code,
"error": err,
}
}
o.ServeJSON()
}
// @Title Post.
// @Description create booking
// @Param booking body string true "the booking you want to post"
// @Param is_draft query string false "draft wished"
// @Success 200 {object} models.object
// @router / [post]
func (o *BookingController) Post() {
/*
* This function is used to create a booking.
* It takes the following parameters:
* - booking: the booking you want to post
* The booking is a JSON object that contains the following fields:
* - datacenter_resource_id: the id of the datacenter
* - workflow_execution: the workflow execution
*/
logger := oclib.GetLogger()
logger.Info().Msg("Received a Booking")
var resp booking.Booking
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
err := json.Unmarshal(o.Ctx.Input.CopyBody(10000000), &resp)
if err != nil {
o.Data["json"] = map[string]interface{}{
"data": nil,
"code": 422,
"error": err,
}
o.ServeJSON()
return
}
if resp.ResourceType == tools.COMPUTE_RESOURCE {
// later should check... health for any such as docker...
res := oclib.NewRequest(oclib.LibDataEnum(oclib.COMPUTE_RESOURCE), user, peerID, groups, nil).LoadOne(resp.ResourceID)
if res.Err != "" {
o.Data["json"] = map[string]interface{}{
"data": nil,
"code": res.Code,
"error": res.Err,
}
o.ServeJSON()
return
}
/*serv, err := infrastructure.NewServiceByType(res.ToComputeResource().Infrastructure.String())
if err != nil {
o.Data["json"] = map[string]interface{}{
"data": nil,
"code": 500,
"error": err,
}
o.ServeJSON()
return
}
if err := serv.CheckHealth(); err != nil {
o.Data["json"] = map[string]interface{}{
"data": nil,
"code": 500,
"error": err,
}
o.ServeJSON()
return
}*/
}
// delete all previous bookings
isDraft := o.Ctx.Input.Query("is_draft")
res := oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).Search(&dbs.Filters{And: map[string][]dbs.Filter{
"workflow_id": {{Operator: dbs.EQUAL.String(), Value: resp.WorkflowID}},
"resource_id": {{Operator: dbs.EQUAL.String(), Value: resp.ResourceID}},
}}, "", isDraft == "true")
if res.Code != 200 {
o.Data["json"] = map[string]interface{}{
"data": nil,
"code": res.Code,
"error": res.Err,
}
o.ServeJSON()
return
}
for _, b := range res.Data { // delete all previous bookings
oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).DeleteOne(b.GetID())
}
b := oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).StoreOne(resp.Serialize(&resp))
if b.Code != 200 {
o.Data["json"] = map[string]interface{}{
"data": nil,
"code": b.Code,
"error": b.Err,
}
o.ServeJSON()
return
}
if b.Data.(*booking.Booking).ResourceType == tools.COMPUTE_RESOURCE {
monitors, monErr := monitor.NewMonitorService()
if monErr == nil {
go func() {
time.Sleep(time.Until(b.Data.(*booking.Booking).ExpectedStartDate))
ctx := monitor.StreamRegistry.Register(b.Data.(*booking.Booking).ExecutionsID)
monitors.Stream(ctx, b.Data.GetID(), 1*time.Second, nil)
}()
}
}
logger.Info().Msg("Creating new namespace : " + resp.ExecutionsID)
if err := o.createNamespace(resp.ExecutionsID); err != nil {
logger.Debug().Msg("Error when creating a namespace")
fmt.Println(err.Error())
}
o.Data["json"] = map[string]interface{}{
"data": b.Data,
"code": 200,
"error": "",
}
o.ServeJSON()
}
// @Title ExtendForNamespace
// @Description ExtendForNamespace booking
// @Param namespace path string "targetted namespace"
// @Param resource_id path string "resource id"
// @Param end_date path string "the booking end date" format "2006-01-02T15:04:05"
// @Param is_draft query string false "draft wished"
// @Success 200 {object} models.object
// @router /extend/:resource_id/from_namespace/:namespace/to/:duration [post]
func (o *BookingController) ExtendForNamespace() {
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
resourceID := o.Ctx.Input.Param(":resource_id")
namespace := o.Ctx.Input.Param(":namespace")
duration, err := strconv.Atoi(o.Ctx.Input.Param(":duration"))
if err != nil {
o.Data["json"] = map[string]interface{}{
"data": map[string]interface{}{
"is_available": false,
},
"code": 400,
"error": errors.New("invalid date format"),
}
} else {
o.extend(duration, resourceID, "executions_id", namespace, user, peerID, groups)
}
}
// @Title ExtendForExecution
// @Description ExtendForExecution booking
// @Param namespace path string "targetted namespace"
// @Param resource_id path string "resource id"
// @Param end_date path string "the booking end date" format "2006-01-02T15:04:05"
// @Param is_draft query string false "draft wished"
// @Success 200 {object} models.object
// @router /extend/:resource_id/from_execution/:execution_id/to/:duration [post]
func (o *BookingController) ExtendForExecution() {
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
resourceID := o.Ctx.Input.Param(":resource_id")
executionID := o.Ctx.Input.Param(":execution_id")
duration, err := strconv.Atoi(o.Ctx.Input.Param(":duration"))
if err != nil {
o.Data["json"] = map[string]interface{}{
"data": map[string]interface{}{
"is_available": false,
},
"code": 400,
"error": errors.New("invalid date format"),
}
} else {
o.extend(duration, resourceID, "execution_id", executionID, user, peerID, groups)
}
}
func (o *BookingController) extend(duration int, resourceID string, key string, namespace string, user string, peerID string, groups []string) {
/*
* This function is used to check if a booking is available for a specific datacenter.
* It takes the following parameters:
* - id: the id of the datacenter
* - start_date: the start date of the booking/search/execution/:id
* - end_date: the end date of the booking
*/
req := oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil)
res := req.Search(&dbs.Filters{
Or: map[string][]dbs.Filter{
"expected_end_date": {
{Operator: dbs.GTE.String(), Value: time.Now()},
{Operator: dbs.EQUAL.String(), Value: bson.TypeNull},
},
},
And: map[string][]dbs.Filter{
"resource_id": {{Operator: dbs.EQUAL.String(), Value: resourceID}},
key: {{Operator: dbs.EQUAL.String(), Value: namespace}},
"expected_start_date": {{Operator: dbs.GTE.String(), Value: time.Now()}},
},
}, "", false)
if res.Err == "" && len(res.Data) > 0 {
var id string
datas := []utils.DBObject{}
for _, b := range res.Data {
book := b.(*booking.Booking)
if book.ExpectedEndDate != nil {
bb := book.ExpectedEndDate.Add(time.Duration(duration) * time.Second)
if isAvailable, err := (&booking.Booking{}).Check(id, (*book.ExpectedEndDate).Add(1*time.Second), &bb, 1); isAvailable && err == nil {
book.ExpectedEndDate = &bb
result := req.UpdateOne(book.Serialize(book), book.GetID())
datas = append(datas, result.Data)
}
}
}
o.Data["json"] = map[string]interface{}{
"data": datas,
"code": 200,
"error": "",
}
o.ServeJSON()
return
} else if res.Err == "" {
o.Data["json"] = map[string]interface{}{
"data": map[string]interface{}{
"is_available": false,
},
"code": 400,
"error": errors.New("can't find any booking to extend"),
}
} else {
o.Data["json"] = res
}
o.ServeJSON()
}
func (o *BookingController) createNamespace(ns string) error {
/*
* This function is used to create a namespace.
* It takes the following parameters:
* - ns: the namespace you want to create
*/
logger := oclib.GetLogger()
serv, err := infrastructure.NewService()
if err != nil {
return nil
}
ok, err := serv.GetNamespace(o.Ctx.Request.Context(), ns)
if ok != nil && err == nil {
logger.Debug().Msg("A namespace with name " + ns + " already exists")
return nil
}
if err != nil {
return err
}
err = serv.CreateNamespace(o.Ctx.Request.Context(), ns)
if err != nil {
return err
}
err = serv.CreateServiceAccount(o.Ctx.Request.Context(), ns)
if err != nil {
return err
}
role := "argo-role"
err = serv.CreateRole(o.Ctx.Request.Context(), ns, role,
[][]string{
{"coordination.k8s.io"},
{""},
{""}},
[][]string{
{"leases"},
{"secrets"},
{"pods"}},
[][]string{
{"get", "create", "update"},
{"get"},
{"patch"}})
if err != nil {
return err
}
return serv.CreateRoleBinding(o.Ctx.Request.Context(), ns, "argo-role-binding", role)
}

View File

@@ -1,9 +1,14 @@
package controllers
import (
"net/http"
"oc-datacenter/infrastructure/monitor"
"time"
oclib "cloud.o-forge.io/core/oc-lib"
"cloud.o-forge.io/core/oc-lib/dbs"
beego "github.com/beego/beego/v2/server/web"
"github.com/gorilla/websocket"
)
// Operations about workspace
@@ -19,12 +24,12 @@ type DatacenterController struct {
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.STORAGE_RESOURCE), user, peerID, groups, nil).Search(&dbs.Filters{
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.COMPUTE_RESOURCE), user, peerID, groups, nil).Search(&dbs.Filters{
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}},
},
@@ -47,14 +52,14 @@ 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.STORAGE_RESOURCE), user, peerID, groups, nil).Search(&dbs.Filters{
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.COMPUTE_RESOURCE), user, peerID, groups, nil).Search(&dbs.Filters{
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}},
@@ -83,3 +88,30 @@ func (o *DatacenterController) Get() {
}
o.ServeJSON()
}
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true }, // allow all origins
}
// @Title Log
// @Description find booking by id
// @Param id path string true "the id you want to get"
// @Success 200 {booking} models.booking
// @router /:id [get]
func (o *DatacenterController) Log() {
// user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
id := o.Ctx.Input.Param(":id")
conn, err := upgrader.Upgrade(o.Ctx.ResponseWriter, o.Ctx.Request, nil)
if err != nil {
o.Ctx.WriteString("WebSocket upgrade failed: " + err.Error())
return
}
defer conn.Close()
monitors, err := monitor.NewMonitorService()
if err != nil {
o.Ctx.WriteString("Monitor service unavailable: " + err.Error())
return
}
ctx := monitor.StreamRegistry.Register(id)
monitors.Stream(ctx, id, 1*time.Second, conn)
}

View File

@@ -1,130 +0,0 @@
package controllers
import (
"oc-datacenter/infrastructure"
oclib "cloud.o-forge.io/core/oc-lib"
"cloud.o-forge.io/core/oc-lib/models/live"
beego "github.com/beego/beego/v2/server/web"
)
type MinioController struct {
beego.Controller
}
// @Title CreateServiceAccounnt
// @Description Add a new ServiceAccount to a Minio server using its ID and an execution ID
// @Success 200
// @Param executions path string true "The executionsID of the execution"
// @Param minioId path string true "The ID of the Minio you want to reach"
// @router /serviceaccount/:minioId/:executions [post]
func (m *MinioController) CreateServiceAccount() {
_, peerID, _ := oclib.ExtractTokenInfo(*m.Ctx.Request)
// This part is solely for dev purposes and should be removed once test on
executionsId := m.Ctx.Input.Param(":executions")
minioId := m.Ctx.Input.Param(":minioId")
// retrieve the live storage with the minioId
s := oclib.NewRequest(oclib.LibDataEnum(oclib.STORAGE_RESOURCE), "", "", []string{}, nil).LoadOne(minioId)
if s.Err != "" {
m.Ctx.Output.SetStatus(400)
m.Data["json"] = map[string]interface{}{"error": " Could not load the storage resource with id " + minioId + ": " + s.Err}
m.ServeJSON()
return
}
live := findLiveStorage(minioId, peerID)
if live == nil {
m.Ctx.Output.SetStatus(404)
m.Data["json"] = map[string]interface{}{"error":"could not find the Minio instance " + s.Err}
m.ServeJSON()
return
}
url := live.Source
service := infrastructure.NewMinioService(url)
// call the method ctrating the svcacc
err := service.CreateClient()
if err != nil {
m.Ctx.Output.SetStatus(500)
m.Data["json"] = map[string]interface{}{"error":"could not create the client for " + minioId + " : " + err.Error()}
m.ServeJSON()
return
}
access, secret, err := service.CreateCredentials(executionsId)
if err != nil {
m.Ctx.Output.SetStatus(500)
m.Data["json"] = map[string]interface{}{"error":"could not create the service account for " + minioId + " : " + err.Error()}
m.ServeJSON()
return
}
err = service.CreateBucket(executionsId)
if err != nil {
m.Ctx.Output.SetStatus(500)
m.Data["json"] = map[string]interface{}{"error":"could not create the service account for " + minioId + " : " + err.Error()}
m.ServeJSON()
return
}
// test if the namespace exists
k, err := infrastructure.NewService()
if err != nil {
m.Ctx.Output.SetStatus(500)
m.Data["json"] = map[string]string{"error": err.Error()}
m.ServeJSON()
return
}
ns, err := k.GetNamespace(m.Ctx.Request.Context(), executionsId)
if ns == nil {
m.Ctx.Output.SetStatus(403)
m.Data["json"] = map[string]string{"error":"Could not find the namespace corresponding to executionsID " + executionsId}
m.ServeJSON()
return
}
if err != nil {
m.Ctx.Output.SetStatus(500)
m.Data["json"] = map[string]string{"error": "Error when trying to check if namespace " + executionsId + " exists : " + err.Error()}
m.ServeJSON()
return
}
// store the credentials in the namespace
err = k.CreateSecret(m.Ctx.Request.Context(), minioId, executionsId, access, secret)
if err != nil {
m.Ctx.Output.SetStatus(500)
m.Data["json"] = map[string]string{"error": "Error when storing Minio serviceAccount credentials in namespace " + executionsId + " exists : " + err.Error()}
m.ServeJSON()
return
}
m.Data["json"] = map[string]string{"success": "created secret " + executionsId + "-secret-sa in namespace ns-" + executionsId}
m.ServeJSON()
}
func findLiveStorage(storageId string, peerId string) *live.LiveStorage {
res := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_STORAGE),"",peerId,[]string{},nil).LoadAll(false)
if res.Err != "" {
l := oclib.GetLogger()
l.Error().Msg(res.Err)
return nil
}
for _, dbo := range res.Data {
r := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_STORAGE),"","",[]string{},nil).LoadOne(dbo.GetID())
l := r.ToLiveStorage()
for _, id := range l.ResourcesID {
if id == storageId {
return l
}
}
}
return nil
}

View File

@@ -2,9 +2,10 @@ package controllers
import (
"fmt"
"oc-datacenter/infrastructure"
"oc-datacenter/conf"
"strconv"
"cloud.o-forge.io/core/oc-lib/tools"
beego "github.com/beego/beego/v2/server/web"
)
@@ -31,7 +32,8 @@ func (o *SessionController) GetToken() {
return
}
serv, err := infrastructure.NewService()
serv, err := tools.NewKubernetesService(conf.GetConfig().KubeHost+":"+conf.GetConfig().KubePort,
conf.GetConfig().KubeCA, conf.GetConfig().KubeCert, conf.GetConfig().KubeData)
if err != nil {
// change code to 500
o.Ctx.Output.SetStatus(500)